Compare commits

..

1 Commits

Author SHA1 Message Date
33f67e99f8 🔧 Fixed the path to funding.yml 2023-01-28 09:31:07 -08:00
43 changed files with 8815 additions and 3498 deletions

17
.babelrc Normal file
View File

@@ -0,0 +1,17 @@
{
"presets": [
"@babel/preset-env",
"@babel/preset-react",
"@babel/preset-typescript"
],
"plugins": [
"@babel/transform-runtime",
"@babel/plugin-proposal-class-properties",
"@babel/plugin-syntax-top-level-await"
],
"env": {
"production": {
"presets": ["minify"]
}
}
}

View File

@@ -1,14 +1,14 @@
module.exports = {
extends: ["plugin:react/recommended", "plugin:@typescript-eslint/recommended", "plugin:prettier/recommended", "plugin:css-modules/recommended", "plugin:storybook/recommended", "plugin:storybook/recommended", "plugin:storybook/recommended", "plugin:storybook/recommended"],
extends: ["plugin:react/recommended", "plugin:@typescript-eslint/recommended", "plugin:prettier/recommended", "plugin:css-modules/recommended", "plugin:storybook/recommended", "plugin:storybook/recommended", "plugin:storybook/recommended"],
parser: "@typescript-eslint/parser",
parserOptions: {
sourceType: "module",
ecmaVersion: 2020,
ecmaFeatures: {
jsx: true // Allows for the parsing of JSX
}
},
plugins: ["@typescript-eslint", "css-modules"],
settings: {
"import/resolver": {
@@ -18,9 +18,9 @@ module.exports = {
},
react: {
version: "detect" // Tells eslint-plugin-react to automatically detect the version of React to use
}
},
// Fine tune rules
rules: {
"@typescript-eslint/no-var-requires": 0

View File

@@ -12,7 +12,7 @@ jobs:
steps:
- uses: actions/checkout@master
- name: Publish to Registry
uses: elgohr/Publish-Docker-Github-Action@v5
uses: elgohr/Publish-Docker-Github-Action@master
with:
name: frishi/threetwo
username: ${{ secrets.DOCKER_USERNAME }}

19
.storybook/main.js Normal file
View File

@@ -0,0 +1,19 @@
module.exports = {
stories: [
"../src/client/stories/*.stories.mdx",
"../src/client/stories/*.stories.@(js|jsx|ts|tsx)",
],
staticDirs: [
"../src/client/stories/assets"
],
addons: [
"@storybook/addon-links",
"@storybook/preset-scss",
"@storybook/addon-essentials",
"@storybook/addon-interactions",
],
framework: "@storybook/react",
core: {
builder: "webpack5",
},
};

View File

@@ -1,17 +0,0 @@
import type { StorybookConfig } from "@storybook/react-vite";
const config: StorybookConfig = {
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
addons: [
"@storybook/addon-links",
"@storybook/addon-essentials",
"@storybook/addon-interactions",
],
framework: {
name: "@storybook/react-vite",
options: {},
},
docs: {
autodocs: "tag",
},
};
export default config;

View File

@@ -1,3 +0,0 @@
<script>
window.global = window;
</script>

9
.storybook/preview.js Normal file
View File

@@ -0,0 +1,9 @@
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
}

View File

@@ -1,18 +0,0 @@
import type { Preview } from "@storybook/react";
const preview: Preview = {
parameters: {
backgrounds: {
default: "light",
},
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
},
};
export default preview;

BIN
ComicVine Matching.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

BIN
DC++ integration.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1013 KiB

BIN
Dashboard.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 MiB

View File

@@ -1,4 +1,4 @@
FROM node:18.15.0-alpine
FROM node:17.3-alpine
LABEL maintainer="Rishi Ghan <rishi.ghan@gmail.com>"
WORKDIR /threetwo
@@ -9,7 +9,7 @@ COPY nodemon.json ./
COPY jsdoc.json ./
# RUN apt-get update && apt-get install -y git python3 build-essential autoconf automake g++ libpng-dev make
RUN apk --no-cache add g++ make libpng-dev git python3 libc6-compat autoconf automake libjpeg-turbo-dev libpng-dev mesa-dev mesa libxi build-base gcc libtool nasm
RUN apk --no-cache add g++ make libpng-dev python3 git libc6-compat autoconf automake bash libjpeg-turbo-dev libpng-dev mesa-dev mesa libxi build-base gcc libtool nasm
RUN yarn --ignore-engines

BIN
Library.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@@ -6,13 +6,13 @@ ThreeTwo! _aims to be_ a comic book curation app.
### Screenshots
![](https://github.com/rishighan/threetwo-visual-updates/raw/67e56878eb0381c73c1dea746a45253d3dcaa184/update_december_2022/Dashboard.png)
![](https://raw.githubusercontent.com/rishighan/threetwo/rishighan-screenshots-dec-2022/Dashboard.png)
![](https://github.com/rishighan/threetwo-visual-updates/raw/67e56878eb0381c73c1dea746a45253d3dcaa184/update_december_2022/Library.png)
![](https://raw.githubusercontent.com/rishighan/threetwo/rishighan-screenshots-dec-2022/Library.png)
![](https://github.com/rishighan/threetwo-visual-updates/raw/67e56878eb0381c73c1dea746a45253d3dcaa184/update_december_2022/DC%2B%2B%20integration.png)
![](https://raw.githubusercontent.com/rishighan/threetwo/rishighan-screenshots-dec-2022/DC%2B%2B%20integration.png)
![](https://github.com/rishighan/threetwo-visual-updates/raw/67e56878eb0381c73c1dea746a45253d3dcaa184/update_december_2022/ComicVine%20Matching.png)
![](https://raw.githubusercontent.com/rishighan/threetwo/rishighan-screenshots-dec-2022/ComicVine%20Matching.png)
### 🦄 Early Development Support Channel
@@ -40,8 +40,9 @@ For debugging and troubleshooting, you can run this app locally using these step
1. Clone this repo using `git clone https://github.com/rishighan/threetwo.git`
2. `yarn run dev` (you can ignore the warnings)
3. This will open `http://localhost:5173` in your default browser
4. Note that this is simply the UI layer and won't offer anything beyond a scaffold. You have to spin up the microservices locally to get it to work.
3. This will open `http://localhost:3050` in your default browser
4. For testing `OPDS` functionality, create a folder called `comics` under `/src/server` and put some comics in there. The `OPDS` feed is accessed to `http://localhost:8050/api/opds`
5. Note that this is simply the UI layer and won't offer anything beyond a scaffold. You have to spin up the microservices locally to get it to work.
## Troubleshooting

View File

@@ -5,12 +5,15 @@
"main": "server/index.js",
"typings": "server/index.js",
"scripts": {
"build": "vite build",
"dev": "rimraf dist && npm run build && vite",
"start": "npm run build && vite",
"build": "webpack --mode production",
"start": "npm run build && npm run server",
"client": "webpack serve --mode development --devtool inline-source-map --hot",
"server": "tsc -p tsconfig.server.json && node server/",
"dev": "concurrently \"nodemon\" \"npm run client\"",
"server-dev": "nodemon",
"docs": "jsdoc -c jsdoc.json",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook"
},
"author": "Rishi Ghan",
"license": "MIT",
@@ -20,68 +23,75 @@
"@dnd-kit/core": "^4.0.0",
"@dnd-kit/sortable": "^5.0.0",
"@dnd-kit/utilities": "^3.2.0",
"@fortawesome/fontawesome-free": "^6.3.0",
"@fortawesome/fontawesome-free": "^6.1.1",
"@parcel/config-default": "^2.6.0",
"@redux-devtools/extension": "^3.2.2",
"@rollup/plugin-node-resolve": "^15.0.1",
"@tanstack/react-table": "^8.5.11",
"@types/axios": "^0.14.0",
"@types/mime-types": "^2.1.0",
"@types/react-router-dom": "^5.3.3",
"@types/react": "^17.0.3",
"@types/react-dom": "^17.0.2",
"@types/react-redux": "^7.1.16",
"@types/react-router-dom": "^5.1.7",
"@types/sharp": "^0.28.0",
"@types/socket.io": "^3.0.2",
"@types/socket.io-client": "^3.0.0",
"@vitejs/plugin-react": "^3.1.0",
"airdcpp-apisocket": "2.4.5-beta.1",
"axios": "^1.3.4",
"axios-cache-interceptor": "^1.0.1",
"airdcpp-apisocket": "^2.4.4",
"array-sort-by": "^1.2.1",
"axios": "^0.27.2",
"axios-rate-limit": "^1.3.0",
"axios-simple-cache-adapter": "^1.1.0",
"babel-polyfill": "^6.26.0",
"babel-preset-minify": "^0.5.2",
"better-docs": "^2.7.2",
"comlink-loader": "^2.0.0",
"date-fns": "^2.28.0",
"dayjs": "^1.10.6",
"ellipsize": "^0.5.1",
"ellipsize": "^0.1.0",
"express": "^4.17.1",
"fastest-validator": "^1.11.0",
"filename-parser": "^1.0.2",
"final-form": "^4.20.2",
"final-form-arrays": "^3.0.2",
"history": "^5.3.0",
"html-to-text": "^8.1.0",
"jsdoc": "^3.6.10",
"lodash": "^4.17.21",
"node-sass": "npm:sass",
"opds-extra": "^3.0.9",
"pretty-bytes": "^5.6.0",
"prop-types": "^15.8.1",
"qs": "^6.10.5",
"react": "^18.2.0",
"react-collapsible": "^2.9.0",
"react-comic-viewer": "^0.4.0",
"react-day-picker": "^8.6.0",
"react-day-picker": "^8.0.6",
"react-dom": "^18.1.0",
"react-fast-compare": "^3.2.0",
"react-final-form": "^6.5.9",
"react-final-form-arrays": "^3.1.4",
"react-final-form-arrays": "^3.1.3",
"react-loader-spinner": "^4.0.0",
"react-masonry-css": "^1.0.16",
"react-modal": "^3.15.1",
"react-redux": "^8.0.5",
"react-router": "^6.9.0",
"react-router-dom": "^6.9.0",
"react-redux": "^7.2.6",
"react-router": "^6.2.2",
"react-router-dom": "^6.2.2",
"react-select": "^5.3.2",
"react-select-async-paginate": "^0.7.2",
"react-select-async-paginate": "^0.6.1",
"react-slick": "^0.29.0",
"react-sliding-pane": "^7.1.0",
"react-stickynode": "^4.1.0",
"react-stickynode": "^4.0.0",
"react-textarea-autosize": "^8.3.4",
"reapop": "^4.2.1",
"redux-first-history": "^5.1.1",
"reapop": "^4.0.5",
"redux-first-history": "^5.0.9",
"redux-socket.io-middleware": "^1.0.4",
"redux-thunk": "^2.4.2",
"redux-thunk": "^2.4.1",
"sharp": "^0.30.5",
"slick-carousel": "^1.8.1",
"socket.io-client": "^4.3.2",
"styled-components": "^5.3.9",
"styled-components": "^5.3.5",
"threetwo-ui-typings": "^1.0.14",
"vite-plugin-html": "^3.2.0",
"voca": "^1.4.0",
"websocket": "^1.0.34",
"ws": "^7.5.3",
"xml2js": "^0.4.23",
"xregexp": "^5.0.2"
},
"devDependencies": {
@@ -89,30 +99,44 @@
"@babel/core": "^7.13.10",
"@babel/plugin-syntax-top-level-await": "^7.14.5",
"@babel/plugin-transform-runtime": "^7.13.15",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"@babel/preset-env": "^7.13.10",
"@babel/preset-react": "^7.12.13",
"@babel/preset-typescript": "^7.13.0",
"@storybook/addon-essentials": "^7.0.0-rc.3",
"@storybook/addon-interactions": "^7.0.0-rc.3",
"@storybook/addon-links": "^7.0.0-rc.3",
"@storybook/blocks": "^7.0.0-rc.3",
"@storybook/react": "^7.0.0-rc.3",
"@storybook/react-vite": "^7.0.0-rc.3",
"@storybook/testing-library": "^0.0.14-next.1",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.7",
"@storybook/addon-actions": "^6.5.9",
"@storybook/addon-essentials": "^6.5.9",
"@storybook/addon-interactions": "^6.5.9",
"@storybook/addon-links": "^6.5.9",
"@storybook/builder-webpack4": "^6.5.9",
"@storybook/builder-webpack5": "^6.5.9",
"@storybook/manager-webpack4": "^6.5.9",
"@storybook/manager-webpack5": "^6.5.9",
"@storybook/preset-scss": "^1.0.3",
"@storybook/react": "^6.5.9",
"@storybook/testing-library": "^0.0.13",
"@teamsupercell/typings-for-css-modules-loader": "^2.5.1",
"@tsconfig/node14": "^1.0.0",
"@types/ellipsize": "^0.1.1",
"@types/express": "^4.17.8",
"@types/jest": "^26.0.20",
"@types/lodash": "^4.14.168",
"@types/node": "^14.14.34",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@types/react-redux": "^7.1.25",
"@types/react": "^17.0.3",
"@types/react-dom": "^17.0.2",
"@types/react-redux": "^7.1.16",
"@typescript-eslint/eslint-plugin": "^4.17.0",
"@typescript-eslint/parser": "^4.17.0",
"awesome-typescript-loader": "^5.2.1",
"babel-eslint": "^10.0.0",
"babel-loader": "^8.2.2",
"babel-plugin-transform-class-properties": "^6.24.1",
"body-parser": "^1.19.0",
"bulma": "^0.9.4",
"buffer": "^6.0.3",
"bulma": "^0.9.3",
"clean-webpack-plugin": "^1.0.0",
"comlink": "^4.3.0",
"concurrently": "^4.0.0",
"copy-webpack-plugin": "^9.0.1",
"css-loader": "^5.1.2",
"eslint": "^7.22.0",
"eslint-config-airbnb": "^18.2.1",
"eslint-config-airbnb-base": "^14.2.1",
@@ -122,19 +146,35 @@
"eslint-plugin-jsx-a11y": "^6.0.3",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-react": "^7.22.0",
"eslint-plugin-storybook": "^0.6.11",
"express": "^4.17.1",
"file-loader": "^6.2.0",
"fs-extra": "^9.1.0",
"html-webpack-plugin": "^5.3.1",
"http-response-stream": "^1.0.9",
"image-webpack-loader": "^8.1.0",
"install": "^0.13.0",
"jest": "^26.6.3",
"mini-css-extract-plugin": "^1.4.1",
"node-sass": "^7.0.1",
"nodemon": "^1.17.3",
"npm": "^8.11.0",
"prettier": "^2.2.1",
"react-refresh": "^0.14.0",
"rimraf": "^4.1.3",
"sass": "^1.58.1",
"storybook": "^7.0.0-rc.3",
"rimraf": "^3.0.2",
"sass-loader": "^11.0.1",
"source-map-loader": "^0.2.4",
"string-similarity": "^4.0.4",
"style-loader": "^2.0.0",
"tslint": "^6.1.3",
"typescript": "^5.0.2",
"vite": "^4.2.0"
"typescript": "^4.2.3",
"url-loader": "^1.0.1",
"webpack": "^5.73.0",
"webpack-bundle-analyzer": "^4.4.2",
"webpack-cli": "^4.6.0",
"webpack-dev-server": "^4.7.4",
"webpack-merge": "^5.7.3"
},
"resolutions": {
"@storybook/react/webpack": "^5"
}
}

View File

@@ -10,7 +10,6 @@
<body>
<div id="root"></div>
<script type="module" src="/src/client/index.tsx"></script>
</body>
</html>

View File

@@ -1,6 +1,9 @@
import axios from "axios";
import rateLimiter from "axios-rate-limit";
import { setupCache } from "axios-cache-interceptor";
import {
AxiosCacheRequestConfig,
createCacheAdapter,
} from "axios-simple-cache-adapter";
import qs from "qs";
import {
CV_SEARCH_SUCCESS,
@@ -23,22 +26,25 @@ import {
LIBRARY_SERVICE_BASE_URI,
} from "../constants/endpoints";
const axiosCacheAdapter = createCacheAdapter();
const http = rateLimiter(axios.create(), {
maxRequests: 1,
perMilliseconds: 1000,
maxRPS: 1,
});
const cachedAxios = setupCache(axios);
export const getWeeklyPullList = (options) => async (dispatch) => {
try {
dispatch({
type: CV_WEEKLY_PULLLIST_CALL_IN_PROGRESS,
});
await cachedAxios(`${COMICVINE_SERVICE_URI}/getWeeklyPullList`, {
await axios(`${COMICVINE_SERVICE_URI}/getWeeklyPullList`, {
method: "get",
params: options,
}).then((response) => {
axiosCacheAdapter,
cache: 1000, // value in MS
} as AxiosCacheRequestConfig).then((response) => {
dispatch({
type: CV_WEEKLY_PULLLIST_FETCHED,
data: response.data.result,
@@ -64,6 +70,9 @@ export const comicinfoAPICall = (options) => async (dispatch) => {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
},
paramsSerializer: (params) => {
return qs.stringify(params, { arrayFormat: "repeat" });
},
});
switch (options.callURIAction) {

View File

@@ -127,50 +127,49 @@ export const getComicBooks = (options) => async (dispatch) => {
* @returns Nothing.
* @param payload
*/
export const importToDB =
(sourceName: string, metadata?: any) => (dispatch) => {
try {
const comicBookMetadata = {
importType: "new",
payload: {
rawFileDetails: {
name: "",
},
importStatus: {
isImported: true,
tagged: false,
matchedResult: {
score: "0",
},
},
sourcedMetadata: metadata || null,
acquisition: { source: { wanted: true, name: sourceName } },
export const importToDB = (sourceName: string, metadata?: any) => (dispatch) => {
try {
const comicBookMetadata = {
importType: "new",
payload: {
rawFileDetails: {
name: "",
},
};
dispatch({
type: IMS_CV_METADATA_IMPORT_CALL_IN_PROGRESS,
});
return axios
.request({
url: `${LIBRARY_SERVICE_BASE_URI}/rawImportToDb`,
method: "POST",
data: comicBookMetadata,
// transformResponse: (r: string) => JSON.parse(r),
})
.then((response) => {
const { data } = response;
dispatch({
type: IMS_CV_METADATA_IMPORT_SUCCESSFUL,
importResult: data,
});
importStatus: {
isImported: true,
tagged: false,
matchedResult: {
score: "0",
},
},
sourcedMetadata: metadata || null,
acquisition: { source: { wanted: true, name: sourceName } },
}
};
dispatch({
type: IMS_CV_METADATA_IMPORT_CALL_IN_PROGRESS,
});
return axios
.request({
url: `${LIBRARY_SERVICE_BASE_URI}/rawImportToDb`,
method: "POST",
data: comicBookMetadata,
// transformResponse: (r: string) => JSON.parse(r),
})
.then((response) => {
const { data } = response;
dispatch({
type: IMS_CV_METADATA_IMPORT_SUCCESSFUL,
importResult: data,
});
} catch (error) {
dispatch({
type: IMS_CV_METADATA_IMPORT_FAILED,
importError: error,
});
}
};
} catch (error) {
dispatch({
type: IMS_CV_METADATA_IMPORT_FAILED,
importError: error,
});
}
};
export const fetchVolumeGroups = () => async (dispatch) => {
try {
@@ -255,23 +254,24 @@ export const fetchComicVineMatches =
* @returns {any}
*/
export const extractComicArchive =
(path: string, options: any): any =>
async (dispatch) => {
dispatch({
type: IMS_COMIC_BOOK_ARCHIVE_EXTRACTION_CALL_IN_PROGRESS,
});
await axios({
method: "POST",
url: `${LIBRARY_SERVICE_BASE_URI}/uncompressFullArchive`,
headers: {
"Content-Type": "application/json; charset=utf-8",
},
data: {
filePath: path,
options,
},
});
};
async (dispatch) => {
dispatch({
type: IMS_COMIC_BOOK_ARCHIVE_EXTRACTION_CALL_IN_PROGRESS,
});
await axios({
method: "POST",
url: `${LIBRARY_SERVICE_BASE_URI}/uncompressFullArchive`,
headers: {
"Content-Type": "application/json; charset=utf-8",
},
data: {
filePath: path,
options,
},
});
};
/**
* Description
@@ -301,26 +301,26 @@ export const searchIssue = (query, options) => async (dispatch) => {
case "wantedComicsPage":
dispatch({
type: WANTED_COMICS_FETCHED,
data: response.data.hits,
data: response.data.body,
});
break;
case "globalSearchBar":
dispatch({
type: SS_SEARCH_RESULTS_FETCHED_SPECIAL,
data: response.data.hits,
data: response.data.body,
});
break;
case "libraryPage":
dispatch({
type: SS_SEARCH_RESULTS_FETCHED,
data: response.data.hits,
data: response.data.body,
});
break;
case "volumesPage":
dispatch({
type: VOLUMES_FETCHED,
data: response.data.hits,
data: response.data.body,
});
break;
@@ -349,4 +349,4 @@ export const analyzeImage =
type: IMG_ANALYSIS_DATA_FETCH_SUCCESS,
result: foo.data,
});
};
};

View File

@@ -1,8 +1,8 @@
@import "/node_modules/bulma/bulma.sass";
$fa-font-path: "/node_modules/@fortawesome/fontawesome-free/webfonts";
@import "/node_modules/@fortawesome/fontawesome-free/scss/fontawesome.scss";
@import "/node_modules/@fortawesome/fontawesome-free/scss/regular.scss";
@import "/node_modules/@fortawesome/fontawesome-free/scss/solid.scss";
@import "../../../../node_modules/bulma/bulma.sass";
$fa-font-path: "~@fortawesome/fontawesome-free/webfonts";
@import "../../../../node_modules/@fortawesome/fontawesome-free/scss/fontawesome.scss";
@import "../../../../node_modules/@fortawesome/fontawesome-free/scss/regular.scss";
@import "../../../../node_modules/@fortawesome/fontawesome-free/scss/solid.scss";
$bg-color: yellow;
$border-color: red;

View File

@@ -167,7 +167,7 @@ export const AcquisitionPanel = (
}
>
<span className="icon is-small">
<img src="/src/client/assets/img/airdcpp_logo.svg" />
<img src="/img/airdcpp_logo.svg" />
</span>
<span className="airdcpp-text">Search on AirDC++</span>
</button>

View File

@@ -1,5 +1,7 @@
import React, { useEffect, useContext, ReactElement } from "react";
import { getBundlesForComic } from "../../actions/airdcpp.actions";
import {
getBundlesForComic,
} from "../../actions/airdcpp.actions";
import { useDispatch, useSelector } from "react-redux";
import { RootState } from "threetwo-ui-typings";
import { isEmpty, isNil, map } from "lodash";

View File

@@ -1,7 +1,7 @@
import React, { ReactElement } from "react";
import PropTypes from "prop-types";
import prettyBytes from "pretty-bytes";
import { isEmpty } from "lodash";
import { isUndefined } from "lodash";
export const RawFileDetails = (props): ReactElement => {
const { rawFileDetails, inferredMetadata } = props.data;
@@ -34,14 +34,6 @@ export const RawFileDetails = (props): ReactElement => {
</span>
</div>
</div>
<div className="control">
<div className="tags has-addons">
<span className="tag">MIME type</span>
<span className="tag is-primary is-light">
{rawFileDetails.mimeType}
</span>
</div>
</div>
</div>
</dd>
</dl>
@@ -61,7 +53,7 @@ export const RawFileDetails = (props): ReactElement => {
</span>
</div>
</div>
{!isEmpty(inferredMetadata.issue.number) ? (
{!isUndefined(inferredMetadata.issue.number) ? (
<div className="control">
<div className="tags has-addons">
<span className="tag">Number</span>
@@ -89,7 +81,6 @@ RawFileDetails.propTypes = {
fileSize: PropTypes.number,
path: PropTypes.string,
extension: PropTypes.string,
mimeType: PropTypes.string,
cover: PropTypes.shape({
filePath: PropTypes.string,
}),

View File

@@ -20,7 +20,7 @@ export const PullList = ({ issues }: PullListProps): ReactElement => {
useEffect(() => {
dispatch(
getWeeklyPullList({
startDate: "2023-5-25",
startDate: "2022-12-25",
pageSize: "15",
currentPage: "1",
}),

View File

@@ -83,14 +83,14 @@ export const RecentlyImported = ({
{/* Raw file presence */}
{isNil(rawFileDetails) && (
<span className="icon custom-icon is-small has-text-danger mr-2">
<img src="/src/client/assets/img/missing-file.svg" />
<img src="/img/missing-file.svg" />
</span>
)}
{/* ComicInfo.xml presence */}
{!isNil(comicInfo) && !isEmpty(comicInfo) && (
<span className="icon custom-icon is-small has-text-danger">
<img
src="/src/client/assets/img/comicinfoxml.svg"
src="/img/comicinfoxml.svg"
alt={"ComicInfo.xml file detected."}
/>
</span>
@@ -99,8 +99,8 @@ export const RecentlyImported = ({
{isComicBookMetadataAvailable && (
<span className="icon custom-icon">
<img
src="/src/client/assets/img/cvlogo.svg"
alt={"ComicVine metadata detected."}
src="/img/cvlogo.svg"
alt={"ComicInfo.xml file detected."}
/>
</span>
)}
@@ -125,7 +125,7 @@ export const RecentlyImported = ({
<dd className="is-size-9">
<dl>
<span className="icon custom-icon">
<img src={`/src/client/assets/img/${icon}`} />
<img src={`/img/${icon}`} />
</span>
</dl>
<dl>

View File

@@ -82,12 +82,12 @@ export const WantedComicsList = ({
{/* comicVine metadata presence */}
{isComicBookMetadataAvailable && (
<span className="icon custom-icon">
<img src="/src/client/assets/img/cvlogo.svg" />
<img src="/img/cvlogo.svg" />
</span>
)}
{!isEmpty(locg) && (
<span className="icon custom-icon">
<img src="/src/client/assets/img/locglogo.svg" />
<img src="/img/locglogo.svg" />
</span>
)}
{/* Issue type */}

View File

@@ -3,6 +3,7 @@ import { getTransfers } from "../../actions/airdcpp.actions";
import { useDispatch, useSelector } from "react-redux";
import { AirDCPPSocketContext } from "../../context/AirDCPPSocket";
import { isEmpty, isNil, isUndefined } from "lodash";
import { searchIssue } from "../../actions/fileops.actions";
import { determineCoverFile } from "../../shared/utils/metadata.utils";
import MetadataPanel from "../shared/MetadataPanel";
@@ -46,6 +47,7 @@ export const Downloads = (props: IDownloadsProps): ReactElement => {
})
setBundles(foo);
}
}, [issueBundles])
return !isNil(bundles) ?
@@ -54,9 +56,9 @@ export const Downloads = (props: IDownloadsProps): ReactElement => {
<h1 className="title">Downloads</h1>
<div className="columns">
<div className="column is-half">
{bundles.map((bundle, idx) => {
{bundles.map(bundle => {
console.log(bundle);
return <div key={idx}>
return <>
<MetadataPanel
data={bundle}
imageStyle={{ maxWidth: 80 }}
@@ -68,33 +70,30 @@ export const Downloads = (props: IDownloadsProps): ReactElement => {
margin: "0 0 8px 0",
}} />
<table className="table is-size-7">
<thead>
<table className="table is-size-7">
<tr>
<th>Name</th>
<th>Size</th>
<th>Type</th>
<th>Bundle ID</th>
</tr>
</thead>
<tbody>
{bundle.acquisition.directconnect.downloads.map((bundle, idx) => {
return (<tr key={idx}>
{bundle.acquisition.directconnect.downloads.map((bundle) => {
return(<tr>
<td>{bundle.name}</td>
<td>{bundle.size}</td>
<td>{bundle.type.str}</td>
<td><span className="tag is-warning">{bundle.bundleId}</span></td>
</tr>)
})}
</tbody>
</table>
</table>
{/* <pre>{JSON.stringify(bundle.acquisition.directconnect.downloads, null, 2)}</pre> */}
</div>
</>
})}
</div>
</div>
</section>
</div> : <div>There are no downloads.</div>;
</div> : <div>asd</div>;
};
export default Downloads;

View File

@@ -31,7 +31,7 @@ export const SearchBar = (data: ISearchBarProps): ReactElement => {
from: 0,
},
type: "volumeName",
trigger: "globalSearchBar",
trigger: "globalSearchBar"
},
),
);
@@ -51,6 +51,7 @@ export const SearchBar = (data: ISearchBarProps): ReactElement => {
<i className="fa-solid fa-magnifying-glass"></i>
</span>
</div>
{!isEmpty(searchResults) ? (
<div
className="columns box is-multiline"
@@ -61,10 +62,9 @@ export const SearchBar = (data: ISearchBarProps): ReactElement => {
margin: "60px 0 0 350px",
}}
>
{map(searchResults, (result, idx) => (
{map(searchResults, (result) => (
<MetadataPanel
data={result}
key={idx}
imageStyle={{ maxWidth: 70 }}
titleStyle={{ fontSize: "0.8rem" }}
tagsStyle={{ fontSize: "0.7rem" }}

View File

@@ -19,7 +19,10 @@ export const Library = (): ReactElement => {
const searchResults = useSelector(
(state: RootState) => state.fileOps.libraryComics,
);
const searchError = useSelector((state: RootState) => state.fileOps.librarySearchError);
const searchError = useSelector((state: RootState) => {
console.log(state);
return state.fileOps.librarySearchError;
});
const dispatch = useDispatch();
useEffect(() => {
dispatch(
@@ -218,9 +221,9 @@ export const Library = (): ReactElement => {
<div>
<div className="library">
<T2Table
totalPages={searchResults.total.value}
totalPages={searchResults.hits.total.value}
columns={columns}
sourceData={searchResults?.hits}
sourceData={searchResults?.hits?.hits}
rowClickHandler={navigateToComicDetail}
paginationHandlers={{
nextPage,

View File

@@ -73,7 +73,7 @@ export const LibraryGrid = (libraryGridProps: ILibraryGridProps) => {
<div className="content is-flex is-flex-direction-row">
{!isEmpty(sourcedMetadata.comicvine) && (
<span className="icon cv-icon is-small">
<img src="/src/client/assets/img/cvlogo.svg" />
<img src="/dist/img/cvlogo.svg" />
</span>
)}
{isNil(rawFileDetails) && (

View File

@@ -25,7 +25,7 @@ const Navbar: React.FunctionComponent = (props) => {
<div className="navbar-brand">
<Link to="/" className="navbar-item">
<img
src="/src/client/assets/img/threetwo.svg"
src="/img/threetwo.svg"
alt="ThreeTwo! A comic book curator"
width="112"
height="28"
@@ -85,7 +85,7 @@ const Navbar: React.FunctionComponent = (props) => {
{downloadProgressTick && <div className="pulsating-circle"></div>}
</a>
{!isUndefined(downloadProgressTick) ? (
<div className="navbar-dropdown is-right is-boxed">
<div className="navbar-dropdown is-right">
<a className="navbar-item">
<DownloadProgressTick data={downloadProgressTick} />
</a> </div>
@@ -98,7 +98,7 @@ const Navbar: React.FunctionComponent = (props) => {
<a className="navbar-link is-arrowless has-text-success">
<i className="fa-solid fa-bolt"></i>
</a>
<div className="navbar-dropdown pr-2 pl-2 is-right airdcpp-status is-boxed">
<div className="navbar-dropdown pt-4 pr-2 pl-2 is-right airdcpp-status">
{/* AirDC++ Session Information */}
<p>
@@ -133,7 +133,7 @@ const Navbar: React.FunctionComponent = (props) => {
<a className="navbar-link is-arrowless has-text-danger">
<i className="fa-solid fa-bolt"></i>
</a>
<div className="navbar-dropdown pr-2 pl-2 is-right is-boxed">
<div className="navbar-dropdown pr-2 pl-2 is-right">
<pre>
{JSON.stringify(socketDisconnectionReason, null, 2)}
</pre>

View File

@@ -166,8 +166,8 @@ export const Volumes = (props): ReactElement => {
<div>
<div className="library">
<T2Table
sourceData={volumes?.hits}
totalPages={volumes.hits.length}
sourceData={volumes?.hits?.hits}
totalPages={volumes.hits.hits.length}
paginationHandlers={{
nextPage: () => {},
previousPage: () => {},

View File

@@ -73,8 +73,8 @@ export const WantedComics = (props): ReactElement => {
id: "downloadDetails",
accessorKey: "acquisition",
cell: data => <ol>
{data.getValue().directconnect.downloads.map((download, idx) => {
return <li className="is-size-7" key={idx}>{download.name}</li>;
{data.getValue().directconnect.downloads.map(download => {
return <li className="is-size-7">{download.name}</li>;
})}
</ol>
},

View File

@@ -57,12 +57,6 @@ export const MetadataPanel = (props: IMetadatPanelProps): ReactElement => {
>
{rawFileDetails.extension}
</span>
<span
className="tag is-success is-light has-text-weight-semibold"
style={props.tagsStyle}
>
{rawFileDetails.mimeType}
</span>
<span
className="tag is-success is-light"
style={props.tagsStyle}

View File

@@ -20,6 +20,8 @@ export const T2Table = (tableOptions): ReactElement => {
pageIndex: 1,
pageSize: 15,
});
console.log(sourceData)
const pagination = useMemo(
() => ({
@@ -79,12 +81,12 @@ export const T2Table = (tableOptions): ReactElement => {
{/* <p>{totalPages} comics in all</p> */}
</div>
<div className="field has-addons">
<div className="control">
<p className="control">
<div className="button" onClick={() => goToPreviousPage()}> <i className="fas fa-chevron-left"></i></div>
</div>
<div className="control">
</p>
<p className="control">
<div className="button" onClick={() => goToNextPage()}> <i className="fas fa-chevron-right"></i> </div>
</div>
</p>
<div className="field has-addons ml-5">
<p className="control">

View File

@@ -8,72 +8,71 @@ export const hostURIBuilder = (options: Record<string, string>): string => {
options.apiPath
);
};
console.log(import.meta);
export const CORS_PROXY_SERVER_URI = hostURIBuilder({
protocol: "http",
host: import.meta.env.UNDERLYING_HOSTNAME || "localhost",
host: process.env.UNDERLYING_HOSTNAME || "localhost",
port: "8050",
apiPath: "/",
});
export const COMICVINE_SERVICE_URI = hostURIBuilder({
protocol: "http",
host: import.meta.env.UNDERLYING_HOSTNAME || "localhost",
host: process.env.UNDERLYING_HOSTNAME || "localhost",
port: "3080",
apiPath: "/api/comicvine",
});
export const METRON_SERVICE_URI = hostURIBuilder({
protocol: "http",
host: import.meta.env.UNDERLYING_HOSTNAME || "localhost",
host: process.env.UNDERLYING_HOSTNAME || "localhost",
port: "3080",
apiPath: "/api/metron",
});
export const API_BASE_URI = hostURIBuilder({
protocol: "http",
host: import.meta.env.UNDERLYING_HOSTNAME || "localhost",
host: process.env.UNDERLYING_HOSTNAME || "localhost",
port: "8050",
apiPath: "/api",
});
export const LIBRARY_SERVICE_HOST = hostURIBuilder({
protocol: "http",
host: import.meta.env.UNDERLYING_HOSTNAME || "localhost",
host: process.env.UNDERLYING_HOSTNAME || "localhost",
port: "3000",
apiPath: ``,
});
export const LIBRARY_SERVICE_BASE_URI = hostURIBuilder({
protocol: "http",
host: import.meta.env.UNDERLYING_HOSTNAME || "localhost",
host: process.env.UNDERLYING_HOSTNAME || "localhost",
port: "3000",
apiPath: "/api/library",
});
export const SEARCH_SERVICE_BASE_URI = hostURIBuilder({
protocol: "http",
host: import.meta.env.UNDERLYING_HOSTNAME || "localhost",
host: process.env.UNDERLYING_HOSTNAME || "localhost",
port: "3000",
apiPath: "/api/search",
});
export const SETTINGS_SERVICE_BASE_URI = hostURIBuilder({
protocol: "http",
host: import.meta.env.UNDERLYING_HOSTNAME || "localhost",
host: process.env.UNDERLYING_HOSTNAME || "localhost",
port: "3000",
apiPath: "/api/settings",
});
export const IMAGETRANSFORMATION_SERVICE_BASE_URI = hostURIBuilder({
protocol: "http",
host: import.meta.env.UNDERLYING_HOSTNAME || "localhost",
host: process.env.UNDERLYING_HOSTNAME || "localhost",
port: "3000",
apiPath: "/api/imagetransformation",
});
export const SOCKET_BASE_URI = hostURIBuilder({
protocol: "ws",
host: import.meta.env.UNDERLYING_HOSTNAME || "localhost",
host: process.env.UNDERLYING_HOSTNAME || "localhost",
port: "3001",
apiPath: `/`,
});

View File

@@ -233,7 +233,7 @@ function fileOpsReducer(state = initialState, action) {
case SS_SEARCH_RESULTS_FETCHED_SPECIAL: {
const foo = [];
if (!isUndefined(action.data.hits)) {
map(action.data.hits, ({ _source }) => {
map(action.data.hits.hits, ({ _source }) => {
foo.push(_source);
});
}
@@ -246,7 +246,7 @@ function fileOpsReducer(state = initialState, action) {
case WANTED_COMICS_FETCHED: {
const foo = [];
if (!isUndefined(action.data.hits)) {
map(action.data.hits, ({ _source }) => {
map(action.data.hits.hits, ({ _source }) => {
foo.push(_source);
});
}

View File

@@ -30,20 +30,20 @@ RawFileDetails.args = {
},
"inferredMetadata": {
"issue": {
"name": "Batman Annual 02",
"name": "Fantastic Four 143",
"number": 197402,
"year": "1974",
"subtitle": ""
}
},
"rawFileDetails": {
"name": "Batman Annual 02",
"name": "197402 Fantastic Four 143",
"filePath": "/Users/rishi/work/threetwo-core-service/comics/197402 Fantastic Four 143.cbz",
"fileSize": 10341074,
"extension": ".cbz",
"containedIn": "./userdata/covers/197402 Fantastic Four 143",
"cover": {
"filePath": "userdata/covers/Batman Annual 02/Batman 002_00a.jpg"
"filePath": "userdata/covers/197402 Fantastic Four 143/FF143_01.jpg"
}
},
"sourcedMetadata": { "comicInfo": null, "comicvine": { "aliases": [] } },

1
src/funding.yml Normal file
View File

@@ -0,0 +1 @@
github: [rishighan]

View File

@@ -30,12 +30,12 @@ app.use(bodyParser.urlencoded({ extended: true }));
// REGISTER ROUTES
// all of the routes will be prefixed with /api
const routes: Router[] = Object.values(router);
// app.use("/api", routes);
app.use("/api", routes);
// Send index.html on root request
// app.use(express.static("dist"));
// app.use(express.static("public"));
app.use(express.static("dist"));
app.use(express.static("public"));
// app.listen(port);
app.listen(port);
// console.log(`Server is listening on ${port}`);
console.log(`Server is listening on ${port}`);

View File

@@ -2,21 +2,19 @@
"extends": "@tsconfig/node14/tsconfig.json",
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"noImplicitAny": false,
"target": "es2019",
"jsx": "react",
"module": "commonjs",
"sourceMap": true,
"outDir": "./dist/",
"skipLibCheck": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
"lib": ["dom", "dom.iterable", "esnext", "webworker"],
"baseUrl": ".",
"esModuleInterop": true,
"strict": true
},
"settings": {
"eslint.workingDirectories": [

View File

@@ -1,23 +0,0 @@
import react from "@vitejs/plugin-react";
import { nodeResolve } from "@rollup/plugin-node-resolve";
import { defineConfig } from "vite";
export default defineConfig({
publicDir: "public",
base: "",
build: "esnext",
server: { host: true },
plugins: [
nodeResolve({
// browser: true
exportConditions: ["node"],
}),
react({
// Use React plugin in all *.jsx and *.tsx files
include: "**/*.{jsx,tsx}",
babel: {
plugins: ["babel-plugin-styled-components"],
},
}),
],
});

128
webpack.config.js Normal file
View File

@@ -0,0 +1,128 @@
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const webpack = require("webpack");
const outputDirectory = "dist";
const BundleAnalyzerPlugin =
require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
const CopyPlugin = require("copy-webpack-plugin");
module.exports = (_, argv) => {
const mode = argv.mode;
const isDevelopment = mode === "development";
return {
entry: ["babel-polyfill", "./src/client/index.tsx"],
output: {
path: path.resolve(__dirname, outputDirectory),
filename: "./js/[name].bundle.js",
},
devtool: "source-map",
stats: {
children: true,
},
experiments: {
topLevelAwait: true,
},
module: {
rules: [
{
test: /\.worker\.(js|ts)$/i,
use: [
{
loader: "comlink-loader",
options: {
singleton: true,
},
},
],
},
{
test: [/\.js?$/, /\.jsx?$/, /\.tsx?$/],
use: [
{
loader: "babel-loader",
options: {
plugins: [
isDevelopment && require("react-refresh/babel"),
].filter(Boolean),
},
},
],
exclude: /node_modules/,
},
{
enforce: "pre",
test: /\.js$/,
loader: "source-map-loader",
},
{
test: /\.css$/i,
use: ["style-loader", "css-loader"],
},
{
test: /\.s[ac]ss$/i,
use: [
// Creates `style` nodes from JS strings
"style-loader",
// Translates CSS into CommonJS
"css-loader",
// Compiles Sass to CSS
"sass-loader",
],
},
{
test: /\.(png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)$/i,
use: [
"file-loader?hash=sha512&digest=hex&name=img/[contenthash].[ext]",
"image-webpack-loader?bypassOnDebug&optipng.optimizationLevel=7&gifsicle.interlaced=false",
],
},
],
},
resolve: {
extensions: ["*", ".ts", ".tsx", ".js", ".jsx", ".json"],
aliasFields: ["browser", "browser.esm"],
},
mode: process.env.NODE_ENV,
devServer: {
hot: true,
port: 3050,
open: true,
proxy: {
"/api/**": {
target: "http://localhost:8050",
secure: false,
changeOrigin: true,
},
},
},
optimization: {
usedExports: false,
},
plugins: [
// new BundleAnalyzerPlugin(),
new CopyPlugin({
patterns: [{ from: "./src/client/assets/img/", to: "img/" }],
options: {
concurrency: 100,
},
}),
isDevelopment && new ReactRefreshWebpackPlugin(),
new HtmlWebpackPlugin({
template: "./public/index.html",
favicon: "./public/favicon.ico",
title: "express-typescript-react",
}),
new webpack.DefinePlugin({
"process.env.UNDERLYING_HOSTNAME": JSON.stringify(
process.env.UNDERLYING_HOSTNAME,
),
}),
new MiniCssExtractPlugin({
filename: "./css/[name].css",
chunkFilename: "./css/[id].css",
}),
].filter(Boolean),
};
};

11593
yarn.lock

File diff suppressed because it is too large Load Diff