🌈 Color histograms for images, along with stats
This commit is contained in:
@@ -22,6 +22,7 @@ import {
|
|||||||
IMG_ANALYSIS_DATA_FETCH_SUCCESS,
|
IMG_ANALYSIS_DATA_FETCH_SUCCESS,
|
||||||
IMS_COMIC_BOOK_ARCHIVE_EXTRACTION_SUCCESS,
|
IMS_COMIC_BOOK_ARCHIVE_EXTRACTION_SUCCESS,
|
||||||
IMS_COMIC_BOOK_ARCHIVE_EXTRACTION_CALL_IN_PROGRESS,
|
IMS_COMIC_BOOK_ARCHIVE_EXTRACTION_CALL_IN_PROGRESS,
|
||||||
|
FILEOPS_STATE_RESET,
|
||||||
} from "../constants/action-types";
|
} from "../constants/action-types";
|
||||||
import { success } from "react-notification-system-redux";
|
import { success } from "react-notification-system-redux";
|
||||||
import { isNil, map } from "lodash";
|
import { isNil, map } from "lodash";
|
||||||
@@ -258,7 +259,10 @@ export const extractComicArchive =
|
|||||||
|
|
||||||
export const analyzeImage =
|
export const analyzeImage =
|
||||||
(imageFilePath: string | Buffer) => async (dispatch) => {
|
(imageFilePath: string | Buffer) => async (dispatch) => {
|
||||||
console.log(imageFilePath);
|
dispatch({
|
||||||
|
type: FILEOPS_STATE_RESET,
|
||||||
|
});
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: IMG_ANALYSIS_CALL_IN_PROGRESS,
|
type: IMG_ANALYSIS_CALL_IN_PROGRESS,
|
||||||
});
|
});
|
||||||
@@ -270,7 +274,6 @@ export const analyzeImage =
|
|||||||
imageFilePath,
|
imageFilePath,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
console.log(foo);
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: IMG_ANALYSIS_DATA_FETCH_SUCCESS,
|
type: IMG_ANALYSIS_DATA_FETCH_SUCCESS,
|
||||||
result: foo.data,
|
result: foo.data,
|
||||||
|
|||||||
@@ -42,11 +42,20 @@ pre {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Dashboard
|
// Dashboard
|
||||||
|
// Comic Detail
|
||||||
|
.stats-palette {
|
||||||
|
background-color: #fff6de;
|
||||||
|
display: inline-block;
|
||||||
|
p {
|
||||||
|
display: flex;
|
||||||
|
// padding: 1.5rem 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
.recent-comics-container {
|
.recent-comics-container {
|
||||||
display: -webkit-box; /* Not needed if autoprefixing */
|
display: -webkit-box; /* Not needed if autoprefixing */
|
||||||
display: -ms-flexbox; /* Not needed if autoprefixing */
|
display: -ms-flexbox; /* Not needed if autoprefixing */
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-left: -30px; /* gutter size offset */
|
margin-left: -22px; /* gutter size offset */
|
||||||
width: auto;
|
width: auto;
|
||||||
|
|
||||||
.recent-comics-column {
|
.recent-comics-column {
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import React, { ReactElement, useCallback, useState } from "react";
|
import React, { ReactElement, useCallback, useState } from "react";
|
||||||
import { useSelector, useDispatch } from "react-redux";
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
import { DnD } from "../../DnD";
|
import { DnD } from "../../DnD";
|
||||||
import { isEmpty } from "lodash";
|
import { isEmpty, isNil, isUndefined } from "lodash";
|
||||||
import Sticky from "react-stickynode";
|
import Sticky from "react-stickynode";
|
||||||
import SlidingPane from "react-sliding-pane";
|
import SlidingPane from "react-sliding-pane";
|
||||||
import { extractComicArchive } from "../../../actions/fileops.actions";
|
import { extractComicArchive } from "../../../actions/fileops.actions";
|
||||||
import { analyzeImage } from "../../../actions/fileops.actions";
|
import { analyzeImage } from "../../../actions/fileops.actions";
|
||||||
|
import { Canvas } from "../../shared/Canvas";
|
||||||
|
|
||||||
export const ArchiveOperations = (props): ReactElement => {
|
export const ArchiveOperations = (props): ReactElement => {
|
||||||
const { data } = props;
|
const { data } = props;
|
||||||
@@ -16,9 +17,9 @@ export const ArchiveOperations = (props): ReactElement => {
|
|||||||
(state: RootState) => state.fileOps.extractedComicBookArchive,
|
(state: RootState) => state.fileOps.extractedComicBookArchive,
|
||||||
);
|
);
|
||||||
|
|
||||||
const imageAnalysisResult = useSelector(
|
const imageAnalysisResult = useSelector((state: RootState) => {
|
||||||
(state: RootState) => state.fileOps.imageAnalysisResults,
|
return state.fileOps.imageAnalysisResults;
|
||||||
);
|
});
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const unpackComicArchive = useCallback(() => {
|
const unpackComicArchive = useCallback(() => {
|
||||||
@@ -44,9 +45,14 @@ export const ArchiveOperations = (props): ReactElement => {
|
|||||||
content: () => {
|
content: () => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<pre>{currentImage}</pre>
|
<pre className="is-size-7">{currentImage}</pre>
|
||||||
<pre className="is-size-7">
|
{!isEmpty(imageAnalysisResult) ? (
|
||||||
{JSON.stringify(imageAnalysisResult, null, 2)}
|
<pre className="is-size-7 p-2 mt-3">
|
||||||
|
<Canvas data={imageAnalysisResult} />
|
||||||
|
</pre>
|
||||||
|
) : null}
|
||||||
|
<pre className="is-size-7 mt-3">
|
||||||
|
{JSON.stringify(imageAnalysisResult.analyzedData, null, 2)}
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -32,6 +32,28 @@ export const Dashboard = (): ReactElement => {
|
|||||||
|
|
||||||
{!isEmpty(recentComics) && !isEmpty(recentComics.docs) ? (
|
{!isEmpty(recentComics) && !isEmpty(recentComics.docs) ? (
|
||||||
<>
|
<>
|
||||||
|
{/* stats */}
|
||||||
|
<div>
|
||||||
|
<div className="box stats-palette p-3 column is-one-quarter">
|
||||||
|
<dl>
|
||||||
|
<dd className="is-size-4">
|
||||||
|
<span className="has-text-weight-bold">113123</span> files
|
||||||
|
</dd>
|
||||||
|
<dd className="is-size-6">
|
||||||
|
<span className="has-text-weight-bold">140</span> tagged
|
||||||
|
with ComicVine
|
||||||
|
</dd>
|
||||||
|
<dd className="is-size-6">
|
||||||
|
<span className="has-text-weight-bold">1304</span> with
|
||||||
|
custom metadata
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="box stats-palette p-3 column ml-5 is-one-quarter">
|
||||||
|
asdasd
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<RecentlyImported comicBookCovers={recentComics} />
|
<RecentlyImported comicBookCovers={recentComics} />
|
||||||
{!isNil(volumeGroups) ? <VolumeGroups /> : null}
|
{!isNil(volumeGroups) ? <VolumeGroups /> : null}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { Link } from "react-router-dom";
|
|||||||
export const SearchBar = (): ReactElement => {
|
export const SearchBar = (): ReactElement => {
|
||||||
const foo = () => {};
|
const foo = () => {};
|
||||||
return (
|
return (
|
||||||
<div className="box columns sticky">
|
<div className="box sticky">
|
||||||
<Form
|
<Form
|
||||||
onSubmit={foo}
|
onSubmit={foo}
|
||||||
initialValues={{}}
|
initialValues={{}}
|
||||||
|
|||||||
@@ -47,8 +47,9 @@ export const LibraryGrid = (libraryGridProps: ILibraryGridProps) => {
|
|||||||
let comicName = "";
|
let comicName = "";
|
||||||
if (!isNil(rawFileDetails)) {
|
if (!isNil(rawFileDetails)) {
|
||||||
const encodedFilePath = encodeURI(
|
const encodedFilePath = encodeURI(
|
||||||
`${LIBRARY_SERVICE_HOST}` +
|
`${LIBRARY_SERVICE_HOST}/${removeLeadingPeriod(
|
||||||
removeLeadingPeriod(rawFileDetails.cover.filePath),
|
rawFileDetails.cover.filePath,
|
||||||
|
)}`,
|
||||||
);
|
);
|
||||||
imagePath = escapePoundSymbol(encodedFilePath);
|
imagePath = escapePoundSymbol(encodedFilePath);
|
||||||
comicName = rawFileDetails.name;
|
comicName = rawFileDetails.name;
|
||||||
|
|||||||
64
src/client/components/shared/Canvas.tsx
Normal file
64
src/client/components/shared/Canvas.tsx
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import React, { useEffect, useRef } from "react";
|
||||||
|
|
||||||
|
export const Canvas = (data) => {
|
||||||
|
const { colorHistogramData } = data.data;
|
||||||
|
console.log(data);
|
||||||
|
const width = 559;
|
||||||
|
const height = 200;
|
||||||
|
const pixelRatio = window.devicePixelRatio;
|
||||||
|
|
||||||
|
const canvas = useRef(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const context = canvas.current.getContext("2d");
|
||||||
|
|
||||||
|
const guideHeight = 8;
|
||||||
|
const startY = height - guideHeight;
|
||||||
|
const dx = width / 256;
|
||||||
|
const dy = startY / colorHistogramData.maxBrightness;
|
||||||
|
context.lineWidth = dx;
|
||||||
|
context.fillStyle = "transparent";
|
||||||
|
context.fillRect(0, 0, width, height);
|
||||||
|
|
||||||
|
for (let i = 0; i < 256; i++) {
|
||||||
|
const x = i * dx;
|
||||||
|
|
||||||
|
// Red
|
||||||
|
context.strokeStyle = "rgba(220,0,0,0.5)";
|
||||||
|
context.beginPath();
|
||||||
|
context.moveTo(x, startY);
|
||||||
|
context.lineTo(x, startY - colorHistogramData.r[i] * dy);
|
||||||
|
context.closePath();
|
||||||
|
context.stroke();
|
||||||
|
// Green
|
||||||
|
context.strokeStyle = "rgba(0,210,0,0.5)";
|
||||||
|
context.beginPath();
|
||||||
|
context.moveTo(x, startY);
|
||||||
|
context.lineTo(x, startY - colorHistogramData.g[i] * dy);
|
||||||
|
context.closePath();
|
||||||
|
context.stroke();
|
||||||
|
// Blue
|
||||||
|
context.strokeStyle = "rgba(0,0,255,0.5)";
|
||||||
|
context.beginPath();
|
||||||
|
context.moveTo(x, startY);
|
||||||
|
context.lineTo(x, startY - colorHistogramData.b[i] * dy);
|
||||||
|
context.closePath();
|
||||||
|
context.stroke();
|
||||||
|
|
||||||
|
// Guide
|
||||||
|
context.strokeStyle = "rgb(" + i + ", " + i + ", " + i + ")";
|
||||||
|
context.beginPath();
|
||||||
|
context.moveTo(x, startY);
|
||||||
|
context.lineTo(x, height);
|
||||||
|
context.closePath();
|
||||||
|
context.stroke();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const dw = Math.floor(pixelRatio * width);
|
||||||
|
const dh = Math.floor(pixelRatio * height);
|
||||||
|
const style = { width, height };
|
||||||
|
return <canvas ref={canvas} width={dw} height={dh} style={style} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Canvas;
|
||||||
@@ -70,6 +70,9 @@ export const IMG_ANALYSIS_DATA_FETCH_SUCCESS =
|
|||||||
"IMG_ANALYSIS_DATA_FETCH_SUCCESS";
|
"IMG_ANALYSIS_DATA_FETCH_SUCCESS";
|
||||||
export const IMG_ANALYSIS_DATA_FETCH_ERROR = "IMG_ANALYSIS_DATA_FETCH_ERROR";
|
export const IMG_ANALYSIS_DATA_FETCH_ERROR = "IMG_ANALYSIS_DATA_FETCH_ERROR";
|
||||||
|
|
||||||
|
// fileops cleanup
|
||||||
|
export const FILEOPS_STATE_RESET = "FILEOPS_STATE_RESET";
|
||||||
|
|
||||||
// AirDC++
|
// AirDC++
|
||||||
export const AIRDCPP_SEARCH_IN_PROGRESS = "AIRDCPP_SEARCH_IN_PROGRESS";
|
export const AIRDCPP_SEARCH_IN_PROGRESS = "AIRDCPP_SEARCH_IN_PROGRESS";
|
||||||
export const AIRDCPP_SEARCH_RESULTS_ADDED = "AIRDCPP_SEARCH_RESULTS_ADDED";
|
export const AIRDCPP_SEARCH_RESULTS_ADDED = "AIRDCPP_SEARCH_RESULTS_ADDED";
|
||||||
|
|||||||
@@ -93,7 +93,6 @@ function comicinfoReducer(state = initialState, action) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
console.log(updatedState);
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
issuesForVolume: updatedState,
|
issuesForVolume: updatedState,
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import {
|
|||||||
IMS_RAW_IMPORT_SUCCESSFUL,
|
IMS_RAW_IMPORT_SUCCESSFUL,
|
||||||
IMS_RAW_IMPORT_FAILED,
|
IMS_RAW_IMPORT_FAILED,
|
||||||
IMS_RECENT_COMICS_FETCHED,
|
IMS_RECENT_COMICS_FETCHED,
|
||||||
IMS_DATA_FETCH_ERROR,
|
|
||||||
IMS_CV_METADATA_IMPORT_SUCCESSFUL,
|
IMS_CV_METADATA_IMPORT_SUCCESSFUL,
|
||||||
IMS_CV_METADATA_IMPORT_FAILED,
|
IMS_CV_METADATA_IMPORT_FAILED,
|
||||||
IMS_CV_METADATA_IMPORT_CALL_IN_PROGRESS,
|
IMS_CV_METADATA_IMPORT_CALL_IN_PROGRESS,
|
||||||
@@ -19,6 +18,7 @@ import {
|
|||||||
LS_COMIC_ADDED,
|
LS_COMIC_ADDED,
|
||||||
IMG_ANALYSIS_CALL_IN_PROGRESS,
|
IMG_ANALYSIS_CALL_IN_PROGRESS,
|
||||||
IMG_ANALYSIS_DATA_FETCH_SUCCESS,
|
IMG_ANALYSIS_DATA_FETCH_SUCCESS,
|
||||||
|
FILEOPS_STATE_RESET,
|
||||||
} from "../constants/action-types";
|
} from "../constants/action-types";
|
||||||
const initialState = {
|
const initialState = {
|
||||||
IMSCallInProgress: false,
|
IMSCallInProgress: false,
|
||||||
@@ -151,6 +151,13 @@ function fileOpsReducer(state = initialState, action) {
|
|||||||
imageAnalysisResults: action.result,
|
imageAnalysisResults: action.result,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case FILEOPS_STATE_RESET: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
imageAnalysisResults: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5805,10 +5805,10 @@ filehound@^1.17.5:
|
|||||||
moment "^2.29.1"
|
moment "^2.29.1"
|
||||||
unit-compare "^1.0.1"
|
unit-compare "^1.0.1"
|
||||||
|
|
||||||
filename-parser@^1.0.1:
|
filename-parser@^1.0.2:
|
||||||
version "1.0.1"
|
version "1.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/filename-parser/-/filename-parser-1.0.1.tgz#cd54d9131913dd2bf96c2a1d19edf7c1ee75c4cc"
|
resolved "https://registry.yarnpkg.com/filename-parser/-/filename-parser-1.0.4.tgz#dfa2ae09040997e4e4d2063b735edae6e21bfbaa"
|
||||||
integrity sha512-MMzuklXc1r4N6uQXg8CQYxoiTX7w6QPeJE73L5lCTSoNR7CftCXHIA6tyINomkvWIUanrlqTB629DyTIqCucEA==
|
integrity sha512-r/cFGGlCFaz2taSlDzOtVWO66WvqPDBv6CtyqKw4GCP7+V3r5D5J0ci3fnYaDm5/GkqWL5aGA6JTbu8e+oKQMA==
|
||||||
dependencies:
|
dependencies:
|
||||||
compromise "^13.11.4"
|
compromise "^13.11.4"
|
||||||
compromise-dates "^2.2.1"
|
compromise-dates "^2.2.1"
|
||||||
|
|||||||
Reference in New Issue
Block a user