Compare commits
8 Commits
import-que
...
service-st
| Author | SHA1 | Date | |
|---|---|---|---|
| 1d8dd3ca3e | |||
| 3102b489c8 | |||
| 355812513b | |||
| d026cf1a71 | |||
| b8f0b5426e | |||
| 25b706c4f2 | |||
| 0e72d64009 | |||
| c05fb2f341 |
17
.babelrc
Normal file
17
.babelrc
Normal 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"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
module.exports = {
|
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",
|
parser: "@typescript-eslint/parser",
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
sourceType: "module",
|
sourceType: "module",
|
||||||
ecmaVersion: 2020,
|
ecmaVersion: 2020,
|
||||||
ecmaFeatures: {
|
ecmaFeatures: {
|
||||||
jsx: true // Allows for the parsing of JSX
|
jsx: true // Allows for the parsing of JSX
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
plugins: ["@typescript-eslint", "css-modules"],
|
plugins: ["@typescript-eslint", "css-modules"],
|
||||||
settings: {
|
settings: {
|
||||||
"import/resolver": {
|
"import/resolver": {
|
||||||
@@ -18,9 +18,9 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
react: {
|
react: {
|
||||||
version: "detect" // Tells eslint-plugin-react to automatically detect the version of React to use
|
version: "detect" // Tells eslint-plugin-react to automatically detect the version of React to use
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Fine tune rules
|
// Fine tune rules
|
||||||
rules: {
|
rules: {
|
||||||
"@typescript-eslint/no-var-requires": 0
|
"@typescript-eslint/no-var-requires": 0
|
||||||
|
|||||||
2
.github/workflows/docker-image.yml
vendored
2
.github/workflows/docker-image.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@master
|
- uses: actions/checkout@master
|
||||||
- name: Publish to Registry
|
- name: Publish to Registry
|
||||||
uses: elgohr/Publish-Docker-Github-Action@v5
|
uses: elgohr/Publish-Docker-Github-Action@master
|
||||||
with:
|
with:
|
||||||
name: frishi/threetwo
|
name: frishi/threetwo
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
|||||||
19
.storybook/main.js
Normal file
19
.storybook/main.js
Normal 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",
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import type { StorybookConfig } from "@storybook/react-vite";
|
|
||||||
|
|
||||||
const config: StorybookConfig = {
|
|
||||||
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
|
|
||||||
addons: [
|
|
||||||
"@storybook/addon-links",
|
|
||||||
"@storybook/addon-essentials",
|
|
||||||
"@storybook/addon-onboarding",
|
|
||||||
"@storybook/addon-interactions",
|
|
||||||
"@storybook/addon-mdx-gfm"
|
|
||||||
],
|
|
||||||
framework: {
|
|
||||||
name: "@storybook/react-vite",
|
|
||||||
options: {},
|
|
||||||
},
|
|
||||||
docs: {
|
|
||||||
autodocs: "tag",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
export default config;
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
<script>
|
|
||||||
window.global = window;
|
|
||||||
</script>
|
|
||||||
9
.storybook/preview.js
Normal file
9
.storybook/preview.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export const parameters = {
|
||||||
|
actions: { argTypesRegex: "^on[A-Z].*" },
|
||||||
|
controls: {
|
||||||
|
matchers: {
|
||||||
|
color: /(background|color)$/i,
|
||||||
|
date: /Date$/,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -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
BIN
ComicVine Matching.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 MiB |
BIN
DC++ integration.png
Normal file
BIN
DC++ integration.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1013 KiB |
BIN
Dashboard.png
Normal file
BIN
Dashboard.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 MiB |
@@ -1,4 +1,4 @@
|
|||||||
FROM node:18.15.0-alpine
|
FROM node:17.3-alpine
|
||||||
LABEL maintainer="Rishi Ghan <rishi.ghan@gmail.com>"
|
LABEL maintainer="Rishi Ghan <rishi.ghan@gmail.com>"
|
||||||
|
|
||||||
WORKDIR /threetwo
|
WORKDIR /threetwo
|
||||||
@@ -9,11 +9,11 @@ COPY nodemon.json ./
|
|||||||
COPY jsdoc.json ./
|
COPY jsdoc.json ./
|
||||||
|
|
||||||
# RUN apt-get update && apt-get install -y git python3 build-essential autoconf automake g++ libpng-dev make
|
# 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
|
RUN yarn --ignore-engines
|
||||||
|
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
EXPOSE 5173
|
EXPOSE 3050
|
||||||
|
|
||||||
ENTRYPOINT [ "npm", "start" ]
|
ENTRYPOINT [ "npm", "start" ]
|
||||||
BIN
Library.png
Normal file
BIN
Library.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
13
README.md
13
README.md
@@ -6,13 +6,13 @@ ThreeTwo! _aims to be_ a comic book curation app.
|
|||||||
|
|
||||||
### Screenshots
|
### Screenshots
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
### 🦄 Early Development Support Channel
|
### 🦄 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`
|
1. Clone this repo using `git clone https://github.com/rishighan/threetwo.git`
|
||||||
2. `yarn run dev` (you can ignore the warnings)
|
2. `yarn run dev` (you can ignore the warnings)
|
||||||
3. This will open `http://localhost:5173` in your default browser
|
3. This will open `http://localhost:3050` 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.
|
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
|
## Troubleshooting
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
github: [rishighan]
|
|
||||||
157
package.json
157
package.json
@@ -5,104 +5,138 @@
|
|||||||
"main": "server/index.js",
|
"main": "server/index.js",
|
||||||
"typings": "server/index.js",
|
"typings": "server/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "vite build",
|
"build": "webpack --mode production",
|
||||||
"dev": "rimraf dist && npm run build && vite",
|
"start": "npm run build && npm run server",
|
||||||
"start": "npm run build && vite",
|
"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",
|
"docs": "jsdoc -c jsdoc.json",
|
||||||
"storybook": "storybook dev -p 6006",
|
"storybook": "start-storybook -p 6006",
|
||||||
"build-storybook": "storybook build"
|
"build-storybook": "build-storybook"
|
||||||
},
|
},
|
||||||
"author": "Rishi Ghan",
|
"author": "Rishi Ghan",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.17",
|
||||||
"@bluelovers/fast-glob": "https://github.com/rishighan/fast-glob-v2-api.git",
|
"@bluelovers/fast-glob": "https://github.com/rishighan/fast-glob-v2-api.git",
|
||||||
"@dnd-kit/core": "^4.0.0",
|
"@dnd-kit/core": "^4.0.0",
|
||||||
"@dnd-kit/sortable": "^5.0.0",
|
"@dnd-kit/sortable": "^5.0.0",
|
||||||
"@dnd-kit/utilities": "^3.2.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",
|
"@redux-devtools/extension": "^3.2.2",
|
||||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
"@tanstack/react-table": "^8.5.11",
|
||||||
"@tanstack/react-table": "^8.9.3",
|
|
||||||
"@types/mime-types": "^2.1.0",
|
"@types/mime-types": "^2.1.0",
|
||||||
"@types/react-router-dom": "^5.3.3",
|
"@types/react": "^17.0.3",
|
||||||
"@vitejs/plugin-react": "^3.1.0",
|
"@types/react-dom": "^17.0.2",
|
||||||
"airdcpp-apisocket": "^2.5.0-beta.2",
|
"@types/react-redux": "^7.1.16",
|
||||||
"axios": "^1.3.4",
|
"@types/react-router-dom": "^5.1.7",
|
||||||
"axios-cache-interceptor": "^1.0.1",
|
"@types/sharp": "^0.28.0",
|
||||||
|
"@types/socket.io": "^3.0.2",
|
||||||
|
"@types/socket.io-client": "^3.0.0",
|
||||||
|
"airdcpp-apisocket": "^2.4.4",
|
||||||
|
"array-sort-by": "^1.2.1",
|
||||||
|
"axios": "^0.27.2",
|
||||||
"axios-rate-limit": "^1.3.0",
|
"axios-rate-limit": "^1.3.0",
|
||||||
|
"axios-simple-cache-adapter": "^1.1.0",
|
||||||
"babel-polyfill": "^6.26.0",
|
"babel-polyfill": "^6.26.0",
|
||||||
"babel-preset-minify": "^0.5.2",
|
"babel-preset-minify": "^0.5.2",
|
||||||
"better-docs": "^2.7.2",
|
"better-docs": "^2.7.2",
|
||||||
|
"comlink-loader": "^2.0.0",
|
||||||
"date-fns": "^2.28.0",
|
"date-fns": "^2.28.0",
|
||||||
"dayjs": "^1.10.6",
|
"dayjs": "^1.10.6",
|
||||||
"ellipsize": "^0.5.1",
|
"ellipsize": "^0.1.0",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
|
"fastest-validator": "^1.11.0",
|
||||||
"filename-parser": "^1.0.2",
|
"filename-parser": "^1.0.2",
|
||||||
"final-form": "^4.20.2",
|
"final-form": "^4.20.2",
|
||||||
"final-form-arrays": "^3.0.2",
|
"final-form-arrays": "^3.0.2",
|
||||||
"history": "^5.3.0",
|
|
||||||
"html-to-text": "^8.1.0",
|
"html-to-text": "^8.1.0",
|
||||||
"jsdoc": "^3.6.10",
|
"jsdoc": "^3.6.10",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"node-sass": "npm:sass",
|
"opds-extra": "^3.0.9",
|
||||||
"pretty-bytes": "^5.6.0",
|
"pretty-bytes": "^5.6.0",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"qs": "^6.10.5",
|
"qs": "^6.10.5",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-collapsible": "^2.9.0",
|
"react-collapsible": "^2.9.0",
|
||||||
"react-comic-viewer": "^0.4.0",
|
"react-comic-viewer": "^0.4.0",
|
||||||
"react-day-picker": "^8.6.0",
|
"react-day-picker": "^8.0.6",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.1.0",
|
||||||
"react-fast-compare": "^3.2.0",
|
"react-fast-compare": "^3.2.0",
|
||||||
"react-final-form": "^6.5.9",
|
"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-loader-spinner": "^4.0.0",
|
||||||
"react-masonry-css": "^1.0.16",
|
"react-masonry-css": "^1.0.16",
|
||||||
"react-modal": "^3.15.1",
|
"react-modal": "^3.15.1",
|
||||||
"react-redux": "^8.0.5",
|
"react-redux": "^7.2.6",
|
||||||
"react-router": "^6.9.0",
|
"react-router": "^6.2.2",
|
||||||
"react-router-dom": "^6.9.0",
|
"react-router-dom": "^6.2.2",
|
||||||
"react-select": "^5.3.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-slick": "^0.29.0",
|
||||||
"react-sliding-pane": "^7.1.0",
|
"react-sliding-pane": "^7.1.0",
|
||||||
"react-stickynode": "^4.1.0",
|
"react-stickynode": "^4.0.0",
|
||||||
"react-textarea-autosize": "^8.3.4",
|
"react-textarea-autosize": "^8.3.4",
|
||||||
"reapop": "^4.2.1",
|
"reapop": "^4.0.5",
|
||||||
"redux-first-history": "^5.1.1",
|
"redux-first-history": "^5.0.9",
|
||||||
"redux-socket.io-middleware": "^1.0.4",
|
"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",
|
"slick-carousel": "^1.8.1",
|
||||||
"socket.io-client": "^4.3.2",
|
"socket.io-client": "^4.3.2",
|
||||||
"styled-components": "^6.0.7",
|
"styled-components": "^5.3.5",
|
||||||
"threetwo-ui-typings": "^1.0.14",
|
"threetwo-ui-typings": "^1.0.14",
|
||||||
"vite-plugin-html": "^3.2.0",
|
"voca": "^1.4.0",
|
||||||
"websocket": "^1.0.34"
|
"websocket": "^1.0.34",
|
||||||
|
"ws": "^7.5.3",
|
||||||
|
"xml2js": "^0.4.23",
|
||||||
|
"xregexp": "^5.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@storybook/addon-essentials": "^7.3.2",
|
"@babel/cli": "^7.13.10",
|
||||||
"@storybook/addon-interactions": "^7.3.2",
|
"@babel/core": "^7.13.10",
|
||||||
"@storybook/addon-links": "^7.3.2",
|
"@babel/plugin-syntax-top-level-await": "^7.14.5",
|
||||||
"@storybook/addon-mdx-gfm": "^7.3.2",
|
"@babel/plugin-transform-runtime": "^7.13.15",
|
||||||
"@storybook/addon-onboarding": "^1.0.8",
|
"@babel/preset-env": "^7.13.10",
|
||||||
"@storybook/blocks": "^7.3.2",
|
"@babel/preset-react": "^7.12.13",
|
||||||
"@storybook/react": "^7.3.2",
|
"@babel/preset-typescript": "^7.13.0",
|
||||||
"@storybook/react-vite": "^7.3.2",
|
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.7",
|
||||||
"@storybook/testing-library": "^0.2.0",
|
"@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",
|
"@tsconfig/node14": "^1.0.0",
|
||||||
"@types/ellipsize": "^0.1.1",
|
|
||||||
"@types/express": "^4.17.8",
|
"@types/express": "^4.17.8",
|
||||||
"@types/jest": "^26.0.20",
|
"@types/jest": "^26.0.20",
|
||||||
"@types/lodash": "^4.14.168",
|
"@types/lodash": "^4.14.168",
|
||||||
"@types/node": "^14.14.34",
|
"@types/node": "^14.14.34",
|
||||||
"@types/react": "^18.0.28",
|
"@types/react": "^17.0.3",
|
||||||
"@types/react-dom": "^18.0.11",
|
"@types/react-dom": "^17.0.2",
|
||||||
"@types/react-redux": "^7.1.25",
|
"@types/react-redux": "^7.1.16",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.17.0",
|
"@typescript-eslint/eslint-plugin": "^4.17.0",
|
||||||
"@typescript-eslint/parser": "^4.17.0",
|
"@typescript-eslint/parser": "^4.17.0",
|
||||||
"babel-plugin-styled-components": "^2.1.4",
|
"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",
|
"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": "^7.22.0",
|
||||||
"eslint-config-airbnb": "^18.2.1",
|
"eslint-config-airbnb": "^18.2.1",
|
||||||
"eslint-config-airbnb-base": "^14.2.1",
|
"eslint-config-airbnb-base": "^14.2.1",
|
||||||
@@ -112,20 +146,35 @@
|
|||||||
"eslint-plugin-jsx-a11y": "^6.0.3",
|
"eslint-plugin-jsx-a11y": "^6.0.3",
|
||||||
"eslint-plugin-prettier": "^3.3.1",
|
"eslint-plugin-prettier": "^3.3.1",
|
||||||
"eslint-plugin-react": "^7.22.0",
|
"eslint-plugin-react": "^7.22.0",
|
||||||
"eslint-plugin-storybook": "^0.6.13",
|
|
||||||
"express": "^4.17.1",
|
"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",
|
"install": "^0.13.0",
|
||||||
"jest": "^29.6.3",
|
"jest": "^26.6.3",
|
||||||
"nodemon": "^3.0.1",
|
"mini-css-extract-plugin": "^1.4.1",
|
||||||
|
"node-sass": "^7.0.1",
|
||||||
|
"nodemon": "^1.17.3",
|
||||||
"npm": "^8.11.0",
|
"npm": "^8.11.0",
|
||||||
"prettier": "^2.2.1",
|
"prettier": "^2.2.1",
|
||||||
"react-refresh": "^0.14.0",
|
"react-refresh": "^0.14.0",
|
||||||
"rimraf": "^4.1.3",
|
"rimraf": "^3.0.2",
|
||||||
"sass": "^1.66.1",
|
"sass-loader": "^11.0.1",
|
||||||
"storybook": "^7.3.2",
|
"source-map-loader": "^0.2.4",
|
||||||
|
"string-similarity": "^4.0.4",
|
||||||
|
"style-loader": "^2.0.0",
|
||||||
"tslint": "^6.1.3",
|
"tslint": "^6.1.3",
|
||||||
"typescript": "^5.1.6",
|
"typescript": "^4.2.3",
|
||||||
"vite": "^4.4.9"
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="/src/client/index.tsx"></script>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import rateLimiter from "axios-rate-limit";
|
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 {
|
import {
|
||||||
CV_SEARCH_SUCCESS,
|
CV_SEARCH_SUCCESS,
|
||||||
CV_API_CALL_IN_PROGRESS,
|
CV_API_CALL_IN_PROGRESS,
|
||||||
@@ -22,21 +26,25 @@ import {
|
|||||||
LIBRARY_SERVICE_BASE_URI,
|
LIBRARY_SERVICE_BASE_URI,
|
||||||
} from "../constants/endpoints";
|
} from "../constants/endpoints";
|
||||||
|
|
||||||
|
const axiosCacheAdapter = createCacheAdapter();
|
||||||
const http = rateLimiter(axios.create(), {
|
const http = rateLimiter(axios.create(), {
|
||||||
maxRequests: 1,
|
maxRequests: 1,
|
||||||
perMilliseconds: 1000,
|
perMilliseconds: 1000,
|
||||||
maxRPS: 1,
|
maxRPS: 1,
|
||||||
});
|
});
|
||||||
const cachedAxios = setupCache(axios);
|
|
||||||
export const getWeeklyPullList = (options) => async (dispatch) => {
|
export const getWeeklyPullList = (options) => async (dispatch) => {
|
||||||
try {
|
try {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: CV_WEEKLY_PULLLIST_CALL_IN_PROGRESS,
|
type: CV_WEEKLY_PULLLIST_CALL_IN_PROGRESS,
|
||||||
});
|
});
|
||||||
await cachedAxios(`${COMICVINE_SERVICE_URI}/getWeeklyPullList`, {
|
|
||||||
|
await axios(`${COMICVINE_SERVICE_URI}/getWeeklyPullList`, {
|
||||||
method: "get",
|
method: "get",
|
||||||
params: options,
|
params: options,
|
||||||
}).then((response) => {
|
axiosCacheAdapter,
|
||||||
|
cache: 1000, // value in MS
|
||||||
|
} as AxiosCacheRequestConfig).then((response) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: CV_WEEKLY_PULLLIST_FETCHED,
|
type: CV_WEEKLY_PULLLIST_FETCHED,
|
||||||
data: response.data.result,
|
data: response.data.result,
|
||||||
@@ -62,6 +70,9 @@ export const comicinfoAPICall = (options) => async (dispatch) => {
|
|||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"Access-Control-Allow-Origin": "*",
|
"Access-Control-Allow-Origin": "*",
|
||||||
},
|
},
|
||||||
|
paramsSerializer: (params) => {
|
||||||
|
return qs.stringify(params, { arrayFormat: "repeat" });
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
switch (options.callURIAction) {
|
switch (options.callURIAction) {
|
||||||
@@ -126,7 +137,7 @@ export const analyzeLibrary = (issues) => async (dispatch) => {
|
|||||||
queryObjects,
|
queryObjects,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: CV_ISSUES_MATCHES_IN_LIBRARY_FETCHED,
|
type: CV_ISSUES_MATCHES_IN_LIBRARY_FETCHED,
|
||||||
matches: foo.data,
|
matches: foo.data,
|
||||||
|
|||||||
@@ -4,12 +4,13 @@ import {
|
|||||||
COMICVINE_SERVICE_URI,
|
COMICVINE_SERVICE_URI,
|
||||||
IMAGETRANSFORMATION_SERVICE_BASE_URI,
|
IMAGETRANSFORMATION_SERVICE_BASE_URI,
|
||||||
LIBRARY_SERVICE_BASE_URI,
|
LIBRARY_SERVICE_BASE_URI,
|
||||||
|
LIBRARY_SERVICE_HOST,
|
||||||
SEARCH_SERVICE_BASE_URI,
|
SEARCH_SERVICE_BASE_URI,
|
||||||
JOB_QUEUE_SERVICE_BASE_URI,
|
|
||||||
} from "../constants/endpoints";
|
} from "../constants/endpoints";
|
||||||
import {
|
import {
|
||||||
IMS_COMIC_BOOK_GROUPS_FETCHED,
|
IMS_COMIC_BOOK_GROUPS_FETCHED,
|
||||||
IMS_COMIC_BOOK_GROUPS_CALL_IN_PROGRESS,
|
IMS_COMIC_BOOK_GROUPS_CALL_IN_PROGRESS,
|
||||||
|
IMS_COMIC_BOOK_GROUPS_CALL_FAILED,
|
||||||
IMS_RECENT_COMICS_FETCHED,
|
IMS_RECENT_COMICS_FETCHED,
|
||||||
IMS_WANTED_COMICS_FETCHED,
|
IMS_WANTED_COMICS_FETCHED,
|
||||||
CV_API_CALL_IN_PROGRESS,
|
CV_API_CALL_IN_PROGRESS,
|
||||||
@@ -21,38 +22,23 @@ import {
|
|||||||
LS_IMPORT,
|
LS_IMPORT,
|
||||||
IMG_ANALYSIS_CALL_IN_PROGRESS,
|
IMG_ANALYSIS_CALL_IN_PROGRESS,
|
||||||
IMG_ANALYSIS_DATA_FETCH_SUCCESS,
|
IMG_ANALYSIS_DATA_FETCH_SUCCESS,
|
||||||
|
IMS_COMIC_BOOK_ARCHIVE_EXTRACTION_SUCCESS,
|
||||||
IMS_COMIC_BOOK_ARCHIVE_EXTRACTION_CALL_IN_PROGRESS,
|
IMS_COMIC_BOOK_ARCHIVE_EXTRACTION_CALL_IN_PROGRESS,
|
||||||
SS_SEARCH_RESULTS_FETCHED,
|
SS_SEARCH_RESULTS_FETCHED,
|
||||||
SS_SEARCH_IN_PROGRESS,
|
SS_SEARCH_IN_PROGRESS,
|
||||||
FILEOPS_STATE_RESET,
|
FILEOPS_STATE_RESET,
|
||||||
LS_IMPORT_CALL_IN_PROGRESS,
|
LS_IMPORT_CALL_IN_PROGRESS,
|
||||||
|
LS_TOGGLE_IMPORT_QUEUE,
|
||||||
SS_SEARCH_FAILED,
|
SS_SEARCH_FAILED,
|
||||||
SS_SEARCH_RESULTS_FETCHED_SPECIAL,
|
SS_SEARCH_RESULTS_FETCHED_SPECIAL,
|
||||||
WANTED_COMICS_FETCHED,
|
WANTED_COMICS_FETCHED,
|
||||||
VOLUMES_FETCHED,
|
VOLUMES_FETCHED,
|
||||||
LIBRARY_SERVICE_HEALTH,
|
CV_WEEKLY_PULLLIST_FETCHED,
|
||||||
LS_SET_QUEUE_STATUS,
|
|
||||||
LS_IMPORT_JOB_STATISTICS_FETCHED,
|
|
||||||
} from "../constants/action-types";
|
} from "../constants/action-types";
|
||||||
import { success } from "react-notification-system-redux";
|
import { success } from "react-notification-system-redux";
|
||||||
|
|
||||||
import { isNil } from "lodash";
|
import { isNil, map } from "lodash";
|
||||||
|
|
||||||
export const getServiceStatus = (serviceName?: string) => async (dispatch) => {
|
|
||||||
axios
|
|
||||||
.request({
|
|
||||||
url: `${LIBRARY_SERVICE_BASE_URI}/getHealthInformation`,
|
|
||||||
method: "GET",
|
|
||||||
transformResponse: (r: string) => JSON.parse(r),
|
|
||||||
})
|
|
||||||
.then((response) => {
|
|
||||||
const { data } = response;
|
|
||||||
dispatch({
|
|
||||||
type: LIBRARY_SERVICE_HEALTH,
|
|
||||||
status: data,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
export async function walkFolder(path: string): Promise<Array<IFolderData>> {
|
export async function walkFolder(path: string): Promise<Array<IFolderData>> {
|
||||||
return axios
|
return axios
|
||||||
.request<Array<IFolderData>>({
|
.request<Array<IFolderData>>({
|
||||||
@@ -88,38 +74,19 @@ export const fetchComicBookMetadata = () => async (dispatch) => {
|
|||||||
// autoDismiss: 0,
|
// autoDismiss: 0,
|
||||||
// }),
|
// }),
|
||||||
// );
|
// );
|
||||||
const sessionId = localStorage.getItem("sessionId");
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: LS_IMPORT,
|
type: LS_IMPORT,
|
||||||
});
|
meta: { remote: true },
|
||||||
|
data: {},
|
||||||
await axios.request({
|
|
||||||
url: `${LIBRARY_SERVICE_BASE_URI}/newImport`,
|
|
||||||
method: "POST",
|
|
||||||
data: { sessionId },
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
export const toggleImportQueueStatus = (options) => async (dispatch) => {
|
||||||
export const getImportJobResultStatistics = () => async (dispatch) => {
|
|
||||||
const result = await axios.request({
|
|
||||||
url: `${JOB_QUEUE_SERVICE_BASE_URI}/getJobResultStatistics`,
|
|
||||||
method: "GET",
|
|
||||||
});
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: LS_IMPORT_JOB_STATISTICS_FETCHED,
|
type: LS_TOGGLE_IMPORT_QUEUE,
|
||||||
data: result.data,
|
meta: { remote: true },
|
||||||
|
data: { manjhul: "jigyadam", action: options.action },
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setQueueControl =
|
|
||||||
(queueAction: string, queueStatus: string) => async (dispatch) => {
|
|
||||||
dispatch({
|
|
||||||
type: LS_SET_QUEUE_STATUS,
|
|
||||||
meta: { remote: true },
|
|
||||||
data: { queueAction, queueStatus },
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches comic book metadata for various types
|
* Fetches comic book metadata for various types
|
||||||
* @return metadata for the comic book object categories
|
* @return metadata for the comic book object categories
|
||||||
@@ -160,50 +127,49 @@ export const getComicBooks = (options) => async (dispatch) => {
|
|||||||
* @returns Nothing.
|
* @returns Nothing.
|
||||||
* @param payload
|
* @param payload
|
||||||
*/
|
*/
|
||||||
export const importToDB =
|
export const importToDB = (sourceName: string, metadata?: any) => (dispatch) => {
|
||||||
(sourceName: string, metadata?: any) => (dispatch) => {
|
try {
|
||||||
try {
|
const comicBookMetadata = {
|
||||||
const comicBookMetadata = {
|
importType: "new",
|
||||||
importType: "new",
|
payload: {
|
||||||
payload: {
|
rawFileDetails: {
|
||||||
rawFileDetails: {
|
name: "",
|
||||||
name: "",
|
|
||||||
},
|
|
||||||
importStatus: {
|
|
||||||
isImported: true,
|
|
||||||
tagged: false,
|
|
||||||
matchedResult: {
|
|
||||||
score: "0",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
sourcedMetadata: metadata || null,
|
|
||||||
acquisition: { source: { wanted: true, name: sourceName } },
|
|
||||||
},
|
},
|
||||||
};
|
importStatus: {
|
||||||
dispatch({
|
isImported: true,
|
||||||
type: IMS_CV_METADATA_IMPORT_CALL_IN_PROGRESS,
|
tagged: false,
|
||||||
});
|
matchedResult: {
|
||||||
return axios
|
score: "0",
|
||||||
.request({
|
},
|
||||||
url: `${LIBRARY_SERVICE_BASE_URI}/rawImportToDb`,
|
},
|
||||||
method: "POST",
|
sourcedMetadata: metadata || null,
|
||||||
data: comicBookMetadata,
|
acquisition: { source: { wanted: true, name: sourceName } },
|
||||||
// transformResponse: (r: string) => JSON.parse(r),
|
}
|
||||||
})
|
};
|
||||||
.then((response) => {
|
dispatch({
|
||||||
const { data } = response;
|
type: IMS_CV_METADATA_IMPORT_CALL_IN_PROGRESS,
|
||||||
dispatch({
|
});
|
||||||
type: IMS_CV_METADATA_IMPORT_SUCCESSFUL,
|
return axios
|
||||||
importResult: data,
|
.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) => {
|
export const fetchVolumeGroups = () => async (dispatch) => {
|
||||||
try {
|
try {
|
||||||
@@ -288,23 +254,24 @@ export const fetchComicVineMatches =
|
|||||||
* @returns {any}
|
* @returns {any}
|
||||||
*/
|
*/
|
||||||
export const extractComicArchive =
|
export const extractComicArchive =
|
||||||
|
|
||||||
(path: string, options: any): any =>
|
(path: string, options: any): any =>
|
||||||
async (dispatch) => {
|
async (dispatch) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: IMS_COMIC_BOOK_ARCHIVE_EXTRACTION_CALL_IN_PROGRESS,
|
type: IMS_COMIC_BOOK_ARCHIVE_EXTRACTION_CALL_IN_PROGRESS,
|
||||||
});
|
});
|
||||||
await axios({
|
await axios({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: `${LIBRARY_SERVICE_BASE_URI}/uncompressFullArchive`,
|
url: `${LIBRARY_SERVICE_BASE_URI}/uncompressFullArchive`,
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json; charset=utf-8",
|
"Content-Type": "application/json; charset=utf-8",
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
filePath: path,
|
filePath: path,
|
||||||
options,
|
options,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Description
|
* Description
|
||||||
@@ -334,26 +301,26 @@ export const searchIssue = (query, options) => async (dispatch) => {
|
|||||||
case "wantedComicsPage":
|
case "wantedComicsPage":
|
||||||
dispatch({
|
dispatch({
|
||||||
type: WANTED_COMICS_FETCHED,
|
type: WANTED_COMICS_FETCHED,
|
||||||
data: response.data.hits,
|
data: response.data.body,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "globalSearchBar":
|
case "globalSearchBar":
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SS_SEARCH_RESULTS_FETCHED_SPECIAL,
|
type: SS_SEARCH_RESULTS_FETCHED_SPECIAL,
|
||||||
data: response.data.hits,
|
data: response.data.body,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "libraryPage":
|
case "libraryPage":
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SS_SEARCH_RESULTS_FETCHED,
|
type: SS_SEARCH_RESULTS_FETCHED,
|
||||||
data: response.data.hits,
|
data: response.data.body,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "volumesPage":
|
case "volumesPage":
|
||||||
dispatch({
|
dispatch({
|
||||||
type: VOLUMES_FETCHED,
|
type: VOLUMES_FETCHED,
|
||||||
data: response.data.hits,
|
data: response.data.body,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -382,4 +349,4 @@ export const analyzeImage =
|
|||||||
type: IMG_ANALYSIS_DATA_FETCH_SUCCESS,
|
type: IMG_ANALYSIS_DATA_FETCH_SUCCESS,
|
||||||
result: foo.data,
|
result: foo.data,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
@import "/node_modules/bulma/bulma.sass";
|
@import "../../../../node_modules/bulma/bulma.sass";
|
||||||
$fa-font-path: "/node_modules/@fortawesome/fontawesome-free/webfonts";
|
$fa-font-path: "~@fortawesome/fontawesome-free/webfonts";
|
||||||
@import "/node_modules/@fortawesome/fontawesome-free/scss/fontawesome.scss";
|
@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/regular.scss";
|
||||||
@import "/node_modules/@fortawesome/fontawesome-free/scss/solid.scss";
|
@import "../../../../node_modules/@fortawesome/fontawesome-free/scss/solid.scss";
|
||||||
$bg-color: yellow;
|
$bg-color: yellow;
|
||||||
$border-color: red;
|
$border-color: red;
|
||||||
|
|
||||||
|
|||||||
@@ -15,15 +15,14 @@ export const AirDCPPSettingsForm = (): ReactElement => {
|
|||||||
try {
|
try {
|
||||||
if (!isUndefined(hostname)) {
|
if (!isUndefined(hostname)) {
|
||||||
const matches = hostname.match(hostnameRegex);
|
const matches = hostname.match(hostnameRegex);
|
||||||
return isNil(matches) && matches.length !== 0
|
return (isNil(matches) && matches.length !== 0) ? hostname : "Invalid hostname; it should not contain special characters";
|
||||||
? hostname
|
|
||||||
: "Invalid hostname; it should not contain special characters";
|
|
||||||
}
|
}
|
||||||
} catch {
|
}
|
||||||
|
catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const onSubmit = useCallback(async (values) => {
|
const onSubmit = useCallback(async (values) => {
|
||||||
try {
|
try {
|
||||||
airDCPPSettings.setSettings(values);
|
airDCPPSettings.setSettings(values);
|
||||||
@@ -40,7 +39,7 @@ export const AirDCPPSettingsForm = (): ReactElement => {
|
|||||||
airDCPPSettings.setSettings({});
|
airDCPPSettings.setSettings({});
|
||||||
dispatch(deleteSettings());
|
dispatch(deleteSettings());
|
||||||
}, []);
|
}, []);
|
||||||
const validate = async () => {};
|
const validate = async () => { };
|
||||||
const initFormData = !isUndefined(
|
const initFormData = !isUndefined(
|
||||||
airDCPPSettings.airDCPPState.settings.directConnect,
|
airDCPPSettings.airDCPPState.settings.directConnect,
|
||||||
)
|
)
|
||||||
@@ -68,20 +67,13 @@ export const AirDCPPSettingsForm = (): ReactElement => {
|
|||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<div className="control is-expanded">
|
<div className="control is-expanded">
|
||||||
<Field name="hostname" validate={hostValidator}>
|
<Field
|
||||||
|
name="hostname"
|
||||||
|
validate={hostValidator}>
|
||||||
{({ input, meta }) => (
|
{({ input, meta }) => (
|
||||||
<div>
|
<div>
|
||||||
<input
|
<input {...input} type="text" placeholder="AirDC++ hostname" className="input" />
|
||||||
{...input}
|
{meta.error && meta.touched && <span className="is-size-7 has-text-danger">{meta.error}</span>}
|
||||||
type="text"
|
|
||||||
placeholder="AirDC++ hostname"
|
|
||||||
className="input"
|
|
||||||
/>
|
|
||||||
{meta.error && meta.touched && (
|
|
||||||
<span className="is-size-7 has-text-danger">
|
|
||||||
{meta.error}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Field>
|
</Field>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { ReactElement, useContext, useEffect } from "react";
|
import React, { ReactElement, useContext, useEffect } from "react";
|
||||||
import Dashboard from "./Dashboard/Dashboard";
|
import Dashboard from "./Dashboard/Dashboard";
|
||||||
|
|
||||||
import Import from "./Import";
|
import Import from "./Import";
|
||||||
import { ComicDetailContainer } from "./ComicDetail/ComicDetailContainer";
|
import { ComicDetailContainer } from "./ComicDetail/ComicDetailContainer";
|
||||||
import TabulatedContentContainer from "./Library/TabulatedContentContainer";
|
import TabulatedContentContainer from "./Library/TabulatedContentContainer";
|
||||||
@@ -16,9 +17,7 @@ import {
|
|||||||
AirDCPPSocketContextProvider,
|
AirDCPPSocketContextProvider,
|
||||||
AirDCPPSocketContext,
|
AirDCPPSocketContext,
|
||||||
} from "../context/AirDCPPSocket";
|
} from "../context/AirDCPPSocket";
|
||||||
import { SocketIOProvider } from "../context/SocketIOContext";
|
import { isEmpty, isUndefined } from "lodash";
|
||||||
import socketIOConnectionInstance from "../shared/socket.io/instance";
|
|
||||||
import { isEmpty, isNil, isUndefined } from "lodash";
|
|
||||||
import {
|
import {
|
||||||
AIRDCPP_DOWNLOAD_PROGRESS_TICK,
|
AIRDCPP_DOWNLOAD_PROGRESS_TICK,
|
||||||
LS_SINGLE_IMPORT,
|
LS_SINGLE_IMPORT,
|
||||||
@@ -96,65 +95,45 @@ const AirDCPPSocketComponent = (): ReactElement => {
|
|||||||
return <></>;
|
return <></>;
|
||||||
};
|
};
|
||||||
export const App = (): ReactElement => {
|
export const App = (): ReactElement => {
|
||||||
const dispatch = useDispatch();
|
|
||||||
useEffect(() => {
|
|
||||||
// Check if there is a sessionId in localStorage
|
|
||||||
const sessionId = localStorage.getItem("sessionId");
|
|
||||||
if (!isNil(sessionId)) {
|
|
||||||
// Resume the session
|
|
||||||
dispatch({
|
|
||||||
type: "RESUME_SESSION",
|
|
||||||
meta: { remote: true },
|
|
||||||
session: { sessionId },
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Inititalize the session and persist the sessionId to localStorage
|
|
||||||
socketIOConnectionInstance.on("sessionInitialized", (sessionId) => {
|
|
||||||
localStorage.setItem("sessionId", sessionId);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
return (
|
return (
|
||||||
<SocketIOProvider socket={socketIOConnectionInstance}>
|
<AirDCPPSocketContextProvider>
|
||||||
<AirDCPPSocketContextProvider>
|
<div>
|
||||||
<div>
|
<AirDCPPSocketComponent />
|
||||||
<AirDCPPSocketComponent />
|
<Navbar />
|
||||||
<Navbar />
|
<Routes>
|
||||||
<Routes>
|
<Route path="/" element={<Dashboard />} />
|
||||||
<Route path="/" element={<Dashboard />} />
|
<Route path="/import" element={<Import path={"./comics"} />} />
|
||||||
<Route path="/import" element={<Import path={"./comics"} />} />
|
<Route
|
||||||
<Route
|
path="/library"
|
||||||
path="/library"
|
element={<TabulatedContentContainer category="library" />}
|
||||||
element={<TabulatedContentContainer category="library" />}
|
/>
|
||||||
/>
|
<Route path="/library-grid" element={<LibraryGrid />} />
|
||||||
<Route path="/library-grid" element={<LibraryGrid />} />
|
<Route path="/downloads" element={<Downloads data={{}} />} />
|
||||||
<Route path="/downloads" element={<Downloads data={{}} />} />
|
<Route path="/search" element={<Search />} />
|
||||||
<Route path="/search" element={<Search />} />
|
<Route
|
||||||
<Route
|
path={"/comic/details/:comicObjectId"}
|
||||||
path={"/comic/details/:comicObjectId"}
|
element={<ComicDetailContainer />}
|
||||||
element={<ComicDetailContainer />}
|
/>
|
||||||
/>
|
<Route
|
||||||
<Route
|
path={"/volume/details/:comicObjectId"}
|
||||||
path={"/volume/details/:comicObjectId"}
|
element={<VolumeDetail />}
|
||||||
element={<VolumeDetail />}
|
/>
|
||||||
/>
|
<Route path="/settings" element={<Settings />} />
|
||||||
<Route path="/settings" element={<Settings />} />
|
<Route
|
||||||
<Route
|
path="/pull-list/all"
|
||||||
path="/pull-list/all"
|
element={<TabulatedContentContainer category="pullList" />}
|
||||||
element={<TabulatedContentContainer category="pullList" />}
|
/>
|
||||||
/>
|
<Route
|
||||||
<Route
|
path="/wanted/all"
|
||||||
path="/wanted/all"
|
element={<TabulatedContentContainer category="wanted" />}
|
||||||
element={<TabulatedContentContainer category="wanted" />}
|
/>
|
||||||
/>
|
<Route
|
||||||
<Route
|
path="/volumes/all"
|
||||||
path="/volumes/all"
|
element={<TabulatedContentContainer category="volumes" />}
|
||||||
element={<TabulatedContentContainer category="volumes" />}
|
/>
|
||||||
/>
|
</Routes>
|
||||||
</Routes>
|
</div>
|
||||||
</div>
|
</AirDCPPSocketContextProvider>
|
||||||
</AirDCPPSocketContextProvider>
|
|
||||||
</SocketIOProvider>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ export const AcquisitionPanel = (
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<span className="icon is-small">
|
<span className="icon is-small">
|
||||||
<img src="/src/client/assets/img/airdcpp_logo.svg" />
|
<img src="/img/airdcpp_logo.svg" />
|
||||||
</span>
|
</span>
|
||||||
<span className="airdcpp-text">Search on AirDC++</span>
|
<span className="airdcpp-text">Search on AirDC++</span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import React, { useEffect, useContext, ReactElement } from "react";
|
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 { useDispatch, useSelector } from "react-redux";
|
||||||
import { RootState } from "threetwo-ui-typings";
|
import { RootState } from "threetwo-ui-typings";
|
||||||
import { isEmpty, isNil, map } from "lodash";
|
import { isEmpty, isNil, map } from "lodash";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { ReactElement } from "react";
|
import React, { ReactElement } from "react";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import prettyBytes from "pretty-bytes";
|
import prettyBytes from "pretty-bytes";
|
||||||
import { isEmpty } from "lodash";
|
import { isUndefined } from "lodash";
|
||||||
|
|
||||||
export const RawFileDetails = (props): ReactElement => {
|
export const RawFileDetails = (props): ReactElement => {
|
||||||
const { rawFileDetails, inferredMetadata } = props.data;
|
const { rawFileDetails, inferredMetadata } = props.data;
|
||||||
@@ -34,14 +34,6 @@ export const RawFileDetails = (props): ReactElement => {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
@@ -61,7 +53,7 @@ export const RawFileDetails = (props): ReactElement => {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{!isEmpty(inferredMetadata.issue.number) ? (
|
{!isUndefined(inferredMetadata.issue.number) ? (
|
||||||
<div className="control">
|
<div className="control">
|
||||||
<div className="tags has-addons">
|
<div className="tags has-addons">
|
||||||
<span className="tag">Number</span>
|
<span className="tag">Number</span>
|
||||||
@@ -89,7 +81,6 @@ RawFileDetails.propTypes = {
|
|||||||
fileSize: PropTypes.number,
|
fileSize: PropTypes.number,
|
||||||
path: PropTypes.string,
|
path: PropTypes.string,
|
||||||
extension: PropTypes.string,
|
extension: PropTypes.string,
|
||||||
mimeType: PropTypes.string,
|
|
||||||
cover: PropTypes.shape({
|
cover: PropTypes.shape({
|
||||||
filePath: PropTypes.string,
|
filePath: PropTypes.string,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -48,9 +48,7 @@ export const ComicInfoXML = (data): ReactElement => {
|
|||||||
<dd>
|
<dd>
|
||||||
<span className="is-size-7">{json.notes[0]}</span>
|
<span className="is-size-7">{json.notes[0]}</span>
|
||||||
</dd>
|
</dd>
|
||||||
<dd className="mt-1 mb-1">
|
<dd className="mt-1 mb-1">{json.summary[0]}</dd>
|
||||||
{!isUndefined(json.summary) && json.summary[0]}
|
|
||||||
</dd>
|
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import {
|
|||||||
} from "../../actions/fileops.actions";
|
} from "../../actions/fileops.actions";
|
||||||
import { getLibraryStatistics } from "../../actions/comicinfo.actions";
|
import { getLibraryStatistics } from "../../actions/comicinfo.actions";
|
||||||
import { isEmpty, isNil } from "lodash";
|
import { isEmpty, isNil } from "lodash";
|
||||||
import Header from "../Header";
|
|
||||||
|
|
||||||
export const Dashboard = (): ReactElement => {
|
export const Dashboard = (): ReactElement => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@@ -44,7 +43,7 @@ export const Dashboard = (): ReactElement => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const recentComics = useSelector(
|
const recentComics = useSelector(
|
||||||
(state: RootState) => state.fileOps.recentComics,
|
(state: RootState) => state.fileOps.recentComics
|
||||||
);
|
);
|
||||||
const wantedComics = useSelector(
|
const wantedComics = useSelector(
|
||||||
(state: RootState) => state.fileOps.wantedComics,
|
(state: RootState) => state.fileOps.wantedComics,
|
||||||
@@ -65,36 +64,6 @@ export const Dashboard = (): ReactElement => {
|
|||||||
<>
|
<>
|
||||||
{/* Pull List */}
|
{/* Pull List */}
|
||||||
<PullList issues={recentComics} />
|
<PullList issues={recentComics} />
|
||||||
<>
|
|
||||||
<div className="content mt-6">
|
|
||||||
<Header
|
|
||||||
headerContent="Import Activity"
|
|
||||||
subHeaderContent="Results aggregated from the last import"
|
|
||||||
iconClassNames="fa-solid fa-file-invoice mr-2"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<table className="table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
<abbr title="Position">Pos</abbr>
|
|
||||||
</th>
|
|
||||||
<th>Team</th>
|
|
||||||
<th>
|
|
||||||
<abbr title="Played">Pld</abbr>
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th>1</th>
|
|
||||||
<td>38</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</>
|
|
||||||
|
|
||||||
{/* Stats */}
|
{/* Stats */}
|
||||||
{!isEmpty(libraryStatistics) && (
|
{!isEmpty(libraryStatistics) && (
|
||||||
@@ -105,8 +74,8 @@ export const Dashboard = (): ReactElement => {
|
|||||||
<WantedComicsList comics={wantedComics} />
|
<WantedComicsList comics={wantedComics} />
|
||||||
)}
|
)}
|
||||||
{/* Recent imports */}
|
{/* Recent imports */}
|
||||||
<RecentlyImported comicBookCovers={recentComics} />
|
<RecentlyImported comicBookCovers={recentComics} />
|
||||||
|
|
||||||
{/* Volumes */}
|
{/* Volumes */}
|
||||||
{!isEmpty(volumeGroups) && (
|
{!isEmpty(volumeGroups) && (
|
||||||
<VolumeGroups volumeGroups={volumeGroups} />
|
<VolumeGroups volumeGroups={volumeGroups} />
|
||||||
@@ -125,4 +94,4 @@ export const Dashboard = (): ReactElement => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Dashboard;
|
export default Dashboard;
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import { isNil, map } from "lodash";
|
import { isNil, map } from "lodash";
|
||||||
import React, { createRef, ReactElement, useCallback, useEffect } from "react";
|
import React, { createRef, ReactElement, useCallback, useEffect } from "react";
|
||||||
import Card from "../Carda";
|
import Card from "../Carda";
|
||||||
import Header from "../Header";
|
|
||||||
import Masonry from "react-masonry-css";
|
import Masonry from "react-masonry-css";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { getWeeklyPullList } from "../../actions/comicinfo.actions";
|
import { getWeeklyPullList } from "../../actions/comicinfo.actions";
|
||||||
@@ -21,7 +20,7 @@ export const PullList = ({ issues }: PullListProps): ReactElement => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(
|
dispatch(
|
||||||
getWeeklyPullList({
|
getWeeklyPullList({
|
||||||
startDate: "2023-8-9",
|
startDate: "2022-12-25",
|
||||||
pageSize: "15",
|
pageSize: "15",
|
||||||
currentPage: "1",
|
currentPage: "1",
|
||||||
}),
|
}),
|
||||||
@@ -89,9 +88,12 @@ export const PullList = ({ issues }: PullListProps): ReactElement => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="content">
|
<div className="content">
|
||||||
<Header headerContent="Discover"
|
<h4 className="title is-4">
|
||||||
subHeaderContent="Pull List aggregated for the week from League Of Comic Geeks"
|
<i className="fa-solid fa-splotch"></i> Discover
|
||||||
iconClassNames="fa-solid fa-splotch mr-2"/>
|
</h4>
|
||||||
|
<p className="subtitle is-7">
|
||||||
|
Pull List aggregated for the week from League Of Comic Geeks
|
||||||
|
</p>
|
||||||
<div className="field is-grouped">
|
<div className="field is-grouped">
|
||||||
{/* select week */}
|
{/* select week */}
|
||||||
<div className="control">
|
<div className="control">
|
||||||
@@ -125,7 +127,7 @@ export const PullList = ({ issues }: PullListProps): ReactElement => {
|
|||||||
<Slider {...settings} ref={(c) => (sliderRef = c)}>
|
<Slider {...settings} ref={(c) => (sliderRef = c)}>
|
||||||
{!isNil(pullList) &&
|
{!isNil(pullList) &&
|
||||||
pullList &&
|
pullList &&
|
||||||
map(pullList, ({ issue }, idx) => {
|
map(pullList, ({issue}, idx) => {
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
key={idx}
|
key={idx}
|
||||||
@@ -159,4 +161,4 @@ export const PullList = ({ issues }: PullListProps): ReactElement => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PullList;
|
export default PullList;
|
||||||
@@ -83,14 +83,14 @@ export const RecentlyImported = ({
|
|||||||
{/* Raw file presence */}
|
{/* Raw file presence */}
|
||||||
{isNil(rawFileDetails) && (
|
{isNil(rawFileDetails) && (
|
||||||
<span className="icon custom-icon is-small has-text-danger mr-2">
|
<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>
|
</span>
|
||||||
)}
|
)}
|
||||||
{/* ComicInfo.xml presence */}
|
{/* ComicInfo.xml presence */}
|
||||||
{!isNil(comicInfo) && !isEmpty(comicInfo) && (
|
{!isNil(comicInfo) && !isEmpty(comicInfo) && (
|
||||||
<span className="icon custom-icon is-small has-text-danger">
|
<span className="icon custom-icon is-small has-text-danger">
|
||||||
<img
|
<img
|
||||||
src="/src/client/assets/img/comicinfoxml.svg"
|
src="/img/comicinfoxml.svg"
|
||||||
alt={"ComicInfo.xml file detected."}
|
alt={"ComicInfo.xml file detected."}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
@@ -99,8 +99,8 @@ export const RecentlyImported = ({
|
|||||||
{isComicBookMetadataAvailable && (
|
{isComicBookMetadataAvailable && (
|
||||||
<span className="icon custom-icon">
|
<span className="icon custom-icon">
|
||||||
<img
|
<img
|
||||||
src="/src/client/assets/img/cvlogo.svg"
|
src="/img/cvlogo.svg"
|
||||||
alt={"ComicVine metadata detected."}
|
alt={"ComicInfo.xml file detected."}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -125,7 +125,7 @@ export const RecentlyImported = ({
|
|||||||
<dd className="is-size-9">
|
<dd className="is-size-9">
|
||||||
<dl>
|
<dl>
|
||||||
<span className="icon custom-icon">
|
<span className="icon custom-icon">
|
||||||
<img src={`/src/client/assets/img/${icon}`} />
|
<img src={`/img/${icon}`} />
|
||||||
</span>
|
</span>
|
||||||
</dl>
|
</dl>
|
||||||
<dl>
|
<dl>
|
||||||
|
|||||||
@@ -82,12 +82,12 @@ export const WantedComicsList = ({
|
|||||||
{/* comicVine metadata presence */}
|
{/* comicVine metadata presence */}
|
||||||
{isComicBookMetadataAvailable && (
|
{isComicBookMetadataAvailable && (
|
||||||
<span className="icon custom-icon">
|
<span className="icon custom-icon">
|
||||||
<img src="/src/client/assets/img/cvlogo.svg" />
|
<img src="/img/cvlogo.svg" />
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{!isEmpty(locg) && (
|
{!isEmpty(locg) && (
|
||||||
<span className="icon custom-icon">
|
<span className="icon custom-icon">
|
||||||
<img src="/src/client/assets/img/locglogo.svg" />
|
<img src="/img/locglogo.svg" />
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{/* Issue type */}
|
{/* Issue type */}
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
import React, {
|
import React, { ReactElement, useCallback, useContext, useEffect, useState } from "react";
|
||||||
ReactElement,
|
|
||||||
useCallback,
|
|
||||||
useContext,
|
|
||||||
useEffect,
|
|
||||||
useState,
|
|
||||||
} from "react";
|
|
||||||
import { getTransfers } from "../../actions/airdcpp.actions";
|
import { getTransfers } from "../../actions/airdcpp.actions";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { AirDCPPSocketContext } from "../../context/AirDCPPSocket";
|
import { AirDCPPSocketContext } from "../../context/AirDCPPSocket";
|
||||||
import { isEmpty, isNil, isUndefined } from "lodash";
|
import { isEmpty, isNil, isUndefined } from "lodash";
|
||||||
|
import { searchIssue } from "../../actions/fileops.actions";
|
||||||
import { determineCoverFile } from "../../shared/utils/metadata.utils";
|
import { determineCoverFile } from "../../shared/utils/metadata.utils";
|
||||||
import MetadataPanel from "../shared/MetadataPanel";
|
import MetadataPanel from "../shared/MetadataPanel";
|
||||||
|
|
||||||
@@ -26,9 +21,7 @@ export const Downloads = (props: IDownloadsProps): ReactElement => {
|
|||||||
const airDCPPTransfers = useSelector(
|
const airDCPPTransfers = useSelector(
|
||||||
(state: RootState) => state.airdcpp.transfers,
|
(state: RootState) => state.airdcpp.transfers,
|
||||||
);
|
);
|
||||||
const issueBundles = useSelector(
|
const issueBundles = useSelector((state: RootState) => state.airdcpp.issue_bundles);
|
||||||
(state: RootState) => state.airdcpp.issue_bundles,
|
|
||||||
);
|
|
||||||
const [bundles, setBundles] = useState([]);
|
const [bundles, setBundles] = useState([]);
|
||||||
// Make the call to get all transfers from AirDC++
|
// Make the call to get all transfers from AirDC++
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -45,86 +38,62 @@ export const Downloads = (props: IDownloadsProps): ReactElement => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isUndefined(issueBundles)) {
|
if (!isUndefined(issueBundles)) {
|
||||||
const foo = issueBundles.data.map((bundle) => {
|
const foo = issueBundles.data.map((bundle) => {
|
||||||
const {
|
const { rawFileDetails, inferredMetadata, acquisition: { directconnect: { downloads } }, sourcedMetadata: { locg, comicvine } } = bundle;
|
||||||
rawFileDetails,
|
|
||||||
inferredMetadata,
|
|
||||||
acquisition: {
|
|
||||||
directconnect: { downloads },
|
|
||||||
},
|
|
||||||
sourcedMetadata: { locg, comicvine },
|
|
||||||
} = bundle;
|
|
||||||
const { issueName, url } = determineCoverFile({
|
const { issueName, url } = determineCoverFile({
|
||||||
rawFileDetails,
|
rawFileDetails, comicvine, locg,
|
||||||
comicvine,
|
|
||||||
locg,
|
|
||||||
});
|
});
|
||||||
return { ...bundle, issueName, url };
|
return { ...bundle, issueName, url }
|
||||||
});
|
|
||||||
|
})
|
||||||
setBundles(foo);
|
setBundles(foo);
|
||||||
}
|
}
|
||||||
}, [issueBundles]);
|
|
||||||
|
|
||||||
return !isNil(bundles) ? (
|
}, [issueBundles])
|
||||||
|
|
||||||
|
return !isNil(bundles) ?
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<section className="section">
|
<section className="section">
|
||||||
<h1 className="title">Downloads</h1>
|
<h1 className="title">Downloads</h1>
|
||||||
<div className="columns">
|
<div className="columns">
|
||||||
<div className="column is-half">
|
<div className="column is-half">
|
||||||
{bundles.map((bundle, idx) => {
|
{bundles.map(bundle => {
|
||||||
console.log(bundle);
|
console.log(bundle);
|
||||||
return (
|
return <>
|
||||||
<div key={idx}>
|
<MetadataPanel
|
||||||
<MetadataPanel
|
data={bundle}
|
||||||
data={bundle}
|
imageStyle={{ maxWidth: 80 }}
|
||||||
imageStyle={{ maxWidth: 80 }}
|
titleStyle={{ fontSize: "0.8rem" }}
|
||||||
titleStyle={{ fontSize: "0.8rem" }}
|
tagsStyle={{ fontSize: "0.7rem" }}
|
||||||
tagsStyle={{ fontSize: "0.7rem" }}
|
containerStyle={{
|
||||||
containerStyle={{
|
maxWidth: 400,
|
||||||
maxWidth: 400,
|
padding: 0,
|
||||||
padding: 0,
|
margin: "0 0 8px 0",
|
||||||
margin: "0 0 8px 0",
|
}} />
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<table className="table is-size-7">
|
<table className="table is-size-7">
|
||||||
<thead>
|
<tr>
|
||||||
<tr>
|
<th>Name</th>
|
||||||
<th>Name</th>
|
<th>Size</th>
|
||||||
<th>Size</th>
|
<th>Type</th>
|
||||||
<th>Type</th>
|
<th>Bundle ID</th>
|
||||||
<th>Bundle ID</th>
|
</tr>
|
||||||
</tr>
|
|
||||||
</thead>
|
{bundle.acquisition.directconnect.downloads.map((bundle) => {
|
||||||
<tbody>
|
return(<tr>
|
||||||
{bundle.acquisition.directconnect.downloads.map(
|
<td>{bundle.name}</td>
|
||||||
(bundle, idx) => {
|
<td>{bundle.size}</td>
|
||||||
return (
|
<td>{bundle.type.str}</td>
|
||||||
<tr key={idx}>
|
<td><span className="tag is-warning">{bundle.bundleId}</span></td>
|
||||||
<td>{bundle.name}</td>
|
</tr>)
|
||||||
<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> */}
|
{/* <pre>{JSON.stringify(bundle.acquisition.directconnect.downloads, null, 2)}</pre> */}
|
||||||
</div>
|
</>
|
||||||
);
|
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div> : <div>asd</div>;
|
||||||
) : (
|
|
||||||
<div>There are no downloads.</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Downloads;
|
export default Downloads;
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export const SearchBar = (data: ISearchBarProps): ReactElement => {
|
|||||||
from: 0,
|
from: 0,
|
||||||
},
|
},
|
||||||
type: "volumeName",
|
type: "volumeName",
|
||||||
trigger: "globalSearchBar",
|
trigger: "globalSearchBar"
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -51,6 +51,7 @@ export const SearchBar = (data: ISearchBarProps): ReactElement => {
|
|||||||
<i className="fa-solid fa-magnifying-glass"></i>
|
<i className="fa-solid fa-magnifying-glass"></i>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!isEmpty(searchResults) ? (
|
{!isEmpty(searchResults) ? (
|
||||||
<div
|
<div
|
||||||
className="columns box is-multiline"
|
className="columns box is-multiline"
|
||||||
@@ -61,10 +62,9 @@ export const SearchBar = (data: ISearchBarProps): ReactElement => {
|
|||||||
margin: "60px 0 0 350px",
|
margin: "60px 0 0 350px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{map(searchResults, (result, idx) => (
|
{map(searchResults, (result) => (
|
||||||
<MetadataPanel
|
<MetadataPanel
|
||||||
data={result}
|
data={result}
|
||||||
key={idx}
|
|
||||||
imageStyle={{ maxWidth: 70 }}
|
imageStyle={{ maxWidth: 70 }}
|
||||||
titleStyle={{ fontSize: "0.8rem" }}
|
titleStyle={{ fontSize: "0.8rem" }}
|
||||||
tagsStyle={{ fontSize: "0.7rem" }}
|
tagsStyle={{ fontSize: "0.7rem" }}
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
import React, { ReactElement } from "react";
|
|
||||||
|
|
||||||
type IHeaderProps = {
|
|
||||||
headerContent: string;
|
|
||||||
subHeaderContent: string;
|
|
||||||
iconClassNames: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Header = (props: IHeaderProps): ReactElement => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<h4 className="title is-4">
|
|
||||||
<i className={props.iconClassNames}></i> {props.headerContent}
|
|
||||||
</h4>
|
|
||||||
<p className="subtitle is-7">{props.subHeaderContent}</p>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Header;
|
|
||||||
@@ -1,14 +1,11 @@
|
|||||||
import React, { ReactElement, useCallback, useEffect } from "react";
|
import React, { ReactElement, useCallback, useContext, useState } from "react";
|
||||||
import { useSelector, useDispatch } from "react-redux";
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
import {
|
import {
|
||||||
fetchComicBookMetadata,
|
fetchComicBookMetadata,
|
||||||
getImportJobResultStatistics,
|
toggleImportQueueStatus,
|
||||||
setQueueControl,
|
|
||||||
} from "../actions/fileops.actions";
|
} from "../actions/fileops.actions";
|
||||||
import "react-loader-spinner/dist/loader/css/react-spinner-loader.css";
|
import "react-loader-spinner/dist/loader/css/react-spinner-loader.css";
|
||||||
import { format } from "date-fns";
|
|
||||||
import Loader from "react-loader-spinner";
|
import Loader from "react-loader-spinner";
|
||||||
import { isEmpty, isNil, isUndefined } from "lodash";
|
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
matches?: unknown;
|
matches?: unknown;
|
||||||
@@ -32,203 +29,102 @@ interface IProps {
|
|||||||
|
|
||||||
export const Import = (props: IProps): ReactElement => {
|
export const Import = (props: IProps): ReactElement => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const successfulImportJobCount = useSelector(
|
const libraryQueueResults = useSelector(
|
||||||
(state: RootState) => state.fileOps.successfulJobCount,
|
(state: RootState) => state.fileOps.librarySearchResultCount,
|
||||||
);
|
|
||||||
const failedImportJobCount = useSelector(
|
|
||||||
(state: RootState) => state.fileOps.failedJobCount,
|
|
||||||
);
|
|
||||||
|
|
||||||
const lastQueueJob = useSelector(
|
|
||||||
(state: RootState) => state.fileOps.lastQueueJob,
|
|
||||||
);
|
);
|
||||||
const libraryQueueImportStatus = useSelector(
|
const libraryQueueImportStatus = useSelector(
|
||||||
(state: RootState) => state.fileOps.LSQueueImportStatus,
|
(state: RootState) => state.fileOps.IMSCallInProgress,
|
||||||
);
|
);
|
||||||
|
const [isImportQueuePaused, setImportQueueStatus] = useState(false);
|
||||||
const allImportJobResults = useSelector(
|
|
||||||
(state: RootState) => state.fileOps.importJobStatistics,
|
|
||||||
);
|
|
||||||
|
|
||||||
const initiateImport = useCallback(() => {
|
const initiateImport = useCallback(() => {
|
||||||
if (typeof props.path !== "undefined") {
|
if (typeof props.path !== "undefined") {
|
||||||
dispatch(fetchComicBookMetadata(props.path));
|
dispatch(fetchComicBookMetadata(props.path));
|
||||||
}
|
}
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
const toggleQueue = useCallback(
|
const toggleImport = useCallback(() => {
|
||||||
(queueAction: string, queueStatus: string) => {
|
setImportQueueStatus(!isImportQueuePaused);
|
||||||
dispatch(setQueueControl(queueAction, queueStatus));
|
if (isImportQueuePaused === false) {
|
||||||
},
|
dispatch(toggleImportQueueStatus({ action: "resume" }));
|
||||||
[],
|
} else if (isImportQueuePaused === true) {
|
||||||
);
|
dispatch(toggleImportQueueStatus({ action: "pause" }));
|
||||||
useEffect(() => {
|
|
||||||
dispatch(getImportJobResultStatistics());
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const renderQueueControls = (status: string): ReactElement | null => {
|
|
||||||
switch (status) {
|
|
||||||
case "running":
|
|
||||||
return (
|
|
||||||
<div className="control">
|
|
||||||
<button
|
|
||||||
className="button is-warning is-light"
|
|
||||||
onClick={() => toggleQueue("pause", "paused")}
|
|
||||||
>
|
|
||||||
<i className="fa-solid fa-pause mr-2"></i> Pause
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
case "paused":
|
|
||||||
return (
|
|
||||||
<div className="control">
|
|
||||||
<button
|
|
||||||
className="button is-success is-light"
|
|
||||||
onClick={() => toggleQueue("resume", "running")}
|
|
||||||
>
|
|
||||||
<i className="fa-solid fa-play mr-2"></i> Resume
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
case "drained":
|
|
||||||
return null;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
};
|
}, [isImportQueuePaused]);
|
||||||
|
const pauseIconText = (
|
||||||
|
<>
|
||||||
|
<i className="fa-solid fa-pause mr-2"></i> Pause Import
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
const playIconText = (
|
||||||
|
<>
|
||||||
|
<i className="fa-solid fa-play mr-2"></i> Resume Import
|
||||||
|
</>
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<section className="section is-small">
|
<section className="section is-small">
|
||||||
<h1 className="title">Import Comics</h1>
|
<h1 className="title">Import</h1>
|
||||||
<article className="message is-dark">
|
<article className="message is-dark">
|
||||||
<div className="message-body">
|
<div className="message-body">
|
||||||
<p className="mb-2">
|
<p className="mb-2">
|
||||||
<span className="tag is-medium is-info is-light">
|
<span className="tag is-medium is-info is-light">
|
||||||
Import Comics
|
Import Only
|
||||||
</span>
|
</span>
|
||||||
will add comics identified from the mapped folder into ThreeTwo's
|
will add comics identified from the mapped folder into the local
|
||||||
database.
|
db.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Metadata from ComicInfo.xml, if present, will also be extracted.
|
<span className="tag is-medium is-info is-light">
|
||||||
</p>
|
Import and Tag
|
||||||
<p>
|
</span>
|
||||||
This process could take a while, if you have a lot of comics, or
|
will scan the ComicVine, shortboxed APIs and import comics from
|
||||||
are importing over a network connection.
|
the mapped folder with the additional metadata.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
<p className="buttons">
|
<p className="buttons">
|
||||||
<button
|
<button
|
||||||
className={
|
className={
|
||||||
libraryQueueImportStatus === "drained" ||
|
libraryQueueImportStatus
|
||||||
libraryQueueImportStatus === undefined
|
? "button is-loading is-medium"
|
||||||
? "button is-medium"
|
: "button is-medium"
|
||||||
: "button is-loading is-medium"
|
|
||||||
}
|
}
|
||||||
onClick={initiateImport}
|
onClick={initiateImport}
|
||||||
>
|
>
|
||||||
<span className="icon">
|
<span className="icon">
|
||||||
<i className="fas fa-file-import"></i>
|
<i className="fas fa-file-import"></i>
|
||||||
</span>
|
</span>
|
||||||
<span>Start Import</span>
|
<span>Import Only</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button className="button is-medium">
|
||||||
|
<span className="icon">
|
||||||
|
<i className="fas fa-tag"></i>
|
||||||
|
</span>
|
||||||
|
<span>Import and Tag</span>
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
{libraryQueueImportStatus !== "drained" &&
|
<div className="columns is-multiline">
|
||||||
!isUndefined(libraryQueueImportStatus) && (
|
<div className="column is-one-fifth">
|
||||||
<>
|
<div className="box control-palette">
|
||||||
<table className="table">
|
<span className="is-size-2 has-text-weight-bold">
|
||||||
<thead>
|
{JSON.stringify(libraryQueueResults, null, 2)}
|
||||||
<tr>
|
</span>
|
||||||
<th>Completed Jobs</th>
|
</div>
|
||||||
<th>Failed Jobs</th>
|
<div className="is-half">
|
||||||
<th>Queue Controls</th>
|
<div className="content">
|
||||||
<th>Queue Status</th>
|
<div className="control">
|
||||||
</tr>
|
<button
|
||||||
</thead>
|
className="button is-warning is-light"
|
||||||
|
onClick={toggleImport}
|
||||||
<tbody>
|
>
|
||||||
<tr>
|
{!isImportQueuePaused ? pauseIconText : playIconText}
|
||||||
<th>
|
</button>
|
||||||
{successfulImportJobCount > 0 && (
|
</div>
|
||||||
<div className="box has-background-success-light has-text-centered">
|
</div>
|
||||||
<span className="is-size-2 has-text-weight-bold">
|
</div>
|
||||||
{successfulImportJobCount}
|
</div>
|
||||||
</span>
|
</div>
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</th>
|
|
||||||
<td>
|
|
||||||
{failedImportJobCount > 0 && (
|
|
||||||
<div className="box has-background-danger has-text-centered">
|
|
||||||
<span className="is-size-2 has-text-weight-bold">
|
|
||||||
{failedImportJobCount}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td>{renderQueueControls(libraryQueueImportStatus)}</td>
|
|
||||||
<td>
|
|
||||||
{libraryQueueImportStatus !== undefined ? (
|
|
||||||
<span className="tag is-warning">
|
|
||||||
{libraryQueueImportStatus}
|
|
||||||
</span>
|
|
||||||
) : null}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
Imported{" "}
|
|
||||||
<span className="has-text-weight-bold">{lastQueueJob}</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Past imports */}
|
|
||||||
|
|
||||||
<h3 className="subtitle is-4 mt-5">Past Imports</h3>
|
|
||||||
<table className="table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Time Started</th>
|
|
||||||
<th>Session Id</th>
|
|
||||||
<th>Imported</th>
|
|
||||||
<th>Failed</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
{allImportJobResults.map((jobResult, id) => {
|
|
||||||
return (
|
|
||||||
<tr key={id}>
|
|
||||||
<td>
|
|
||||||
{format(
|
|
||||||
new Date(jobResult.earliestTimestamp),
|
|
||||||
"EEEE, hh:mma, do LLLL Y",
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span className="tag is-warning">
|
|
||||||
{jobResult.sessionId}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span className="tag is-success">
|
|
||||||
{jobResult.completedJobs}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span className="tag is-danger">
|
|
||||||
{jobResult.failedJobs}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -19,7 +19,10 @@ export const Library = (): ReactElement => {
|
|||||||
const searchResults = useSelector(
|
const searchResults = useSelector(
|
||||||
(state: RootState) => state.fileOps.libraryComics,
|
(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();
|
const dispatch = useDispatch();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(
|
dispatch(
|
||||||
@@ -47,7 +50,7 @@ export const Library = (): ReactElement => {
|
|||||||
|
|
||||||
const ComicInfoXML = (value) => {
|
const ComicInfoXML = (value) => {
|
||||||
return value.data ? (
|
return value.data ? (
|
||||||
<div className="comicvine-metadata mt-3">
|
<div className="comicvine-metadata">
|
||||||
<dl>
|
<dl>
|
||||||
<span className="tags has-addons is-size-7">
|
<span className="tags has-addons is-size-7">
|
||||||
<span className="tag">Series</span>
|
<span className="tag">Series</span>
|
||||||
@@ -218,9 +221,9 @@ export const Library = (): ReactElement => {
|
|||||||
<div>
|
<div>
|
||||||
<div className="library">
|
<div className="library">
|
||||||
<T2Table
|
<T2Table
|
||||||
totalPages={searchResults.total.value}
|
totalPages={searchResults.hits.total.value}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
sourceData={searchResults?.hits}
|
sourceData={searchResults?.hits?.hits}
|
||||||
rowClickHandler={navigateToComicDetail}
|
rowClickHandler={navigateToComicDetail}
|
||||||
paginationHandlers={{
|
paginationHandlers={{
|
||||||
nextPage,
|
nextPage,
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export const LibraryGrid = (libraryGridProps: ILibraryGridProps) => {
|
|||||||
<div className="content is-flex is-flex-direction-row">
|
<div className="content is-flex is-flex-direction-row">
|
||||||
{!isEmpty(sourcedMetadata.comicvine) && (
|
{!isEmpty(sourcedMetadata.comicvine) && (
|
||||||
<span className="icon cv-icon is-small">
|
<span className="icon cv-icon is-small">
|
||||||
<img src="/src/client/assets/img/cvlogo.svg" />
|
<img src="/dist/img/cvlogo.svg" />
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{isNil(rawFileDetails) && (
|
{isNil(rawFileDetails) && (
|
||||||
|
|||||||
@@ -20,31 +20,12 @@ const Navbar: React.FunctionComponent = (props) => {
|
|||||||
const socketDisconnectionReason = useSelector(
|
const socketDisconnectionReason = useSelector(
|
||||||
(state: RootState) => state.airdcpp.socketDisconnectionReason,
|
(state: RootState) => state.airdcpp.socketDisconnectionReason,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Import-related selector hooks
|
|
||||||
const successfulImportJobCount = useSelector(
|
|
||||||
(state: RootState) => state.fileOps.successfulJobCount,
|
|
||||||
);
|
|
||||||
const failedImportJobCount = useSelector(
|
|
||||||
(state: RootState) => state.fileOps.failedJobCount,
|
|
||||||
);
|
|
||||||
|
|
||||||
const lastQueueJob = useSelector(
|
|
||||||
(state: RootState) => state.fileOps.lastQueueJob,
|
|
||||||
);
|
|
||||||
const libraryQueueImportStatus = useSelector(
|
|
||||||
(state: RootState) => state.fileOps.LSQueueImportStatus,
|
|
||||||
);
|
|
||||||
|
|
||||||
const allImportJobResults = useSelector(
|
|
||||||
(state: RootState) => state.fileOps.importJobStatistics,
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<nav className="navbar is-fixed-top">
|
<nav className="navbar is-fixed-top">
|
||||||
<div className="navbar-brand">
|
<div className="navbar-brand">
|
||||||
<Link to="/" className="navbar-item">
|
<Link to="/" className="navbar-item">
|
||||||
<img
|
<img
|
||||||
src="/src/client/assets/img/threetwo.svg"
|
src="/img/threetwo.svg"
|
||||||
alt="ThreeTwo! A comic book curator"
|
alt="ThreeTwo! A comic book curator"
|
||||||
width="112"
|
width="112"
|
||||||
height="28"
|
height="28"
|
||||||
@@ -104,46 +85,12 @@ const Navbar: React.FunctionComponent = (props) => {
|
|||||||
{downloadProgressTick && <div className="pulsating-circle"></div>}
|
{downloadProgressTick && <div className="pulsating-circle"></div>}
|
||||||
</a>
|
</a>
|
||||||
{!isUndefined(downloadProgressTick) ? (
|
{!isUndefined(downloadProgressTick) ? (
|
||||||
<div className="navbar-dropdown is-right is-boxed">
|
<div className="navbar-dropdown is-right">
|
||||||
<a className="navbar-item">
|
<a className="navbar-item">
|
||||||
<DownloadProgressTick data={downloadProgressTick} />
|
<DownloadProgressTick data={downloadProgressTick} />
|
||||||
</a>
|
</a> </div>
|
||||||
</div>
|
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!isUndefined(libraryQueueImportStatus) &&
|
|
||||||
location.hash !== "#/import" ? (
|
|
||||||
<div className="navbar-item has-dropdown is-hoverable">
|
|
||||||
<a className="navbar-link is-arrowless">
|
|
||||||
<i className="fa-solid fa-file-import has-text-warning-dark"></i>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<div className="navbar-dropdown is-right is-boxed">
|
|
||||||
<a className="navbar-item">
|
|
||||||
<ul>
|
|
||||||
{successfulImportJobCount > 0 ? (
|
|
||||||
<li className="mb-2">
|
|
||||||
<span className="tag is-success mr-2">
|
|
||||||
{successfulImportJobCount}
|
|
||||||
</span>
|
|
||||||
imported.
|
|
||||||
</li>
|
|
||||||
) : null}
|
|
||||||
{failedImportJobCount > 0 ? (
|
|
||||||
<li>
|
|
||||||
<span className="tag is-danger mr-2">
|
|
||||||
{failedImportJobCount}
|
|
||||||
</span>
|
|
||||||
failed to import.
|
|
||||||
</li>
|
|
||||||
) : null}
|
|
||||||
</ul>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{/* AirDC++ socket connection status */}
|
{/* AirDC++ socket connection status */}
|
||||||
<div className="navbar-item has-dropdown is-hoverable">
|
<div className="navbar-item has-dropdown is-hoverable">
|
||||||
{airDCPPSocketConnectionStatus ? (
|
{airDCPPSocketConnectionStatus ? (
|
||||||
@@ -151,7 +98,7 @@ const Navbar: React.FunctionComponent = (props) => {
|
|||||||
<a className="navbar-link is-arrowless has-text-success">
|
<a className="navbar-link is-arrowless has-text-success">
|
||||||
<i className="fa-solid fa-bolt"></i>
|
<i className="fa-solid fa-bolt"></i>
|
||||||
</a>
|
</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 */}
|
{/* AirDC++ Session Information */}
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
@@ -186,7 +133,7 @@ const Navbar: React.FunctionComponent = (props) => {
|
|||||||
<a className="navbar-link is-arrowless has-text-danger">
|
<a className="navbar-link is-arrowless has-text-danger">
|
||||||
<i className="fa-solid fa-bolt"></i>
|
<i className="fa-solid fa-bolt"></i>
|
||||||
</a>
|
</a>
|
||||||
<div className="navbar-dropdown pr-2 pl-2 is-right is-boxed">
|
<div className="navbar-dropdown pr-2 pl-2 is-right">
|
||||||
<pre>
|
<pre>
|
||||||
{JSON.stringify(socketDisconnectionReason, null, 2)}
|
{JSON.stringify(socketDisconnectionReason, null, 2)}
|
||||||
</pre>
|
</pre>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export const PullList = (): ReactElement => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(
|
dispatch(
|
||||||
getWeeklyPullList({
|
getWeeklyPullList({
|
||||||
startDate: "2023-7-28",
|
startDate: "2022-11-15",
|
||||||
pageSize: "15",
|
pageSize: "15",
|
||||||
currentPage: "1",
|
currentPage: "1",
|
||||||
}),
|
}),
|
||||||
@@ -109,15 +109,15 @@ export const PullList = (): ReactElement => {
|
|||||||
{!isNil(pullListComics) && (
|
{!isNil(pullListComics) && (
|
||||||
<div>
|
<div>
|
||||||
<div className="library">
|
<div className="library">
|
||||||
<T2Table
|
<T2Table
|
||||||
sourceData={pullListComics}
|
sourceData={pullListComics}
|
||||||
columns={columnData}
|
columns={columnData}
|
||||||
totalPages={pullListComics.length}
|
totalPages={pullListComics.length}
|
||||||
paginationHandlers={{
|
paginationHandlers={{
|
||||||
nextPage: nextPageHandler,
|
nextPage: nextPageHandler,
|
||||||
previousPage: previousPageHandler,
|
previousPage: previousPageHandler,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback, ReactElement } from "react";
|
import React, { useMemo, useCallback, ReactElement } from "react";
|
||||||
import { isNil, isEmpty } from "lodash";
|
import { isNil, isEmpty } from "lodash";
|
||||||
import { IExtractedComicBookCoverFile, RootState } from "threetwo-ui-typings";
|
import { IExtractedComicBookCoverFile, RootState } from "threetwo-ui-typings";
|
||||||
import { importToDB } from "../actions/fileops.actions";
|
import { importToDB } from "../actions/fileops.actions";
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
import React, { ReactElement } from "react";
|
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { getServiceStatus } from "../../actions/fileops.actions";
|
|
||||||
|
|
||||||
export const ServiceStatuses = (): ReactElement => {
|
|
||||||
const serviceStatus = useSelector(
|
|
||||||
(state: RootState) => state.fileOps.libraryServiceStatus,
|
|
||||||
);
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
useEffect(() => {
|
|
||||||
dispatch(getServiceStatus());
|
|
||||||
}, []);
|
|
||||||
return (
|
|
||||||
<div className="is-clearfix">
|
|
||||||
<div className="mt-4">
|
|
||||||
<h3 className="title">Core Services</h3>
|
|
||||||
<h6 className="subtitle has-text-grey-light">
|
|
||||||
Statuses for core services
|
|
||||||
</h6>
|
|
||||||
</div>
|
|
||||||
<pre>{JSON.stringify(serviceStatus, null, 2)}</pre>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -2,7 +2,6 @@ import React, { useState, ReactElement } from "react";
|
|||||||
import { AirDCPPSettingsForm } from "./AirDCPPSettings/AirDCPPSettingsForm";
|
import { AirDCPPSettingsForm } from "./AirDCPPSettings/AirDCPPSettingsForm";
|
||||||
import { AirDCPPHubsForm } from "./AirDCPPSettings/AirDCPPHubsForm";
|
import { AirDCPPHubsForm } from "./AirDCPPSettings/AirDCPPHubsForm";
|
||||||
import { SystemSettingsForm } from "./SystemSettings/SystemSettingsForm";
|
import { SystemSettingsForm } from "./SystemSettings/SystemSettingsForm";
|
||||||
import { ServiceStatuses } from "./ServiceStatuses/ServiceStatuses";
|
|
||||||
import settingsObject from "../constants/settings/settingsMenu.json";
|
import settingsObject from "../constants/settings/settingsMenu.json";
|
||||||
import { isUndefined, map } from "lodash";
|
import { isUndefined, map } from "lodash";
|
||||||
|
|
||||||
@@ -23,10 +22,6 @@ export const Settings = (props: ISettingsProps): ReactElement => {
|
|||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: "core-service",
|
|
||||||
content: <ServiceStatuses />,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: "flushdb",
|
id: "flushdb",
|
||||||
content: (
|
content: (
|
||||||
|
|||||||
@@ -166,8 +166,8 @@ export const Volumes = (props): ReactElement => {
|
|||||||
<div>
|
<div>
|
||||||
<div className="library">
|
<div className="library">
|
||||||
<T2Table
|
<T2Table
|
||||||
sourceData={volumes?.hits}
|
sourceData={volumes?.hits?.hits}
|
||||||
totalPages={volumes.hits.length}
|
totalPages={volumes.hits.hits.length}
|
||||||
paginationHandlers={{
|
paginationHandlers={{
|
||||||
nextPage: () => {},
|
nextPage: () => {},
|
||||||
previousPage: () => {},
|
previousPage: () => {},
|
||||||
|
|||||||
@@ -73,8 +73,8 @@ export const WantedComics = (props): ReactElement => {
|
|||||||
id: "downloadDetails",
|
id: "downloadDetails",
|
||||||
accessorKey: "acquisition",
|
accessorKey: "acquisition",
|
||||||
cell: data => <ol>
|
cell: data => <ol>
|
||||||
{data.getValue().directconnect.downloads.map((download, idx) => {
|
{data.getValue().directconnect.downloads.map(download => {
|
||||||
return <li className="is-size-7" key={idx}>{download.name}</li>;
|
return <li className="is-size-7">{download.name}</li>;
|
||||||
})}
|
})}
|
||||||
</ol>
|
</ol>
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -57,12 +57,6 @@ export const MetadataPanel = (props: IMetadatPanelProps): ReactElement => {
|
|||||||
>
|
>
|
||||||
{rawFileDetails.extension}
|
{rawFileDetails.extension}
|
||||||
</span>
|
</span>
|
||||||
<span
|
|
||||||
className="tag is-success is-light has-text-weight-semibold"
|
|
||||||
style={props.tagsStyle}
|
|
||||||
>
|
|
||||||
{rawFileDetails.mimeType}
|
|
||||||
</span>
|
|
||||||
<span
|
<span
|
||||||
className="tag is-success is-light"
|
className="tag is-success is-light"
|
||||||
style={props.tagsStyle}
|
style={props.tagsStyle}
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ export const T2Table = (tableOptions): ReactElement => {
|
|||||||
pageIndex: 1,
|
pageIndex: 1,
|
||||||
pageSize: 15,
|
pageSize: 15,
|
||||||
});
|
});
|
||||||
|
console.log(sourceData)
|
||||||
|
|
||||||
|
|
||||||
const pagination = useMemo(
|
const pagination = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
@@ -79,12 +81,12 @@ export const T2Table = (tableOptions): ReactElement => {
|
|||||||
{/* <p>{totalPages} comics in all</p> */}
|
{/* <p>{totalPages} comics in all</p> */}
|
||||||
</div>
|
</div>
|
||||||
<div className="field has-addons">
|
<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 className="button" onClick={() => goToPreviousPage()}> <i className="fas fa-chevron-left"></i></div>
|
||||||
</div>
|
</p>
|
||||||
<div className="control">
|
<p className="control">
|
||||||
<div className="button" onClick={() => goToNextPage()}> <i className="fas fa-chevron-right"></i> </div>
|
<div className="button" onClick={() => goToNextPage()}> <i className="fas fa-chevron-right"></i> </div>
|
||||||
</div>
|
</p>
|
||||||
|
|
||||||
<div className="field has-addons ml-5">
|
<div className="field has-addons ml-5">
|
||||||
<p className="control">
|
<p className="control">
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export const IMS_RAW_IMPORT_FAILED = "IMS_RAW_IMPORT_FAILED";
|
|||||||
export const LS_IMPORT_CALL_IN_PROGRESS = "LS_IMPORT_CALL_IN_PROGRESS";
|
export const LS_IMPORT_CALL_IN_PROGRESS = "LS_IMPORT_CALL_IN_PROGRESS";
|
||||||
// Library import bull mq queue control
|
// Library import bull mq queue control
|
||||||
export const LS_TOGGLE_IMPORT_QUEUE = "LS_TOGGLE_IMPORT_QUEUE";
|
export const LS_TOGGLE_IMPORT_QUEUE = "LS_TOGGLE_IMPORT_QUEUE";
|
||||||
|
export const LS_QUEUE_DRAINED = "LS_QUEUE_DRAINED";
|
||||||
|
|
||||||
// ComicVine Metadata
|
// ComicVine Metadata
|
||||||
export const IMS_CV_METADATA_IMPORT_CALL_IN_PROGRESS =
|
export const IMS_CV_METADATA_IMPORT_CALL_IN_PROGRESS =
|
||||||
@@ -50,12 +51,11 @@ export const IMS_COMIC_BOOK_GROUPS_CALL_IN_PROGRESS =
|
|||||||
"IMS_COMIC_BOOK_GROUPS_CALL_IN_PROGRESS";
|
"IMS_COMIC_BOOK_GROUPS_CALL_IN_PROGRESS";
|
||||||
export const IMS_COMIC_BOOK_GROUPS_CALL_FAILED =
|
export const IMS_COMIC_BOOK_GROUPS_CALL_FAILED =
|
||||||
"IMS_COMIC_BOOK_GROUPS_CALL_FAILED";
|
"IMS_COMIC_BOOK_GROUPS_CALL_FAILED";
|
||||||
export const VOLUMES_FETCHED = "VOLUMES_FETCHED";
|
export const VOLUMES_FETCHED="VOLUMES_FETCHED";
|
||||||
|
|
||||||
// search results from the Search service
|
// search results from the Search service
|
||||||
export const SS_SEARCH_RESULTS_FETCHED = "SS_SEARCH_RESULTS_FETCHED";
|
export const SS_SEARCH_RESULTS_FETCHED = "SS_SEARCH_RESULTS_FETCHED";
|
||||||
export const SS_SEARCH_RESULTS_FETCHED_SPECIAL =
|
export const SS_SEARCH_RESULTS_FETCHED_SPECIAL = "SS_SEARCH_RESULTS_FETCHED_SPECIAL";
|
||||||
"SS_SEARCH_RESULTS_FETCHED_SPECIAL";
|
|
||||||
export const SS_SEARCH_IN_PROGRESS = "SS_SEARCH_IN_PROGRESS";
|
export const SS_SEARCH_IN_PROGRESS = "SS_SEARCH_IN_PROGRESS";
|
||||||
export const SS_SEARCH_FAILED = "SS_SEARCH_FAILED";
|
export const SS_SEARCH_FAILED = "SS_SEARCH_FAILED";
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@ export const AIRDCPP_SEARCH_RESULTS_UPDATED = "AIRDCPP_SEARCH_RESULTS_UPDATED";
|
|||||||
export const AIRDCPP_SEARCH_COMPLETE = "AIRDCPP_SEARCH_COMPLETE";
|
export const AIRDCPP_SEARCH_COMPLETE = "AIRDCPP_SEARCH_COMPLETE";
|
||||||
|
|
||||||
// AirDC++ related library query for issues with bundles associated with them
|
// AirDC++ related library query for issues with bundles associated with them
|
||||||
export const LIBRARY_ISSUE_BUNDLES = "LIBRARY_ISSUE_BUNDLES";
|
export const LIBRARY_ISSUE_BUNDLES = "LIBRARY_ISSUE_BUNDLES";
|
||||||
|
|
||||||
export const AIRDCPP_HUB_SEARCHES_SENT = "AIRDCPP_HUB_SEARCHES_SENT";
|
export const AIRDCPP_HUB_SEARCHES_SENT = "AIRDCPP_HUB_SEARCHES_SENT";
|
||||||
export const AIRDCPP_RESULT_DOWNLOAD_INITIATED =
|
export const AIRDCPP_RESULT_DOWNLOAD_INITIATED =
|
||||||
@@ -124,17 +124,10 @@ export const AIRDCPP_TRANSFERS_FETCHED = "AIRDCPP_TRANSFERS_FETCHED";
|
|||||||
// Comics marked as "wanted"
|
// Comics marked as "wanted"
|
||||||
export const WANTED_COMICS_FETCHED = "WANTED_COMICS_FETCHED";
|
export const WANTED_COMICS_FETCHED = "WANTED_COMICS_FETCHED";
|
||||||
|
|
||||||
// LIBRARY Service import queue-related action types
|
// LIBRARY SOCKET ENDPOINT
|
||||||
export const LS_IMPORT = "LS_IMPORT";
|
export const LS_IMPORT = "LS_IMPORT";
|
||||||
export const LS_COVER_EXTRACTED = "LS_COVER_EXTRACTED";
|
export const LS_COVER_EXTRACTED = "LS_COVER_EXTRACTED";
|
||||||
export const LS_COVER_EXTRACTION_FAILED = "LS_COVER_EXTRACTION_FAILED";
|
|
||||||
export const LS_COMIC_ADDED = "LS_COMIC_ADDED";
|
export const LS_COMIC_ADDED = "LS_COMIC_ADDED";
|
||||||
export const LS_IMPORT_QUEUE_DRAINED = "LS_IMPORT_QUEUE_DRAINED";
|
|
||||||
export const LS_SET_QUEUE_STATUS = "LS_SET_QUEUE_STATUS";
|
|
||||||
export const RESTORE_JOB_COUNTS_AFTER_SESSION_RESTORATION =
|
|
||||||
"RESTORE_JOB_COUNTS_AFTER_SESSION_RESTORATION";
|
|
||||||
export const LS_IMPORT_JOB_STATISTICS_FETCHED =
|
|
||||||
"LS_IMPORT_JOB_STATISTICS_FETCHED";
|
|
||||||
|
|
||||||
// Settings
|
// Settings
|
||||||
export const SETTINGS_CALL_IN_PROGRESS = "SETTINGS_CALL_IN_PROGRESS";
|
export const SETTINGS_CALL_IN_PROGRESS = "SETTINGS_CALL_IN_PROGRESS";
|
||||||
@@ -146,7 +139,4 @@ export const SETTINGS_DB_FLUSH_SUCCESS = "SETTINGS_DB_FLUSH_SUCCESS";
|
|||||||
// Metron Metadata
|
// Metron Metadata
|
||||||
export const METRON_DATA_FETCH_SUCCESS = "METRON_DATA_FETCH_SUCCESS";
|
export const METRON_DATA_FETCH_SUCCESS = "METRON_DATA_FETCH_SUCCESS";
|
||||||
export const METRON_DATA_FETCH_IN_PROGRESS = "METRON_DATA_FETCH_IN_PROGRESS";
|
export const METRON_DATA_FETCH_IN_PROGRESS = "METRON_DATA_FETCH_IN_PROGRESS";
|
||||||
export const METRON_DATA_FETCH_ERROR = "METRON_DATA_FETCH_ERROR";
|
export const METRON_DATA_FETCH_ERROR = "METRON_DATA_FETCH_ERROR";
|
||||||
|
|
||||||
// service health statuses
|
|
||||||
export const LIBRARY_SERVICE_HEALTH = "LIBRARY_SERVICE_HEALTH";
|
|
||||||
@@ -8,79 +8,71 @@ export const hostURIBuilder = (options: Record<string, string>): string => {
|
|||||||
options.apiPath
|
options.apiPath
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
console.log(import.meta);
|
|
||||||
|
|
||||||
export const CORS_PROXY_SERVER_URI = hostURIBuilder({
|
export const CORS_PROXY_SERVER_URI = hostURIBuilder({
|
||||||
protocol: "http",
|
protocol: "http",
|
||||||
host: import.meta.env.VITE_UNDERLYING_HOSTNAME || "localhost",
|
host: process.env.UNDERLYING_HOSTNAME || "localhost",
|
||||||
port: "8050",
|
port: "8050",
|
||||||
apiPath: "/",
|
apiPath: "/",
|
||||||
});
|
});
|
||||||
|
|
||||||
export const COMICVINE_SERVICE_URI = hostURIBuilder({
|
export const COMICVINE_SERVICE_URI = hostURIBuilder({
|
||||||
protocol: "http",
|
protocol: "http",
|
||||||
host: import.meta.env.VITE_UNDERLYING_HOSTNAME || "localhost",
|
host: process.env.UNDERLYING_HOSTNAME || "localhost",
|
||||||
port: "3080",
|
port: "3080",
|
||||||
apiPath: "/api/comicvine",
|
apiPath: "/api/comicvine",
|
||||||
});
|
});
|
||||||
|
|
||||||
export const METRON_SERVICE_URI = hostURIBuilder({
|
export const METRON_SERVICE_URI = hostURIBuilder({
|
||||||
protocol: "http",
|
protocol: "http",
|
||||||
host: import.meta.env.VITE_UNDERLYING_HOSTNAME || "localhost",
|
host: process.env.UNDERLYING_HOSTNAME || "localhost",
|
||||||
port: "3080",
|
port: "3080",
|
||||||
apiPath: "/api/metron",
|
apiPath: "/api/metron",
|
||||||
});
|
});
|
||||||
|
|
||||||
export const API_BASE_URI = hostURIBuilder({
|
export const API_BASE_URI = hostURIBuilder({
|
||||||
protocol: "http",
|
protocol: "http",
|
||||||
host: import.meta.env.VITE_UNDERLYING_HOSTNAME || "localhost",
|
host: process.env.UNDERLYING_HOSTNAME || "localhost",
|
||||||
port: "8050",
|
port: "8050",
|
||||||
apiPath: "/api",
|
apiPath: "/api",
|
||||||
});
|
});
|
||||||
|
|
||||||
export const LIBRARY_SERVICE_HOST = hostURIBuilder({
|
export const LIBRARY_SERVICE_HOST = hostURIBuilder({
|
||||||
protocol: "http",
|
protocol: "http",
|
||||||
host: import.meta.env.VITE_UNDERLYING_HOSTNAME || "localhost",
|
host: process.env.UNDERLYING_HOSTNAME || "localhost",
|
||||||
port: "3000",
|
port: "3000",
|
||||||
apiPath: ``,
|
apiPath: ``,
|
||||||
});
|
});
|
||||||
export const LIBRARY_SERVICE_BASE_URI = hostURIBuilder({
|
export const LIBRARY_SERVICE_BASE_URI = hostURIBuilder({
|
||||||
protocol: "http",
|
protocol: "http",
|
||||||
host: import.meta.env.VITE_UNDERLYING_HOSTNAME || "localhost",
|
host: process.env.UNDERLYING_HOSTNAME || "localhost",
|
||||||
port: "3000",
|
port: "3000",
|
||||||
apiPath: "/api/library",
|
apiPath: "/api/library",
|
||||||
});
|
});
|
||||||
export const SEARCH_SERVICE_BASE_URI = hostURIBuilder({
|
export const SEARCH_SERVICE_BASE_URI = hostURIBuilder({
|
||||||
protocol: "http",
|
protocol: "http",
|
||||||
host: import.meta.env.VITE_UNDERLYING_HOSTNAME || "localhost",
|
host: process.env.UNDERLYING_HOSTNAME || "localhost",
|
||||||
port: "3000",
|
port: "3000",
|
||||||
apiPath: "/api/search",
|
apiPath: "/api/search",
|
||||||
});
|
});
|
||||||
|
|
||||||
export const SETTINGS_SERVICE_BASE_URI = hostURIBuilder({
|
export const SETTINGS_SERVICE_BASE_URI = hostURIBuilder({
|
||||||
protocol: "http",
|
protocol: "http",
|
||||||
host: import.meta.env.VITE_UNDERLYING_HOSTNAME || "localhost",
|
host: process.env.UNDERLYING_HOSTNAME || "localhost",
|
||||||
port: "3000",
|
port: "3000",
|
||||||
apiPath: "/api/settings",
|
apiPath: "/api/settings",
|
||||||
});
|
});
|
||||||
|
|
||||||
export const IMAGETRANSFORMATION_SERVICE_BASE_URI = hostURIBuilder({
|
export const IMAGETRANSFORMATION_SERVICE_BASE_URI = hostURIBuilder({
|
||||||
protocol: "http",
|
protocol: "http",
|
||||||
host: import.meta.env.VITE_UNDERLYING_HOSTNAME || "localhost",
|
host: process.env.UNDERLYING_HOSTNAME || "localhost",
|
||||||
port: "3000",
|
port: "3000",
|
||||||
apiPath: "/api/imagetransformation",
|
apiPath: "/api/imagetransformation",
|
||||||
});
|
});
|
||||||
|
|
||||||
export const SOCKET_BASE_URI = hostURIBuilder({
|
export const SOCKET_BASE_URI = hostURIBuilder({
|
||||||
protocol: "ws",
|
protocol: "ws",
|
||||||
host: import.meta.env.VITE_UNDERLYING_HOSTNAME || "localhost",
|
host: process.env.UNDERLYING_HOSTNAME || "localhost",
|
||||||
port: "3001",
|
port: "3001",
|
||||||
apiPath: `/`,
|
apiPath: `/`,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const JOB_QUEUE_SERVICE_BASE_URI = hostURIBuilder({
|
|
||||||
protocol: "http",
|
|
||||||
host: import.meta.env.UNDERLYING_HOSTNAME || "localhost",
|
|
||||||
port: "3000",
|
|
||||||
apiPath: `/api/jobqueue`,
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -57,21 +57,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 4,
|
"id": 4,
|
||||||
"category": "statuses",
|
|
||||||
"displayName": "Service Status",
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"id": "core-service",
|
|
||||||
"displayName": "Core Services"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "metadata-service",
|
|
||||||
"displayName": "Metadata Services"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 5,
|
|
||||||
"category": "system",
|
"category": "system",
|
||||||
"displayName": "System",
|
"displayName": "System",
|
||||||
"children": [
|
"children": [
|
||||||
@@ -82,7 +67,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 6,
|
"id": 5,
|
||||||
"category": "acknowledgments",
|
"category": "acknowledgments",
|
||||||
"displayName": "Acknowledgments",
|
"displayName": "Acknowledgments",
|
||||||
"children": [
|
"children": [
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
import React, { createContext } from "react";
|
|
||||||
|
|
||||||
export const SocketIOContext = createContext({});
|
|
||||||
|
|
||||||
export const SocketIOProvider = ({ children, socket }) => {
|
|
||||||
return (
|
|
||||||
<SocketIOContext.Provider value={socket}>
|
|
||||||
{children}
|
|
||||||
</SocketIOContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -15,9 +15,10 @@ import {
|
|||||||
IMS_COMIC_BOOK_GROUPS_FETCHED,
|
IMS_COMIC_BOOK_GROUPS_FETCHED,
|
||||||
IMS_COMIC_BOOK_GROUPS_CALL_FAILED,
|
IMS_COMIC_BOOK_GROUPS_CALL_FAILED,
|
||||||
IMS_COMIC_BOOK_ARCHIVE_EXTRACTION_CALL_IN_PROGRESS,
|
IMS_COMIC_BOOK_ARCHIVE_EXTRACTION_CALL_IN_PROGRESS,
|
||||||
|
IMS_COMIC_BOOK_ARCHIVE_EXTRACTION_SUCCESS,
|
||||||
LS_IMPORT,
|
LS_IMPORT,
|
||||||
LS_COVER_EXTRACTED,
|
LS_COVER_EXTRACTED,
|
||||||
LS_COVER_EXTRACTION_FAILED,
|
LS_QUEUE_DRAINED,
|
||||||
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,
|
||||||
@@ -29,11 +30,6 @@ import {
|
|||||||
SS_SEARCH_RESULTS_FETCHED_SPECIAL,
|
SS_SEARCH_RESULTS_FETCHED_SPECIAL,
|
||||||
VOLUMES_FETCHED,
|
VOLUMES_FETCHED,
|
||||||
COMICBOOK_EXTRACTION_SUCCESS,
|
COMICBOOK_EXTRACTION_SUCCESS,
|
||||||
LIBRARY_SERVICE_HEALTH,
|
|
||||||
LS_IMPORT_QUEUE_DRAINED,
|
|
||||||
LS_SET_QUEUE_STATUS,
|
|
||||||
RESTORE_JOB_COUNTS_AFTER_SESSION_RESTORATION,
|
|
||||||
LS_IMPORT_JOB_STATISTICS_FETCHED,
|
|
||||||
} from "../constants/action-types";
|
} from "../constants/action-types";
|
||||||
import { removeLeadingPeriod } from "../shared/utils/formatting.utils";
|
import { removeLeadingPeriod } from "../shared/utils/formatting.utils";
|
||||||
import { LIBRARY_SERVICE_HOST } from "../constants/endpoints";
|
import { LIBRARY_SERVICE_HOST } from "../constants/endpoints";
|
||||||
@@ -44,7 +40,6 @@ const initialState = {
|
|||||||
SSCallInProgress: false,
|
SSCallInProgress: false,
|
||||||
imageAnalysisResults: {},
|
imageAnalysisResults: {},
|
||||||
comicBookExtractionInProgress: false,
|
comicBookExtractionInProgress: false,
|
||||||
LSQueueImportStatus: undefined,
|
|
||||||
comicBookMetadata: [],
|
comicBookMetadata: [],
|
||||||
comicVolumeGroups: [],
|
comicVolumeGroups: [],
|
||||||
isSocketConnected: false,
|
isSocketConnected: false,
|
||||||
@@ -60,14 +55,11 @@ const initialState = {
|
|||||||
libraryComics: [],
|
libraryComics: [],
|
||||||
volumes: [],
|
volumes: [],
|
||||||
librarySearchResultsFormatted: [],
|
librarySearchResultsFormatted: [],
|
||||||
lastQueueJob: "",
|
librarySearchResultCount: 0,
|
||||||
successfulJobCount: 0,
|
|
||||||
failedJobCount: 0,
|
|
||||||
importJobStatistics: [],
|
|
||||||
libraryQueueResults: [],
|
libraryQueueResults: [],
|
||||||
librarySearchError: {},
|
librarySearchError: {},
|
||||||
libraryServiceStatus: {},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function fileOpsReducer(state = initialState, action) {
|
function fileOpsReducer(state = initialState, action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case IMS_COMICBOOK_METADATA_FETCHED:
|
case IMS_COMICBOOK_METADATA_FETCHED:
|
||||||
@@ -157,58 +149,17 @@ function fileOpsReducer(state = initialState, action) {
|
|||||||
case LS_IMPORT: {
|
case LS_IMPORT: {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
LSQueueImportStatus: "running",
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case LS_COVER_EXTRACTED: {
|
case LS_COVER_EXTRACTED: {
|
||||||
if (state.recentComics.length === 5) {
|
console.log("BASH", action);
|
||||||
|
if(state.recentComics.length === 5) {
|
||||||
state.recentComics.pop();
|
state.recentComics.pop();
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
successfulJobCount: action.completedJobCount,
|
librarySearchResultCount: state.librarySearchResultCount + 1,
|
||||||
lastQueueJob: action.importResult.rawFileDetails.name,
|
recentComics: [...state.recentComics, action.result.data.importResult]
|
||||||
recentComics: [...state.recentComics, action.importResult],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
case LS_COVER_EXTRACTION_FAILED: {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
failedJobCount: action.failedJobCount,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
case LS_IMPORT_QUEUE_DRAINED: {
|
|
||||||
localStorage.removeItem("sessionId");
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
LSQueueImportStatus: "drained",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
case RESTORE_JOB_COUNTS_AFTER_SESSION_RESTORATION: {
|
|
||||||
console.log("Restoring state for an active import in progress...");
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
successfulJobCount: action.completedJobCount,
|
|
||||||
failedJobCount: action.failedJobCount,
|
|
||||||
LSQueueImportStatus: action.queueStatus,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
case LS_SET_QUEUE_STATUS: {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
LSQueueImportStatus: action.data.queueStatus,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
case LS_IMPORT_JOB_STATISTICS_FETCHED: {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
importJobStatistics: action.data,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,8 +191,14 @@ function fileOpsReducer(state = initialState, action) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case LS_QUEUE_DRAINED: {
|
||||||
|
console.log("drained", action);
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
};
|
||||||
|
}
|
||||||
case LS_COMIC_ADDED: {
|
case LS_COMIC_ADDED: {
|
||||||
|
console.log("ADDED na anna", action);
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
};
|
};
|
||||||
@@ -276,7 +233,7 @@ function fileOpsReducer(state = initialState, action) {
|
|||||||
case SS_SEARCH_RESULTS_FETCHED_SPECIAL: {
|
case SS_SEARCH_RESULTS_FETCHED_SPECIAL: {
|
||||||
const foo = [];
|
const foo = [];
|
||||||
if (!isUndefined(action.data.hits)) {
|
if (!isUndefined(action.data.hits)) {
|
||||||
map(action.data.hits, ({ _source }) => {
|
map(action.data.hits.hits, ({ _source }) => {
|
||||||
foo.push(_source);
|
foo.push(_source);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -289,7 +246,7 @@ function fileOpsReducer(state = initialState, action) {
|
|||||||
case WANTED_COMICS_FETCHED: {
|
case WANTED_COMICS_FETCHED: {
|
||||||
const foo = [];
|
const foo = [];
|
||||||
if (!isUndefined(action.data.hits)) {
|
if (!isUndefined(action.data.hits)) {
|
||||||
map(action.data.hits, ({ _source }) => {
|
map(action.data.hits.hits, ({ _source }) => {
|
||||||
foo.push(_source);
|
foo.push(_source);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -314,12 +271,7 @@ function fileOpsReducer(state = initialState, action) {
|
|||||||
SSCallInProgress: false,
|
SSCallInProgress: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case LIBRARY_SERVICE_HEALTH: {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
libraryServiceStatus: action.status,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case FILEOPS_STATE_RESET: {
|
case FILEOPS_STATE_RESET: {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@@ -331,4 +283,4 @@ function fileOpsReducer(state = initialState, action) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default fileOpsReducer;
|
export default fileOpsReducer;
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
const socketIOMiddleware = (socket) => {
|
|
||||||
return (store) => (next) => (action) => {
|
|
||||||
if (action.type === "EMIT_SOCKET_EVENT") {
|
|
||||||
const { event, data } = action.payload;
|
|
||||||
socket.emit(event, data);
|
|
||||||
}
|
|
||||||
return next(action);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default socketIOMiddleware;
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import io from "socket.io-client";
|
|
||||||
import { SOCKET_BASE_URI } from "../../constants/endpoints";
|
|
||||||
const sessionId = localStorage.getItem("sessionId");
|
|
||||||
const socketIOConnectionInstance = io(SOCKET_BASE_URI, {
|
|
||||||
transports: ["websocket"],
|
|
||||||
withCredentials: true,
|
|
||||||
query: { sessionId },
|
|
||||||
});
|
|
||||||
|
|
||||||
export default socketIOConnectionInstance;
|
|
||||||
@@ -4,11 +4,11 @@ import { composeWithDevTools } from "@redux-devtools/extension";
|
|||||||
import thunk from "redux-thunk";
|
import thunk from "redux-thunk";
|
||||||
import { createReduxHistoryContext } from "redux-first-history";
|
import { createReduxHistoryContext } from "redux-first-history";
|
||||||
import { reducers } from "../reducers/index";
|
import { reducers } from "../reducers/index";
|
||||||
import socketIoMiddleware from "redux-socket.io-middleware";
|
|
||||||
import socketIOMiddleware from "../shared/middleware/SocketIOMiddleware";
|
|
||||||
import socketIOConnectionInstance from "../shared/socket.io/instance";
|
|
||||||
|
|
||||||
const customSocketIOMiddleware = socketIOMiddleware(socketIOConnectionInstance);
|
import { io } from "socket.io-client";
|
||||||
|
import socketIoMiddleware from "redux-socket.io-middleware";
|
||||||
|
import { SOCKET_BASE_URI } from "../constants/endpoints";
|
||||||
|
const socketConnection = io(SOCKET_BASE_URI, { transports: ["websocket"] });
|
||||||
|
|
||||||
const { createReduxHistory, routerMiddleware, routerReducer } =
|
const { createReduxHistory, routerMiddleware, routerReducer } =
|
||||||
createReduxHistoryContext({
|
createReduxHistoryContext({
|
||||||
@@ -22,8 +22,7 @@ export const store = createStore(
|
|||||||
}),
|
}),
|
||||||
composeWithDevTools(
|
composeWithDevTools(
|
||||||
applyMiddleware(
|
applyMiddleware(
|
||||||
socketIoMiddleware(socketIOConnectionInstance),
|
socketIoMiddleware(socketConnection),
|
||||||
customSocketIOMiddleware,
|
|
||||||
thunk,
|
thunk,
|
||||||
routerMiddleware,
|
routerMiddleware,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -30,20 +30,20 @@ RawFileDetails.args = {
|
|||||||
},
|
},
|
||||||
"inferredMetadata": {
|
"inferredMetadata": {
|
||||||
"issue": {
|
"issue": {
|
||||||
"name": "Batman Annual 02",
|
"name": "Fantastic Four 143",
|
||||||
"number": 197402,
|
"number": 197402,
|
||||||
"year": "1974",
|
"year": "1974",
|
||||||
"subtitle": ""
|
"subtitle": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rawFileDetails": {
|
"rawFileDetails": {
|
||||||
"name": "Batman Annual 02",
|
"name": "197402 Fantastic Four 143",
|
||||||
"filePath": "/Users/rishi/work/threetwo-core-service/comics/197402 Fantastic Four 143.cbz",
|
"filePath": "/Users/rishi/work/threetwo-core-service/comics/197402 Fantastic Four 143.cbz",
|
||||||
"fileSize": 10341074,
|
"fileSize": 10341074,
|
||||||
"extension": ".cbz",
|
"extension": ".cbz",
|
||||||
"containedIn": "./userdata/covers/197402 Fantastic Four 143",
|
"containedIn": "./userdata/covers/197402 Fantastic Four 143",
|
||||||
"cover": {
|
"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": [] } },
|
"sourcedMetadata": { "comicInfo": null, "comicvine": { "aliases": [] } },
|
||||||
|
|||||||
@@ -30,12 +30,12 @@ app.use(bodyParser.urlencoded({ extended: true }));
|
|||||||
// REGISTER ROUTES
|
// REGISTER ROUTES
|
||||||
// all of the routes will be prefixed with /api
|
// all of the routes will be prefixed with /api
|
||||||
const routes: Router[] = Object.values(router);
|
const routes: Router[] = Object.values(router);
|
||||||
// app.use("/api", routes);
|
app.use("/api", routes);
|
||||||
|
|
||||||
// Send index.html on root request
|
// Send index.html on root request
|
||||||
// app.use(express.static("dist"));
|
app.use(express.static("dist"));
|
||||||
// app.use(express.static("public"));
|
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}`);
|
||||||
|
|||||||
@@ -2,21 +2,19 @@
|
|||||||
"extends": "@tsconfig/node14/tsconfig.json",
|
"extends": "@tsconfig/node14/tsconfig.json",
|
||||||
"$schema": "https://json.schemastore.org/tsconfig",
|
"$schema": "https://json.schemastore.org/tsconfig",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ESNext",
|
|
||||||
"useDefineForClassFields": true,
|
|
||||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
|
||||||
"allowJs": false,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"esModuleInterop": false,
|
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"strict": true,
|
"noImplicitAny": false,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"target": "es2019",
|
||||||
"module": "ESNext",
|
"jsx": "react",
|
||||||
"moduleResolution": "Node",
|
"module": "commonjs",
|
||||||
|
"sourceMap": true,
|
||||||
|
"outDir": "./dist/",
|
||||||
|
"skipLibCheck": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"lib": ["dom", "dom.iterable", "esnext", "webworker"],
|
||||||
"noEmit": true,
|
"baseUrl": ".",
|
||||||
"jsx": "react-jsx"
|
"esModuleInterop": true,
|
||||||
|
"strict": true
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"eslint.workingDirectories": [
|
"eslint.workingDirectories": [
|
||||||
|
|||||||
@@ -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
128
webpack.config.js
Normal 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),
|
||||||
|
};
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user