🛻 Upgraded deps, fixed issues

This commit is contained in:
2026-02-25 16:57:46 -05:00
parent e113066094
commit 4498830e29
19 changed files with 9487 additions and 18550 deletions

17814
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -18,124 +18,128 @@
"author": "Rishi Ghan",
"license": "MIT",
"dependencies": {
"@dnd-kit/core": "^6.0.8",
"@dnd-kit/sortable": "^7.0.2",
"@dnd-kit/utilities": "^3.2.1",
"@floating-ui/react": "^0.26.12",
"@floating-ui/react-dom": "^2.0.8",
"@fortawesome/fontawesome-free": "^6.3.0",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@floating-ui/react": "^0.27.18",
"@floating-ui/react-dom": "^2.1.7",
"@fortawesome/fontawesome-free": "^7.2.0",
"@popperjs/core": "^2.11.8",
"@rollup/plugin-node-resolve": "^15.0.1",
"@tanstack/react-query": "^5.0.5",
"@tanstack/react-table": "^8.9.3",
"@types/mime-types": "^2.1.0",
"@rollup/plugin-node-resolve": "^16.0.3",
"@tanstack/react-query": "^5.90.21",
"@tanstack/react-table": "^8.21.3",
"@types/mime-types": "^3.0.1",
"@types/react-router-dom": "^5.3.3",
"@vitejs/plugin-react": "^4.2.1",
"airdcpp-apisocket": "^2.5.0-beta.2",
"axios": "^1.12.0",
"axios-cache-interceptor": "^1.11.1",
"axios-rate-limit": "^1.3.0",
"@vitejs/plugin-react": "^5.1.4",
"airdcpp-apisocket": "^3.0.0-beta.14",
"axios": "^1.13.5",
"axios-cache-interceptor": "^1.11.4",
"axios-rate-limit": "^1.6.2",
"babel-plugin-styled-components": "^2.1.4",
"date-fns": "^2.28.0",
"dayjs": "^1.10.6",
"ellipsize": "^0.5.1",
"date-fns": "^4.1.0",
"dayjs": "^1.11.19",
"ellipsize": "^0.7.0",
"embla-carousel-react": "^8.6.0",
"express": "^4.20.0",
"filename-parser": "^1.0.2",
"final-form": "^4.20.2",
"final-form-arrays": "^3.0.2",
"focus-trap-react": "^10.2.3",
"express": "^5.2.1",
"filename-parser": "^1.0.4",
"final-form": "^5.0.0",
"final-form-arrays": "^4.0.0",
"focus-trap-react": "^12.0.0",
"history": "^5.3.0",
"html-to-text": "^8.1.0",
"i18next": "^23.11.1",
"i18next-browser-languagedetector": "^7.2.1",
"i18next-http-backend": "^2.5.0",
"immer": "^10.0.3",
"jsdoc": "^3.6.10",
"html-to-text": "^9.0.5",
"i18next": "^25.8.13",
"i18next-browser-languagedetector": "^8.2.1",
"i18next-http-backend": "^3.0.2",
"immer": "^11.1.4",
"jsdoc": "^4.0.5",
"lodash": "^4.17.23",
"pretty-bytes": "^5.6.0",
"pretty-bytes": "^7.1.0",
"prop-types": "^15.8.1",
"qs": "^6.14.1",
"react": "^18.3.1",
"react-collapsible": "^2.9.0",
"react-comic-viewer": "^0.4.0",
"react-day-picker": "^8.10.0",
"react-dom": "^18.2.0",
"react-fast-compare": "^3.2.0",
"react-final-form": "^6.5.9",
"react-final-form-arrays": "^3.1.4",
"react-i18next": "^14.1.0",
"react-loader-spinner": "^4.0.0",
"react-modal": "^3.15.1",
"react-router": "^7.12.0",
"react-router-dom": "^6.9.0",
"react-select": "^5.8.0",
"react-select-async-paginate": "^0.7.2",
"react-sliding-pane": "^7.1.0",
"react-textarea-autosize": "^8.3.4",
"react-toastify": "^10.0.5",
"socket.io-client": "^4.3.2",
"styled-components": "^6.1.0",
"qs": "^6.15.0",
"react": "^19.2.4",
"react-collapsible": "^2.10.0",
"react-comic-viewer": "^0.5.1",
"react-day-picker": "^9.13.2",
"react-dom": "^19.2.4",
"react-fast-compare": "^3.2.2",
"react-final-form": "^7.0.0",
"react-final-form-arrays": "^4.0.0",
"react-i18next": "^16.5.4",
"react-loader-spinner": "^8.0.2",
"react-modal": "^3.16.3",
"react-router": "^7.13.1",
"react-router-dom": "^7.13.1",
"react-select": "^5.10.2",
"react-select-async-paginate": "^0.7.11",
"react-sliding-pane": "^7.3.0",
"react-textarea-autosize": "^8.5.9",
"react-toastify": "^11.0.5",
"socket.io-client": "^4.8.3",
"styled-components": "^6.3.11",
"threetwo-ui-typings": "^1.0.14",
"vite": "^5.4.21",
"vite-plugin-html": "^3.2.0",
"websocket": "^1.0.34",
"zustand": "^4.4.6"
"vite": "^7.3.1",
"vite-plugin-html": "^3.2.2",
"websocket": "^1.0.35",
"zustand": "^5.0.11"
},
"devDependencies": {
"@iconify-json/solar": "^1.1.8",
"@iconify/tailwind": "^0.1.4",
"@storybook/addon-essentials": "^7.4.1",
"@storybook/addon-interactions": "^7.4.1",
"@storybook/addon-links": "^7.4.1",
"@storybook/addon-onboarding": "^1.0.8",
"@storybook/blocks": "^7.4.1",
"@storybook/react": "^7.4.1",
"@storybook/react-vite": "^7.4.1",
"@storybook/testing-library": "^0.2.0",
"@tanstack/eslint-plugin-query": "^5.0.5",
"@tanstack/react-query-devtools": "^5.1.0",
"@iconify-json/solar": "^1.2.5",
"@iconify/json": "^2.2.443",
"@iconify/tailwind": "^1.2.0",
"@iconify/tailwind4": "^1.2.1",
"@iconify/utils": "^3.1.0",
"@storybook/addon-essentials": "^8.6.17",
"@storybook/addon-interactions": "^8.6.17",
"@storybook/addon-links": "^8.6.17",
"@storybook/addon-onboarding": "^8.6.17",
"@storybook/blocks": "^8.6.17",
"@storybook/react": "^8.6.17",
"@storybook/react-vite": "^8.6.17",
"@storybook/testing-library": "^0.2.2",
"@tailwindcss/postcss": "^4.2.1",
"@tanstack/eslint-plugin-query": "^5.91.4",
"@tanstack/react-query-devtools": "^5.91.3",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1",
"@tsconfig/node14": "^1.0.0",
"@types/ellipsize": "^0.1.1",
"@types/express": "^4.17.8",
"@types/jest": "^26.0.20",
"@types/lodash": "^4.14.168",
"@types/node": "^14.14.34",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@types/react-redux": "^7.1.25",
"autoprefixer": "^10.4.16",
"body-parser": "^1.19.0",
"@tsconfig/node14": "^14.1.8",
"@types/ellipsize": "^0.1.3",
"@types/express": "^5.0.6",
"@types/jest": "^30.0.0",
"@types/lodash": "^4.17.24",
"@types/node": "^25.3.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@types/react-redux": "^7.1.34",
"autoprefixer": "^10.4.27",
"body-parser": "^2.2.2",
"docdash": "^2.0.2",
"eslint": "^8.49.0",
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-css-modules": "^2.11.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jsdoc": "^46.6.0",
"eslint-plugin-jsx-a11y": "^6.0.3",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-react": "^7.22.0",
"eslint-plugin-storybook": "^0.6.13",
"express": "^4.20.0",
"eslint": "^10.0.2",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-css-modules": "^2.12.0",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-jsdoc": "^62.7.1",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-prettier": "^5.5.5",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-storybook": "^0.11.1",
"express": "^5.2.1",
"identity-obj-proxy": "^3.0.0",
"install": "^0.13.0",
"jest": "^29.6.3",
"jest": "^30.2.0",
"jest-environment-jsdom": "^30.2.0",
"nodemon": "^3.0.1",
"postcss": "^8.4.32",
"postcss-import": "^15.1.0",
"prettier": "^2.2.1",
"react-refresh": "^0.14.0",
"rimraf": "^4.1.3",
"sass": "^1.77.0",
"storybook": "^7.6.21",
"tailwindcss": "^3.4.1",
"nodemon": "^3.1.14",
"postcss": "^8.5.6",
"postcss-import": "^16.1.1",
"prettier": "^3.8.1",
"react-refresh": "^0.18.0",
"rimraf": "^6.1.3",
"sass": "^1.97.3",
"storybook": "^8.6.17",
"tailwindcss": "^4.2.1",
"ts-jest": "^29.4.6",
"tui-jsdoc-template": "^1.2.2",
"typescript": "^5.1.6"
"typescript": "^5.9.3"
},
"resolutions": {
"jackspeak": "2.1.1"

View File

@@ -1,7 +1,7 @@
module.exports = {
plugins: {
"postcss-import": {},
tailwindcss: {},
"@tailwindcss/postcss": {},
autoprefixer: {},
},
};

View File

@@ -1,15 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
@font-face {
font-family: "PP Object Sans Regular";
src: url("/fonts/PPObjectSans-Regular.otf") format("opentype");
}
@font-face {
font-family: "Hasklig Regular";
src: url("/fonts/Hasklig-Regular.otf") format("opentype");
}
}

View File

@@ -2,7 +2,7 @@ import React, { ReactElement, useEffect } from "react";
import { Outlet } from "react-router-dom";
import { Navbar2 } from "./shared/Navbar2";
import { ToastContainer } from "react-toastify";
import "../assets/scss/App.scss";
import "../assets/scss/App.css";
import { useStore } from "../store";
export const App = (): ReactElement => {

View File

@@ -21,7 +21,6 @@ import { components } from "react-select";
import { RootState } from "threetwo-ui-typings";
import "react-sliding-pane/dist/react-sliding-pane.css";
import "react-loader-spinner/dist/loader/css/react-spinner-loader.css";
import Loader from "react-loader-spinner";
import SlidingPane from "react-sliding-pane";
import Modal from "react-modal";

View File

@@ -67,6 +67,8 @@ export const RecentlyImported = (
const isComicVineMetadataAvailable =
!isUndefined(comicvine) &&
!isUndefined(comicvine.volumeInformation);
const hasComicInfo = !isNil(comicInfo) && !isEmpty(comicInfo);
const cardState = (hasComicInfo || isComicVineMetadataAvailable) ? "scraped" : "imported";
return (
<div
key={idx}
@@ -77,6 +79,7 @@ export const RecentlyImported = (
imageUrl={url}
title={inferredMetadata.issue.name}
hasDetails
cardState={cardState}
>
<div>
<dd className="text-sm my-1 flex flex-row gap-1">
@@ -115,7 +118,7 @@ export const RecentlyImported = (
{/* ComicInfo.xml presence */}
{!isNil(comicInfo) && !isEmpty(comicInfo) && (
<div className="mt-1">
<i className="h-7 w-7 icon-[solar--code-file-bold-duotone] text-yellow-500 dark:text-yellow-300"></i>
<i className="h-7 w-7 icon-[solar--code-file-bold-duotone] text-gray-500 dark:text-white-300"></i>
</div>
)}
{/* ComicVine metadata presence */}

View File

@@ -59,6 +59,7 @@ export const WantedComicsList = ({
imageUrl={url}
hasDetails
title={issueName ? titleElement : <span>No Name</span>}
cardState="wanted"
>
<div className="pb-1">
<div className="flex flex-row gap-2">

View File

@@ -1,5 +1,4 @@
import React, { ReactElement, useCallback, useEffect, useState } from "react";
import "react-loader-spinner/dist/loader/css/react-spinner-loader.css";
import { format } from "date-fns";
import Loader from "react-loader-spinner";
import { isEmpty, isNil, isUndefined } from "lodash";
@@ -55,7 +54,6 @@ export const Import = (props: IProps): ReactElement => {
url: "http://localhost:3000/api/jobqueue/getJobResultStatistics",
params: { _t: Date.now() }, // Cache busting
});
console.log("Fetched import results:", response.data);
return response;
},
refetchOnWindowFocus: false,
@@ -69,13 +67,11 @@ export const Import = (props: IProps): ReactElement => {
// Listen for import queue drained event to refresh the table
const handleQueueDrained = () => {
console.log("Import queue drained, refreshing table...");
refetch();
};
// Listen for individual import completions to refresh the table
const handleCoverExtracted = () => {
console.log("Cover extracted, refreshing table...");
refetch();
};
@@ -97,7 +93,6 @@ export const Import = (props: IProps): ReactElement => {
queueAction,
queueStatus,
},
(data: any) => console.log(data),
);
};
/**

View File

@@ -10,11 +10,29 @@ interface ICardProps {
children?: PropTypes.ReactNodeLike;
borderColorClass?: string;
backgroundColor?: string;
cardState?: "wanted" | "delete" | "scraped" | "uncompressed" | "imported";
onClick?: (event: React.MouseEvent<HTMLElement>) => void;
cardContainerStyle?: PropTypes.object;
imageStyle?: PropTypes.object;
cardContainerStyle?: React.CSSProperties;
imageStyle?: React.CSSProperties;
}
const getCardStateClass = (cardState?: string): string => {
switch (cardState) {
case "wanted":
return "bg-card-wanted";
case "delete":
return "bg-card-delete";
case "scraped":
return "bg-card-scraped";
case "uncompressed":
return "bg-card-uncompressed";
case "imported":
return "bg-card-imported";
default:
return "";
}
};
const renderCard = (props: ICardProps): ReactElement => {
switch (props.orientation) {
case "horizontal":
@@ -83,7 +101,7 @@ const renderCard = (props: ICardProps): ReactElement => {
case "vertical-2":
return (
<div className="block rounded-md max-w-64 h-fit shadow-md shadow-white-400 bg-gray-200 dark:bg-slate-500">
<div className={`block rounded-md max-w-64 h-fit shadow-md shadow-white-400 ${getCardStateClass(props.cardState) || "bg-gray-200 dark:bg-slate-500"}`}>
<img
alt="Home"
src={props.imageUrl}
@@ -109,7 +127,7 @@ const renderCard = (props: ICardProps): ReactElement => {
case "horizontal-small":
return (
<>
<div className="flex flex-row justify-start align-top gap-3 bg-slate-200 h-fit rounded-md shadow-md shadow-white-400">
<div className={`flex flex-row justify-start align-top gap-3 h-fit rounded-md shadow-md shadow-white-400 ${getCardStateClass(props.cardState) || "bg-slate-200"}`}>
{/* thumbnail */}
<div className="rounded-md overflow-hidden">
<img src={props.imageUrl} className="object-cover h-20 w-20" />
@@ -125,7 +143,7 @@ const renderCard = (props: ICardProps): ReactElement => {
case "horizontal-medium":
return (
<>
<div className="flex flex-row items-center align-top gap-3 bg-slate-200 h-fit p-2 rounded-md shadow-md shadow-white-400">
<div className={`flex flex-row items-center align-top gap-3 h-fit p-2 rounded-md shadow-md shadow-white-400 ${getCardStateClass(props.cardState) || "bg-slate-200"}`}>
{/* thumbnail */}
<div className="rounded-md overflow-hidden">
<img src={props.imageUrl} />

View File

@@ -31,7 +31,7 @@ export const MetadataPanel = (props: IMetadatPanelProps): ReactElement => {
{
name: "rawFileDetails",
content: () => (
<dl className="dark:bg-[#647587] bg-slate-200 p-3 rounded-lg">
<dl className="dark:bg-card-imported bg-card-imported dark:text-slate-800 p-3 rounded-lg">
<dt>
<p className="text-lg">{issueName}</p>
</dt>

View File

@@ -113,15 +113,15 @@ export const T2Table = (tableOptions: T2TableProps): ReactElement => {
</div>
</div>
<table className="table-auto w-full text-sm text-gray-900 dark:text-slate-100 border-separate border-spacing-0">
<thead className="sticky top-0 bg-white dark:bg-slate-900 z-10 border-b border-gray-300 dark:border-slate-700">
{table.getHeaderGroups().map((headerGroup) => (
<table className="table-auto w-full text-sm text-gray-900 dark:text-slate-100">
<thead className="sticky top-0 z-10 bg-white dark:bg-slate-900">
{table.getHeaderGroups().map((headerGroup, groupIndex) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
{headerGroup.headers.map((header, index) => (
<th
key={header.id}
colSpan={header.colSpan}
className="px-3 py-2 text-[11px] font-semibold tracking-wide uppercase text-left text-gray-500 dark:text-slate-400"
className="px-3 py-2 text-[11px] font-semibold tracking-wide uppercase text-left text-gray-500 dark:text-slate-400 border-b border-gray-300 dark:border-slate-700"
>
{header.isPlaceholder
? null
@@ -136,11 +136,11 @@ export const T2Table = (tableOptions: T2TableProps): ReactElement => {
</thead>
<tbody>
{table.getRowModel().rows.map((row) => (
{table.getRowModel().rows.map((row, rowIndex) => (
<tr
key={row.id}
onClick={() => rowClickHandler(row)}
className="border-b border-gray-200 dark:border-slate-700 hover:bg-gray-50 dark:hover:bg-slate-800 transition-colors"
className="border-b border-gray-200 dark:border-slate-700 hover:bg-slate-100/30 dark:hover:bg-slate-700/20 transition-colors cursor-pointer"
>
{row.getVisibleCells().map((cell) => (
<td key={cell.id} className="px-3 py-2 align-top">

View File

@@ -4,7 +4,7 @@ import { SOCKET_BASE_URI } from "../constants/endpoints";
import { isNil } from "lodash";
import { QueryClient } from "@tanstack/react-query";
import { toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.min.css";
import "react-toastify/dist/ReactToastify.css";
const queryClient = new QueryClient();

View File

@@ -3,7 +3,7 @@ import React from 'react';
import { ComponentMeta, ComponentStory } from '@storybook/react';
import { MetadataPanel } from '../components/shared/MetadataPanel';
import "../assets/scss/App.scss";
import "../assets/scss/App.css";
export default {
/* 👇 The title prop is optional.
* See https://storybook.js.org/docs/react/configure/overview#configure-story-loading

View File

@@ -5,7 +5,7 @@ import React from 'react';
import { ComponentMeta, ComponentStory } from '@storybook/react';
import { Card } from '../components/shared/Carda';
import "../assets/scss/App.scss";
import "../assets/scss/App.css";
export default {
/* 👇 The title prop is optional.
* See https://storybook.js.org/docs/react/configure/overview#configure-story-loading

View File

@@ -5,6 +5,17 @@ module.exports = {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
darkMode: "class",
theme: {
extend: {
colors: {
card: {
wanted: "#f2d98d",
delete: "#FFEBEE",
scraped: "#b8edbc",
uncompressed: "#FFF3E0",
imported: "#d8dab0",
},
},
},
fontFamily: {
sans: ["PP Object Sans Regular", "sans-serif"],
hasklig: ["Hasklig Regular", "monospace"],

132
vite-plugin-iconify.js Normal file
View File

@@ -0,0 +1,132 @@
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
export function iconifyPlugin() {
const iconCache = new Map();
const collections = new Map();
function loadCollection(prefix) {
if (collections.has(prefix)) {
return collections.get(prefix);
}
try {
const collectionPath = join(__dirname, 'node_modules', '@iconify-json', prefix, 'icons.json');
const data = JSON.parse(readFileSync(collectionPath, 'utf8'));
collections.set(prefix, data);
return data;
} catch (e) {
return null;
}
}
function getIconCSS(iconData, selector) {
const { body, width, height } = iconData;
const viewBox = `0 0 ${width || 24} ${height || 24}`;
// Create SVG data URI
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${viewBox}">${body}</svg>`;
const dataUri = `data:image/svg+xml;base64,${Buffer.from(svg).toString('base64')}`;
return `${selector} {
display: inline-block;
width: 1em;
height: 1em;
background-color: currentColor;
-webkit-mask-image: url("${dataUri}");
mask-image: url("${dataUri}");
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-size: 100% 100%;
mask-size: 100% 100%;
}`;
}
return {
name: 'vite-plugin-iconify',
transform(code, id) {
// Only process files that might contain icon classes
if (!id.endsWith('.tsx') && !id.endsWith('.jsx') && !id.endsWith('.ts') && !id.endsWith('.js')) {
return null;
}
// Find all icon-[...] patterns
const iconPattern = /icon-\[([^\]]+)\]/g;
const matches = [...code.matchAll(iconPattern)];
if (matches.length === 0) {
return null;
}
// Extract unique icons
const icons = new Set(matches.map(m => m[1]));
// Generate CSS for each icon
for (const iconName of icons) {
if (iconCache.has(iconName)) {
continue;
}
try {
// Parse icon name (e.g., "solar--add-square-bold-duotone")
const parts = iconName.split('--');
if (parts.length !== 2) continue;
const [prefix, name] = parts;
// Load collection
const collection = loadCollection(prefix);
if (!collection || !collection.icons || !collection.icons[name]) {
continue;
}
// Get icon data
const iconData = collection.icons[name];
// Generate CSS
const selector = `.icon-\\[${iconName}\\]`;
const iconCSS = getIconCSS(iconData, selector);
iconCache.set(iconName, iconCSS);
} catch (e) {
// Silently skip failed icons
}
}
return null;
},
resolveId(id) {
if (id === '/@iconify-css') {
return id;
}
},
load(id) {
if (id === '/@iconify-css') {
const allCSS = Array.from(iconCache.values()).join('\n');
return allCSS;
}
},
transformIndexHtml() {
// Inject icon CSS into HTML
const allCSS = Array.from(iconCache.values()).join('\n');
if (allCSS) {
return [
{
tag: 'style',
attrs: { type: 'text/css' },
children: allCSS,
injectTo: 'head'
}
];
}
}
};
}

View File

@@ -5,7 +5,9 @@ import { defineConfig } from "vite";
export default defineConfig({
publicDir: "public",
base: "",
build: "esnext",
build: {
target: "esnext",
},
esbuild: {
supported: {
"top-level-await": true, //browsers can handle top-level-await features

9793
yarn.lock

File diff suppressed because it is too large Load Diff