From f08ad361eda5bea57ecf561a87fdf52ffa10a877 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Thu, 2 Oct 2025 00:43:49 +0300 Subject: [PATCH 001/197] feat: added review functionality --- package.json | 7 + src/locales/en/translation.json | 23 + .../events/catalogue/check-game-review.ts | 17 + .../events/catalogue/create-game-review.ts | 18 + src/main/events/catalogue/delete-review.ts | 14 + src/main/events/catalogue/get-game-reviews.ts | 26 + src/main/events/catalogue/vote-review.ts | 15 + src/main/events/index.ts | 5 + src/preload/index.ts | 26 + .../src/components/game-card/game-card.tsx | 10 +- src/renderer/src/declaration.d.ts | 28 + .../gallery-slider/gallery-slider.scss | 2 +- .../game-details/game-details-content.tsx | 474 ++++++++++++- .../game-details/game-details-skeleton.tsx | 1 + .../src/pages/game-details/game-details.scss | 656 ++++++++++++++++++ .../game-details/review-prompt-banner.scss | 46 ++ .../game-details/review-prompt-banner.tsx | 44 ++ .../game-details/review-sort-options.scss | 72 ++ .../game-details/review-sort-options.tsx | 60 ++ src/types/index.ts | 19 + yarn.lock | 446 ++++++++++++ 21 files changed, 2003 insertions(+), 6 deletions(-) create mode 100644 src/main/events/catalogue/check-game-review.ts create mode 100644 src/main/events/catalogue/create-game-review.ts create mode 100644 src/main/events/catalogue/delete-review.ts create mode 100644 src/main/events/catalogue/get-game-reviews.ts create mode 100644 src/main/events/catalogue/vote-review.ts create mode 100644 src/renderer/src/pages/game-details/review-prompt-banner.scss create mode 100644 src/renderer/src/pages/game-details/review-prompt-banner.tsx create mode 100644 src/renderer/src/pages/game-details/review-sort-options.scss create mode 100644 src/renderer/src/pages/game-details/review-sort-options.tsx diff --git a/package.json b/package.json index e21c962a..e2f750bd 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,12 @@ "@primer/octicons-react": "^19.9.0", "@radix-ui/react-dropdown-menu": "^2.1.2", "@reduxjs/toolkit": "^2.2.3", + "@tiptap/extension-bold": "^3.6.2", + "@tiptap/extension-italic": "^3.6.2", + "@tiptap/extension-link": "^3.6.2", + "@tiptap/extension-underline": "^3.6.2", + "@tiptap/react": "^3.6.2", + "@tiptap/starter-kit": "^3.6.2", "auto-launch": "^5.0.6", "axios": "^1.7.9", "axios-cookiejar-support": "^5.0.5", @@ -63,6 +69,7 @@ "jsdom": "^24.0.0", "jsonwebtoken": "^9.0.2", "lodash-es": "^4.17.21", + "lucide-react": "^0.544.0", "parse-torrent": "^11.0.18", "rc-virtual-list": "^3.18.3", "react-dnd": "^16.0.1", diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index bcf8cf54..b0fee465 100755 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -198,6 +198,29 @@ "hydra_needs_to_remain_open": "for this download, Hydra needs to remain open util it's completed. If Hydra closes before completing, you will lose your progress.", "achievements": "Achievements", "achievements_count": "Achievements {{unlockedCount}}/{{achievementsCount}}", + "show_more": "Show more", + "show_less": "Show less", + "reviews": "Reviews", + "leave_a_review": "Leave a Review", + "write_review_placeholder": "Share your thoughts about this game...", + "sort_newest": "Newest", + "sort_by": "Sort by", + "no_reviews_yet": "No reviews yet", + "be_first_to_review": "Be the first to share your thoughts about this game!", + "sort_oldest": "Oldest", + "sort_highest_score": "Highest Score", + "sort_lowest_score": "Lowest Score", + "sort_most_voted": "Most Voted", + "rating": "Rating", + "submit_review": "Submit Review", + "submitting": "Submitting...", + "loading_reviews": "Loading reviews...", + "loading_more_reviews": "Loading more reviews...", + "load_more_reviews": "Load More Reviews", + "youve_played_for_hours": "You've played for {{hours}} hours", + "would_you_recommend_this_game": "Would you like to leave a review to this game?", + "yes": "Yes", + "maybe_later": "Maybe Later", "cloud_save": "Cloud save", "cloud_save_description": "Save your progress in the cloud and continue playing on any device", "backups": "Backups", diff --git a/src/main/events/catalogue/check-game-review.ts b/src/main/events/catalogue/check-game-review.ts new file mode 100644 index 00000000..c46ede07 --- /dev/null +++ b/src/main/events/catalogue/check-game-review.ts @@ -0,0 +1,17 @@ +import { registerEvent } from "../register-event"; +import { HydraApi } from "@main/services"; +import type { GameShop } from "@types"; + +const checkGameReview = async ( + _event: Electron.IpcMainInvokeEvent, + shop: GameShop, + objectId: string +) => { + return HydraApi.get( + `/games/${shop}/${objectId}/reviews/check`, + null, + { needsAuth: true } + ); +}; + +registerEvent("checkGameReview", checkGameReview); \ No newline at end of file diff --git a/src/main/events/catalogue/create-game-review.ts b/src/main/events/catalogue/create-game-review.ts new file mode 100644 index 00000000..7f29b639 --- /dev/null +++ b/src/main/events/catalogue/create-game-review.ts @@ -0,0 +1,18 @@ +import { registerEvent } from "../register-event"; +import { HydraApi } from "@main/services"; +import type { GameShop } from "@types"; + +const createGameReview = async ( + _event: Electron.IpcMainInvokeEvent, + shop: GameShop, + objectId: string, + reviewHtml: string, + score: number +) => { + return HydraApi.post(`/games/${shop}/${objectId}/reviews`, { + reviewHtml, + score, + }); +}; + +registerEvent("createGameReview", createGameReview); \ No newline at end of file diff --git a/src/main/events/catalogue/delete-review.ts b/src/main/events/catalogue/delete-review.ts new file mode 100644 index 00000000..2048b3e7 --- /dev/null +++ b/src/main/events/catalogue/delete-review.ts @@ -0,0 +1,14 @@ +import { registerEvent } from "../register-event"; +import { HydraApi } from "@main/services"; +import type { GameShop } from "@types"; + +const deleteReview = async ( + _event: Electron.IpcMainInvokeEvent, + shop: GameShop, + objectId: string, + reviewId: string +) => { + return HydraApi.delete(`/games/${shop}/${objectId}/reviews/${reviewId}`); +}; + +registerEvent("deleteReview", deleteReview); \ No newline at end of file diff --git a/src/main/events/catalogue/get-game-reviews.ts b/src/main/events/catalogue/get-game-reviews.ts new file mode 100644 index 00000000..d3c31780 --- /dev/null +++ b/src/main/events/catalogue/get-game-reviews.ts @@ -0,0 +1,26 @@ +import { registerEvent } from "../register-event"; +import { HydraApi } from "@main/services"; +import type { GameShop } from "@types"; + +const getGameReviews = async ( + _event: Electron.IpcMainInvokeEvent, + shop: GameShop, + objectId: string, + take: number = 20, + skip: number = 0, + sortBy: string = "newest" +) => { + const params = new URLSearchParams({ + take: take.toString(), + skip: skip.toString(), + sortBy, + }); + + return HydraApi.get( + `/games/${shop}/${objectId}/reviews?${params.toString()}`, + null, + { needsAuth: false } + ); +}; + +registerEvent("getGameReviews", getGameReviews); \ No newline at end of file diff --git a/src/main/events/catalogue/vote-review.ts b/src/main/events/catalogue/vote-review.ts new file mode 100644 index 00000000..b60062c3 --- /dev/null +++ b/src/main/events/catalogue/vote-review.ts @@ -0,0 +1,15 @@ +import { registerEvent } from "../register-event"; +import { HydraApi } from "@main/services"; +import type { GameShop } from "@types"; + +const voteReview = async ( + _event: Electron.IpcMainInvokeEvent, + shop: GameShop, + objectId: string, + reviewId: string, + voteType: 'upvote' | 'downvote' +) => { + return HydraApi.put(`/games/${shop}/${objectId}/reviews/${reviewId}/${voteType}`, {}); +}; + +registerEvent("voteReview", voteReview); \ No newline at end of file diff --git a/src/main/events/index.ts b/src/main/events/index.ts index d4c461f8..378a3b6e 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -11,6 +11,11 @@ import "./catalogue/get-game-stats"; import "./catalogue/get-trending-games"; import "./catalogue/get-publishers"; import "./catalogue/get-developers"; +import "./catalogue/create-game-review"; +import "./catalogue/get-game-reviews"; +import "./catalogue/vote-review"; +import "./catalogue/delete-review"; +import "./catalogue/check-game-review"; import "./hardware/get-disk-free-space"; import "./hardware/check-folder-write-permission"; import "./library/add-game-to-library"; diff --git a/src/preload/index.ts b/src/preload/index.ts index 17c1225f..eda43369 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -77,6 +77,32 @@ contextBridge.exposeInMainWorld("electron", { getGameStats: (objectId: string, shop: GameShop) => ipcRenderer.invoke("getGameStats", objectId, shop), getTrendingGames: () => ipcRenderer.invoke("getTrendingGames"), + createGameReview: ( + shop: GameShop, + objectId: string, + reviewHtml: string, + score: number + ) => ipcRenderer.invoke("createGameReview", shop, objectId, reviewHtml, score), + getGameReviews: ( + shop: GameShop, + objectId: string, + take?: number, + skip?: number, + sortBy?: string + ) => ipcRenderer.invoke("getGameReviews", shop, objectId, take, skip, sortBy), + voteReview: ( + shop: GameShop, + objectId: string, + reviewId: string, + voteType: "upvote" | "downvote" + ) => ipcRenderer.invoke("voteReview", shop, objectId, reviewId, voteType), + deleteReview: ( + shop: GameShop, + objectId: string, + reviewId: string + ) => ipcRenderer.invoke("deleteReview", shop, objectId, reviewId), + checkGameReview: (shop: GameShop, objectId: string) => + ipcRenderer.invoke("checkGameReview", shop, objectId), onUpdateAchievements: ( objectId: string, shop: GameShop, diff --git a/src/renderer/src/components/game-card/game-card.tsx b/src/renderer/src/components/game-card/game-card.tsx index 3cdefc19..cb9a060c 100644 --- a/src/renderer/src/components/game-card/game-card.tsx +++ b/src/renderer/src/components/game-card/game-card.tsx @@ -1,4 +1,4 @@ -import { DownloadIcon, PeopleIcon } from "@primer/octicons-react"; +import { DownloadIcon, PeopleIcon, StarIcon } from "@primer/octicons-react"; import type { GameStats } from "@types"; import SteamLogo from "@renderer/assets/steam-logo.svg?react"; @@ -107,6 +107,14 @@ export function GameCard({ game, ...props }: GameCardProps) { {stats ? numberFormatter.format(stats.playerCount) : "…"} + {stats?.averageScore && ( +
+ + + {stats.averageScore.toFixed(1)} + +
+ )} diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index e6277888..752a1115 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -93,6 +93,34 @@ declare global { ) => Promise; getGameStats: (objectId: string, shop: GameShop) => Promise; getTrendingGames: () => Promise; + createGameReview: ( + shop: GameShop, + objectId: string, + reviewHtml: string, + score: number + ) => Promise; + getGameReviews: ( + shop: GameShop, + objectId: string, + take?: number, + skip?: number, + sortBy?: string + ) => Promise; + voteReview: ( + shop: GameShop, + objectId: string, + reviewId: string, + voteType: 'upvote' | 'downvote' + ) => Promise; + deleteReview: ( + shop: GameShop, + objectId: string, + reviewId: string + ) => Promise; + checkGameReview: ( + shop: GameShop, + objectId: string + ) => Promise<{ hasReviewed: boolean }>; onUpdateAchievements: ( objectId: string, shop: GameShop, diff --git a/src/renderer/src/pages/game-details/gallery-slider/gallery-slider.scss b/src/renderer/src/pages/game-details/gallery-slider/gallery-slider.scss index 9483b50e..d1ae2481 100644 --- a/src/renderer/src/pages/game-details/gallery-slider/gallery-slider.scss +++ b/src/renderer/src/pages/game-details/gallery-slider/gallery-slider.scss @@ -65,7 +65,7 @@ &__preview { width: 100%; padding: globals.$spacing-unit 0; - height: 100%; + height: auto; display: flex; position: relative; overflow-x: auto; diff --git a/src/renderer/src/pages/game-details/game-details-content.tsx b/src/renderer/src/pages/game-details/game-details-content.tsx index ca2ca023..48228e8e 100644 --- a/src/renderer/src/pages/game-details/game-details-content.tsx +++ b/src/renderer/src/pages/game-details/game-details-content.tsx @@ -1,33 +1,45 @@ import { useContext, useEffect, useMemo, useRef, useState } from "react"; -import { PencilIcon } from "@primer/octicons-react"; +import { PencilIcon, TrashIcon, ClockIcon } from "@primer/octicons-react"; +import { ThumbsUp, ThumbsDown } from "lucide-react"; +import { useNavigate } from "react-router-dom"; +import { useEditor, EditorContent } from '@tiptap/react'; +import StarterKit from '@tiptap/starter-kit'; +import Bold from '@tiptap/extension-bold'; +import Italic from '@tiptap/extension-italic'; +import Underline from '@tiptap/extension-underline'; +import type { GameReview } from "@types"; import { HeroPanel } from "./hero"; import { DescriptionHeader } from "./description-header/description-header"; import { GallerySlider } from "./gallery-slider/gallery-slider"; import { Sidebar } from "./sidebar/sidebar"; import { EditGameModal } from "./modals"; +import { ReviewSortOptions } from "./review-sort-options"; +import { ReviewPromptBanner } from "./review-prompt-banner"; import { useTranslation } from "react-i18next"; import { cloudSyncContext, gameDetailsContext } from "@renderer/context"; import { AuthPage } from "@shared"; import cloudIconAnimated from "@renderer/assets/icons/cloud-animated.gif"; -import { useUserDetails, useLibrary } from "@renderer/hooks"; +import { useUserDetails, useLibrary, useDate } from "@renderer/hooks"; import { useSubscription } from "@renderer/hooks/use-subscription"; import "./game-details.scss"; export function GameDetailsContent() { const heroRef = useRef(null); + const navigate = useNavigate(); const { t } = useTranslation("game_details"); - const { objectId, shopDetails, game, hasNSFWContentBlocked, updateGame } = + const { objectId, shopDetails, game, hasNSFWContentBlocked, updateGame, shop } = useContext(gameDetailsContext); const { showHydraCloudModal } = useSubscription(); const { userDetails, hasActiveSubscription } = useUserDetails(); const { updateLibrary } = useLibrary(); + const { formatDistance } = useDate(); const { setShowCloudSyncModal, getGameArtifacts } = useContext(cloudSyncContext); @@ -80,6 +92,41 @@ export function GameDetailsContent() { const [backdropOpacity, setBackdropOpacity] = useState(1); const [showEditGameModal, setShowEditGameModal] = useState(false); + const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false); + + // Reviews state management + const [reviews, setReviews] = useState([]); + const [reviewsLoading, setReviewsLoading] = useState(false); + const [reviewScore, setReviewScore] = useState(5); + const [submittingReview, setSubmittingReview] = useState(false); + const [reviewsSortBy, setReviewsSortBy] = useState("newest"); + const [reviewsPage, setReviewsPage] = useState(0); + const [hasMoreReviews, setHasMoreReviews] = useState(true); + const [visibleBlockedReviews, setVisibleBlockedReviews] = useState>(new Set()); + const [totalReviewCount, setTotalReviewCount] = useState(0); + const [showReviewForm, setShowReviewForm] = useState(false); + + // Review prompt banner state + const [showReviewPrompt, setShowReviewPrompt] = useState(false); + const [hasUserReviewed, setHasUserReviewed] = useState(false); + const [reviewCheckLoading, setReviewCheckLoading] = useState(false); + + // Tiptap editor for review input + const editor = useEditor({ + extensions: [ + StarterKit, + Bold, + Italic, + Underline, + ], + content: '', + editorProps: { + attributes: { + class: 'game-details__review-editor', + 'data-placeholder': t("write_review_placeholder"), + }, + }, + }); useEffect(() => { setBackdropOpacity(1); @@ -114,6 +161,188 @@ export function GameDetailsContent() { const isCustomGame = game?.shop === "custom"; + // Reviews functions + const checkUserReview = async () => { + if (!objectId || !userDetails) return; + + setReviewCheckLoading(true); + try { + const response = await window.electron.checkGameReview(shop, objectId); + const hasReviewed = (response as any)?.hasReviewed || false; + setHasUserReviewed(hasReviewed); + + // Show prompt only if user hasn't reviewed and has played the game + if (!hasReviewed && game?.playTimeInMilliseconds && game.playTimeInMilliseconds > 0) { + setShowReviewPrompt(true); + } + } catch (error) { + console.error("Failed to check user review:", error); + } finally { + setReviewCheckLoading(false); + } + }; + + const loadReviews = async (reset = false) => { + if (!objectId) return; + + setReviewsLoading(true); + try { + const skip = reset ? 0 : reviewsPage * 20; + const response = await window.electron.getGameReviews( + shop, + objectId, + 20, + skip, + reviewsSortBy + ); + + // Handle the response structure: { totalCount: number, reviews: Review[] } + const reviewsData = (response as any)?.reviews || []; + const reviewCount = (response as any)?.totalCount || 0; + + if (reset) { + setReviews(reviewsData); + setReviewsPage(0); + setTotalReviewCount(reviewCount); + } else { + setReviews(prev => [...prev, ...reviewsData]); + } + + setHasMoreReviews(reviewsData.length === 20); + } catch (error) { + console.error("Failed to load reviews:", error); + } finally { + setReviewsLoading(false); + } + }; + + const handleVoteReview = async (reviewId: string, voteType: 'upvote' | 'downvote') => { + if (!objectId) return; + + try { + await window.electron.voteReview(shop, objectId, reviewId, voteType); + // Reload reviews to get updated vote counts + loadReviews(true); + } catch (error) { + console.error(`Failed to ${voteType} review:`, error); + } + }; + + const handleDeleteReview = async (reviewId: string) => { + if (!objectId) return; + + try { + await window.electron.deleteReview(shop, objectId, reviewId); + // Reload reviews after deletion + loadReviews(true); + } catch (error) { + console.error('Failed to delete review:', error); + } + }; + + const handleSubmitReview = async () => { + console.log("handleSubmitReview called"); + console.log("game:", game); + console.log("objectId:", objectId); + + const reviewHtml = editor?.getHTML() || ''; + console.log("reviewHtml:", reviewHtml); + console.log("reviewScore:", reviewScore); + console.log("submittingReview:", submittingReview); + + if (!objectId || !reviewHtml.trim() || submittingReview) { + console.log("Early return - validation failed"); + return; + } + + console.log("Starting review submission..."); + setSubmittingReview(true); + try { + console.log("Calling window.electron.createGameReview..."); + await window.electron.createGameReview( + shop, + objectId, + reviewHtml, + reviewScore + ); + + console.log("Review submitted successfully"); + editor?.commands.clearContent(); + setReviewScore(5); + await loadReviews(true); // Reload reviews after submission + setShowReviewForm(false); // Hide the review form after successful submission + setShowReviewPrompt(false); // Hide the prompt banner + setHasUserReviewed(true); // Update the review status + } catch (error) { + console.error("Failed to submit review:", error); + } finally { + setSubmittingReview(false); + console.log("Review submission completed"); + } + }; + + // Review prompt banner handlers + const handleReviewPromptYes = () => { + setShowReviewPrompt(false); + setShowReviewForm(true); + + // Scroll to review form + setTimeout(() => { + const reviewFormElement = document.querySelector('.game-details__review-form'); + if (reviewFormElement) { + reviewFormElement.scrollIntoView({ + behavior: 'smooth', + block: 'start' + }); + } + }, 100); + }; + + const handleReviewPromptLater = () => { + setShowReviewPrompt(false); + }; + + const handleSortChange = (newSortBy: string) => { + setReviewsSortBy(newSortBy); + setReviewsPage(0); + setHasMoreReviews(true); + loadReviews(true); + }; + + const toggleBlockedReview = (reviewId: string) => { + setVisibleBlockedReviews(prev => { + const newSet = new Set(prev); + if (newSet.has(reviewId)) { + newSet.delete(reviewId); + } else { + newSet.add(reviewId); + } + return newSet; + }); + }; + + const loadMoreReviews = () => { + if (!reviewsLoading && hasMoreReviews) { + setReviewsPage(prev => prev + 1); + loadReviews(false); + } + }; + + // Load reviews when component mounts or sort changes + useEffect(() => { + if (objectId && (game || shop)) { + loadReviews(true); + checkUserReview(); // Check if user has reviewed this game + } + }, [game, shop, objectId, reviewsSortBy, userDetails]); + + // Load more reviews when page changes + useEffect(() => { + if (reviewsPage > 0) { + loadReviews(false); + } + }, [reviewsPage]); + // Helper function to get image with custom asset priority const getImageWithCustomPriority = ( customUrl: string | null | undefined, @@ -227,6 +456,14 @@ export function GameDetailsContent() {
+ {/* Review Prompt Banner */} + {showReviewPrompt && userDetails && game?.playTimeInMilliseconds && !hasUserReviewed && !reviewCheckLoading && ( + + )} + @@ -234,8 +471,237 @@ export function GameDetailsContent() { dangerouslySetInnerHTML={{ __html: aboutTheGame, }} - className="game-details__description" + className={`game-details__description ${ + isDescriptionExpanded ? 'game-details__description--expanded' : 'game-details__description--collapsed' + }`} /> + + {aboutTheGame && aboutTheGame.length > 500 && ( + + )} + +
+ {showReviewForm && ( + <> +
+

{t("leave_a_review")}

+
+ +
+
+ +
+
+ + + +
+ + +
+
+ +
+
+ + +
+
+
+ + )} + + {showReviewForm && ( +
+ )} + +
+
+
+

+ {t("reviews")} +

+ + {totalReviewCount} + +
+ +
+ + {reviewsLoading && reviews.length === 0 && ( +
+ {t("loading_reviews")} +
+ )} + + {!reviewsLoading && reviews.length === 0 && ( +
+
📝
+

+ {t("no_reviews_yet")} +

+

+ {t("be_first_to_review")} +

+
+ )} + + {reviews.map((review, index) => ( +
+ {review.isBlocked && !visibleBlockedReviews.has(review.id) ? ( +
+ Review from blocked user — + +
+ ) : ( + <> +
+
+ {review.user?.profileImageUrl && ( + {review.user.displayName + )} +
+
review.user?.id && navigate(`/profile/${review.user.id}`)} + > + {review.user?.displayName || 'Anonymous'} +
+
+ + {formatDistance(new Date(review.createdAt), new Date(), { addSuffix: true })} +
+
+
+
+ {review.score}/10 +
+
+
+
+
+ + +
+ {userDetails?.id === review.user?.id && ( + + )} + {review.isBlocked && visibleBlockedReviews.has(review.id) && ( + + )} +
+ + )} +
+ ))} + + {hasMoreReviews && !reviewsLoading && ( + + )} + + {reviewsLoading && reviews.length > 0 && ( +
+ {t("loading_more_reviews")} +
+ )} +
+
{game?.shop !== "custom" && } diff --git a/src/renderer/src/pages/game-details/game-details-skeleton.tsx b/src/renderer/src/pages/game-details/game-details-skeleton.tsx index c39da3bb..adaf4ab2 100644 --- a/src/renderer/src/pages/game-details/game-details-skeleton.tsx +++ b/src/renderer/src/pages/game-details/game-details-skeleton.tsx @@ -35,6 +35,7 @@ export function GameDetailsSkeleton() { ))} +
diff --git a/src/renderer/src/pages/game-details/game-details.scss b/src/renderer/src/pages/game-details/game-details.scss index e1140d31..b82bd6b1 100644 --- a/src/renderer/src/pages/game-details/game-details.scss +++ b/src/renderer/src/pages/game-details/game-details.scss @@ -27,6 +27,418 @@ $hero-height: 300px; } } + &__review-form { + display: flex; + flex-direction: column; + gap: calc(globals.$spacing-unit * 2); + margin-bottom: calc(globals.$spacing-unit * 3); + padding: calc(globals.$spacing-unit * 2); + background: rgba(255, 255, 255, 0.02); + border-radius: 8px; + border: 1px solid rgba(255, 255, 255, 0.05); + } + + &__review-form-controls { + display: flex; + gap: calc(globals.$spacing-unit * 2); + align-items: flex-end; + flex-wrap: wrap; + + @media (max-width: 768px) { + flex-direction: column; + align-items: stretch; + gap: calc(globals.$spacing-unit * 1.5); + } + } + + &__review-form-bottom { + display: flex; + justify-content: space-between; + align-items: flex-end; + gap: calc(globals.$spacing-unit * 2); + + @media (max-width: 768px) { + flex-direction: column; + align-items: stretch; + gap: calc(globals.$spacing-unit * 1.5); + } + } + + &__review-score-container { + display: flex; + flex-direction: column; + gap: calc(globals.$spacing-unit * 0.75); + min-width: 120px; + } + + &__review-score-label { + display: block; + font-size: globals.$body-font-size; + color: globals.$body-color; + } + + &__review-score-select { + background-color: rgba(255, 255, 255, 0.05); + border: 1px solid globals.$border-color; + border-radius: 4px; + padding: calc(globals.$spacing-unit * 0.75) calc(globals.$spacing-unit * 1); + color: globals.$body-color; + font-size: globals.$body-font-size; + font-family: inherit; + cursor: pointer; + transition: border-color 0.2s ease, background-color 0.2s ease; + + &:focus { + outline: none; + background-color: rgba(255, 255, 255, 0.08); + border-color: globals.$brand-teal; + } + + &:hover { + border-color: rgba(255, 255, 255, 0.15); + } + + option { + background-color: globals.$dark-background-color; + color: globals.$body-color; + } + } + + &__reviews-sort { + display: flex; + flex-direction: column; + gap: calc(globals.$spacing-unit * 0.75); + min-width: 150px; + } + + &__reviews-sort-label { + display: block; + font-size: globals.$body-font-size; + color: globals.$body-color; + } + + &__reviews-sort-select { + background-color: rgba(255, 255, 255, 0.05); + border: 1px solid globals.$border-color; + border-radius: 4px; + padding: calc(globals.$spacing-unit * 0.75) calc(globals.$spacing-unit * 1); + color: globals.$body-color; + font-size: globals.$body-font-size; + font-family: inherit; + cursor: pointer; + transition: border-color 0.2s ease, background-color 0.2s ease; + + &:focus { + outline: none; + background-color: rgba(255, 255, 255, 0.08); + border-color: globals.$brand-teal; + } + + &:hover { + border-color: rgba(255, 255, 255, 0.15); + } + + option { + background-color: globals.$dark-background-color; + color: globals.$body-color; + } + } + + &__review-submit-button { + background-color: rgba(255, 255, 255, 0.05); + border: 1px solid globals.$border-color; + color: globals.$body-color; + padding: calc(globals.$spacing-unit * 0.75) calc(globals.$spacing-unit * 1.5); + border-radius: 6px; + cursor: pointer; + font-size: globals.$small-font-size; + font-family: inherit; + transition: all ease 0.2s; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + white-space: nowrap; + + &:hover:not(:disabled) { + background-color: rgba(255, 255, 255, 0.08); + border-color: rgba(255, 255, 255, 0.15); + } + + &:active { + opacity: 0.9; + } + + &:disabled { + background: rgba(255, 255, 255, 0.1); + cursor: not-allowed; + color: rgba(255, 255, 255, 0.5); + } + } + + &__reviews-list { + margin-top: calc(globals.$spacing-unit * 3); + } + + &__reviews-separator { + height: 1px; + background: rgba(255, 255, 255, 0.1); + margin: calc(globals.$spacing-unit * 3) 0; + width: 100%; + } + + &__reviews-list-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: calc(globals.$spacing-unit * 2); + padding-bottom: calc(globals.$spacing-unit * 1); + } + + &__reviews-empty { + text-align: center; + padding: calc(globals.$spacing-unit * 4) calc(globals.$spacing-unit * 2); + background: rgba(255, 255, 255, 0.02); + border: 1px solid rgba(255, 255, 255, 0.05); + border-radius: 8px; + margin-bottom: calc(globals.$spacing-unit * 2); + } + + &__reviews-empty-icon { + font-size: 48px; + margin-bottom: calc(globals.$spacing-unit * 2); + opacity: 0.6; + } + + &__reviews-empty-title { + color: rgba(255, 255, 255, 0.9); + font-weight: 600; + margin: 0 0 calc(globals.$spacing-unit * 1) 0; + } + + &__reviews-empty-message { + color: rgba(255, 255, 255, 0.6); + font-size: globals.$small-font-size; + margin: 0; + line-height: 1.4; + } + + &__review-item { + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 6px; + padding: calc(globals.$spacing-unit * 2); + margin-bottom: calc(globals.$spacing-unit * 2); + } + + &__review-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: calc(globals.$spacing-unit * 1.5); + } + + &__review-user { + display: flex; + align-items: center; + gap: calc(globals.$spacing-unit * 1); + } + + &__review-avatar { + width: 32px; + height: 32px; + border-radius: 50%; + object-fit: cover; + border: 2px solid rgba(255, 255, 255, 0.1); + } + + &__review-user-info { + display: flex; + flex-direction: column; + gap: calc(globals.$spacing-unit * 0.25); + } + + &__review-display-name { + color: rgba(255, 255, 255, 0.9); + font-size: globals.$small-font-size; + font-weight: 600; + + &--clickable { + cursor: pointer; + transition: color 0.2s ease; + + &:hover { + text-decoration: underline; + } + } + } + + &__review-actions { + margin-top: 12px; + padding-top: 8px; + border-top: 1px solid rgba(255, 255, 255, 0.1); + display: flex; + justify-content: space-between; + align-items: center; + } + + &__review-votes { + display: flex; + gap: 12px; + } + + &__vote-button { + display: flex; + align-items: center; + gap: 6px; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 6px; + padding: 6px 12px; + color: #ccc; + font-size: 14px; + cursor: pointer; + transition: all 0.2s ease; + + &:hover { + background: rgba(255, 255, 255, 0.1); + border-color: rgba(255, 255, 255, 0.2); + } + + &--upvote:hover { + color: #4caf50; + border-color: #4caf50; + } + + &--downvote:hover { + color: #f44336; + border-color: #f44336; + } + + &--active { + &.game-details__vote-button--upvote { + svg { + fill: white; + } + } + + &.game-details__vote-button--downvote { + svg { + fill: white; + } + } + } + + span { + font-weight: 500; + } + } + + &__delete-review-button { + display: flex; + align-items: center; + justify-content: center; + background: rgba(244, 67, 54, 0.1); + border: 1px solid rgba(244, 67, 54, 0.3); + border-radius: 6px; + padding: 6px; + color: #f44336; + cursor: pointer; + transition: all 0.2s ease; + + &:hover { + background: rgba(244, 67, 54, 0.2); + border-color: #f44336; + color: #ff5722; + } + } + + &__blocked-review-simple { + color: rgba(255, 255, 255, 0.6); + font-size: globals.$small-font-size; + display: flex; + align-items: center; + gap: calc(globals.$spacing-unit * 0.5); + } + + &__blocked-review-show-link { + background: none; + border: none; + color: #ffc107; + font-size: globals.$small-font-size; + cursor: pointer; + text-decoration: underline; + padding: 0; + transition: color 0.2s ease; + + &:hover { + color: #ffeb3b; + } + } + + &__blocked-review-hide-link { + background: none; + border: none; + color: rgba(255, 255, 255, 0.5); + font-size: globals.$small-font-size; + cursor: pointer; + text-decoration: underline; + padding: 0; + transition: color 0.2s ease; + + &:hover { + color: rgba(255, 255, 255, 0.8); + } + } + + &__review-score { + background: rgba(255, 255, 255, 0.1); + color: rgba(255, 255, 255, 0.9); + padding: calc(globals.$spacing-unit * 0.5) calc(globals.$spacing-unit * 1); + border-radius: 4px; + font-size: globals.$small-font-size; + font-weight: 600; + border: 1px solid rgba(255, 255, 255, 0.15); + } + + &__review-date { + display: flex; + align-items: center; + gap: 4px; + color: rgba(255, 255, 255, 0.6); + font-size: globals.$small-font-size; + } + + &__review-content { + color: globals.$body-color; + line-height: 1.5; + } + + &__reviews-loading { + text-align: center; + color: rgba(255, 255, 255, 0.6); + padding: calc(globals.$spacing-unit * 2); + } + + &__load-more-reviews { + background: rgba(255, 255, 255, 0.05); + border: 1px solid globals.$border-color; + color: globals.$body-color; + padding: calc(globals.$spacing-unit * 1) calc(globals.$spacing-unit * 2); + border-radius: 4px; + cursor: pointer; + font-size: globals.$body-font-size; + font-family: inherit; + transition: all 0.2s ease; + width: 100%; + margin-top: calc(globals.$spacing-unit * 2); + + &:hover { + background-color: rgba(255, 255, 255, 0.1); + border-color: globals.$brand-teal; + } + } + &__hero { width: 100%; height: $hero-height; @@ -192,6 +604,8 @@ $hero-height: 300px; min-width: 0; flex: 1; overflow-x: hidden; + display: flex; + flex-direction: column; } &__description { @@ -203,6 +617,7 @@ $hero-height: 300px; margin-right: auto; overflow-x: auto; min-height: auto; + transition: max-height 0.3s ease-in-out; @media (min-width: 1280px) { width: 60%; @@ -212,6 +627,27 @@ $hero-height: 300px; width: 50%; } + &--collapsed { + max-height: 300px; + overflow: hidden; + position: relative; + + &::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 60px; + background: linear-gradient(transparent, globals.$background-color); + pointer-events: none; + } + } + + &--expanded { + max-height: none; + } + img, video { border-radius: 5px; @@ -237,6 +673,24 @@ $hero-height: 300px; } } + &__description-toggle { + background: none; + border: 1px solid globals.$border-color; + color: globals.$body-color; + padding: calc(globals.$spacing-unit * 0.75) calc(globals.$spacing-unit * 1.5); + border-radius: 4px; + cursor: pointer; + font-size: globals.$body-font-size; + margin-top: calc(globals.$spacing-unit * 1.5); + transition: all 0.2s ease; + align-self: center; + + &:hover { + background-color: rgba(255, 255, 255, 0.1); + border-color: globals.$brand-teal; + } + } + &__description-skeleton { display: flex; flex-direction: column; @@ -367,4 +821,206 @@ $hero-height: 300px; flex: 1; transition: opacity 0.2s ease; } + + &__reviews-section { + margin-top: calc(globals.$spacing-unit * 3); + padding-top: calc(globals.$spacing-unit * 3); + border-top: 1px solid rgba(255, 255, 255, 0.1); + width: 100%; + margin-left: auto; + margin-right: auto; + + @media (min-width: 1280px) { + width: 60%; + } + + @media (min-width: 1536px) { + width: 50%; + } + } + + &__reviews-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: calc(globals.$spacing-unit * 2); + gap: calc(globals.$spacing-unit * 2); + + @media (max-width: 768px) { + flex-direction: column; + align-items: flex-start; + gap: calc(globals.$spacing-unit * 1.5); + } + } + + &__reviews-title { + font-size: 1.25rem; + font-weight: 600; + color: globals.$muted-color; + margin: 0; + } + + &__reviews-title-group { + display: flex; + align-items: center; + gap: calc(globals.$spacing-unit); + flex: 1; + } + + &__reviews-badge { + background-color: rgba(255, 255, 255, 0.1); + color: rgba(255, 255, 255, 0.7); + padding: 4px 8px; + border-radius: 6px; + font-size: 12px; + font-weight: 600; + min-width: 24px; + text-align: center; + flex-shrink: 0; + } + + &__leave-review-cta { + display: flex; + align-items: center; + gap: calc(globals.$spacing-unit * 0.5); + padding: calc(globals.$spacing-unit * 0.75) calc(globals.$spacing-unit * 1.5); + background: linear-gradient(135deg, globals.$brand-teal, globals.$brand-blue); + color: white; + border: none; + border-radius: 8px; + font-size: 0.9rem; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + margin-bottom: calc(globals.$spacing-unit); + + &:hover { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(globals.$brand-teal, 0.3); + } + + &:active { + transform: translateY(0); + } + + svg { + flex-shrink: 0; + } + } + + &__review-input-container { + width: 100%; + position: relative; + } + + &__review-input-bottom { + position: absolute; + bottom: 8px; + left: 8px; + right: 8px; + display: flex; + justify-content: space-between; + align-items: center; + gap: 8px; + z-index: 10; + } + + &__review-editor-toolbar { + display: flex; + gap: 4px; + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 4px; + padding: 4px; + backdrop-filter: blur(10px); + background: rgba(0, 0, 0, 0.3); + } + + &__editor-button { + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 3px; + color: globals.$body-color; + padding: 4px 6px; + cursor: pointer; + font-size: 12px; + font-weight: 600; + transition: all 0.2s ease; + min-width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + + &:hover:not(:disabled) { + background: rgba(255, 255, 255, 0.1); + border-color: rgba(255, 255, 255, 0.2); + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } + + &.is-active { + background: globals.$brand-teal; + border-color: globals.$brand-teal; + color: white; + } + } + + &__review-input { + width: 100%; + background-color: rgba(255, 255, 255, 0.05); + border: 1px solid globals.$border-color; + border-radius: 6px; + padding: calc(globals.$spacing-unit * 1.5); + padding-bottom: calc(globals.$spacing-unit * 3.5); + color: globals.$body-color; + font-size: globals.$body-font-size; + font-family: inherit; + line-height: 1.5; + min-height: 100px; + transition: border-color 0.2s ease, background-color 0.2s ease; + + &:focus-within { + outline: none; + background-color: rgba(255, 255, 255, 0.08); + border-color: globals.$brand-teal; + } + + &:hover { + border-color: rgba(255, 255, 255, 0.15); + } + + .ProseMirror { + outline: none; + min-height: 80px; + + &:empty:before { + content: attr(data-placeholder); + color: rgba(208, 209, 215, 0.6); + pointer-events: none; + } + + p { + margin: 0 0 8px 0; + + &:last-child { + margin-bottom: 0; + } + } + + strong { + font-weight: 700; + } + + em { + font-style: italic; + } + + u { + text-decoration: underline; + } + } + } } diff --git a/src/renderer/src/pages/game-details/review-prompt-banner.scss b/src/renderer/src/pages/game-details/review-prompt-banner.scss new file mode 100644 index 00000000..28ba1e47 --- /dev/null +++ b/src/renderer/src/pages/game-details/review-prompt-banner.scss @@ -0,0 +1,46 @@ +@use "../../scss/globals.scss"; + +.review-prompt-banner { + background: rgba(255, 255, 255, 0.02); + border-radius: 8px; + padding: calc(globals.$spacing-unit * 2); + margin-bottom: calc(globals.$spacing-unit * 3); + border: 1px solid rgba(255, 255, 255, 0.05); + + &__content { + display: flex; + align-items: center; + justify-content: space-between; + gap: calc(globals.$spacing-unit * 2.5); + + @media (max-width: 768px) { + flex-direction: column; + align-items: flex-start; + gap: calc(globals.$spacing-unit * 2); + } + } + + &__text { + display: flex; + flex-direction: column; + gap: calc(globals.$spacing-unit * 0.5); + } + + &__playtime { + font-size: globals.$body-font-size; + color: globals.$body-color; + font-weight: 600; + } + + &__question { + font-size: globals.$small-font-size; + color: globals.$muted-color; + font-weight: 400; + } + + &__actions { + display: flex; + gap: globals.$spacing-unit; + align-items: center; + } +} \ No newline at end of file diff --git a/src/renderer/src/pages/game-details/review-prompt-banner.tsx b/src/renderer/src/pages/game-details/review-prompt-banner.tsx new file mode 100644 index 00000000..87c1b170 --- /dev/null +++ b/src/renderer/src/pages/game-details/review-prompt-banner.tsx @@ -0,0 +1,44 @@ +import { useTranslation } from "react-i18next"; +import { Button } from "@renderer/components"; +import "./review-prompt-banner.scss"; + +interface ReviewPromptBannerProps { + onYesClick: () => void; + onLaterClick: () => void; +} + +export function ReviewPromptBanner({ + onYesClick, + onLaterClick, +}: ReviewPromptBannerProps) { + const { t } = useTranslation("game_details"); + + return ( +
+
+
+ + You've seemed to enjoy this game + + + {t("would_you_recommend_this_game")} + +
+
+ + +
+
+
+ ); +} \ No newline at end of file diff --git a/src/renderer/src/pages/game-details/review-sort-options.scss b/src/renderer/src/pages/game-details/review-sort-options.scss new file mode 100644 index 00000000..e982cb24 --- /dev/null +++ b/src/renderer/src/pages/game-details/review-sort-options.scss @@ -0,0 +1,72 @@ +@use "../../scss/globals.scss"; + +.review-sort-options { + &__container { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: calc(globals.$spacing-unit); + } + + &__label { + color: rgba(255, 255, 255, 0.6); + font-size: 14px; + font-weight: 400; + } + + &__options { + display: flex; + align-items: center; + gap: calc(globals.$spacing-unit); + font-size: 14px; + flex-wrap: wrap; + + @media (max-width: 768px) { + gap: calc(globals.$spacing-unit * 0.75); + } + } + + &__option { + background: none; + border: none; + color: rgba(255, 255, 255, 0.4); + cursor: pointer; + padding: 4px 0; + font-size: 14px; + font-weight: 300; + transition: all ease 0.2s; + display: flex; + align-items: center; + gap: 6px; + + &:hover:not(:disabled) { + color: rgba(255, 255, 255, 0.6); + } + + &.active { + color: rgba(255, 255, 255, 0.9); + font-weight: 500; + } + + span { + display: inline-block; + + @media (max-width: 480px) { + display: none; + } + } + + @media (max-width: 480px) { + gap: 0; + } + } + + &__separator { + color: rgba(255, 255, 255, 0.3); + font-size: 14px; + + @media (max-width: 480px) { + display: none; + } + } +} \ No newline at end of file diff --git a/src/renderer/src/pages/game-details/review-sort-options.tsx b/src/renderer/src/pages/game-details/review-sort-options.tsx new file mode 100644 index 00000000..5ec25c31 --- /dev/null +++ b/src/renderer/src/pages/game-details/review-sort-options.tsx @@ -0,0 +1,60 @@ +import { CalendarIcon, StarIcon, ThumbsupIcon, ClockIcon } from "@primer/octicons-react"; +import { useTranslation } from "react-i18next"; +import "./review-sort-options.scss"; + +type ReviewSortOption = "newest" | "oldest" | "score_high" | "score_low" | "most_voted"; + +interface ReviewSortOptionsProps { + sortBy: ReviewSortOption; + onSortChange: (sortBy: ReviewSortOption) => void; +} + +export function ReviewSortOptions({ sortBy, onSortChange }: ReviewSortOptionsProps) { + const { t } = useTranslation("game_details"); + + return ( +
+
+ + | + + | + + | + + | + +
+
+ ); +} \ No newline at end of file diff --git a/src/types/index.ts b/src/types/index.ts index 593c45be..f4b0645b 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -234,6 +234,25 @@ export interface GameStats { downloadCount: number; playerCount: number; assets: ShopAssets | null; + averageScore: number | null; +} + +export interface GameReview { + id: string; + reviewHtml: string; + score: number; + createdAt: string; + updatedAt: string; + upvotes: number; + downvotes: number; + isBlocked: boolean; + hasUpvoted: boolean; + hasDownvoted: boolean; + user: { + id: string; + displayName: string; + profileImageUrl: string | null; + } | null; } export interface TrendingGame extends ShopAssets { diff --git a/yarn.lock b/yarn.lock index 2c321857..71551858 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2073,6 +2073,11 @@ redux-thunk "^3.1.0" reselect "^5.1.0" +"@remirror/core-constants@3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@remirror/core-constants/-/core-constants-3.0.0.tgz#96fdb89d25c62e7b6a5d08caf0ce5114370e3b8f" + integrity sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg== + "@remix-run/router@1.19.2": version "1.19.2" resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.19.2.tgz#0c896535473291cb41f152c180bedd5680a3b273" @@ -2840,6 +2845,201 @@ dependencies: uint8-util "^2.2.5" +"@tiptap/core@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-3.6.2.tgz#abda4116e4a39779fca7070e316b9ed9fdcded7e" + integrity sha512-XKZYrCVFsyQGF6dXQR73YR222l/76wkKfZ+2/4LCrem5qtcOarmv5pYxjUBG8mRuBPskTTBImSFTeQltJIUNCg== + +"@tiptap/extension-blockquote@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-3.6.2.tgz#01b589565c87a691e586e189ddcbcdc5f35618fc" + integrity sha512-TSl41UZhi3ugJMDaf91CA4F5NeFylgTSm6GqnZAHOE6IREdCpAK3qej2zaW3EzfpzxW7sRGLlytkZRvpeyjgJA== + +"@tiptap/extension-bold@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-3.6.2.tgz#ed721961daf3210c7ba4433a5aeae981043c2d77" + integrity sha512-Q9KO8CCPCAXYqHzIw8b/ookVmrfqfCg2cyh9h9Hvw6nhO4LOOnJMcGVmWsrpFItbwCGMafI5iY9SbSj7RpCyuw== + +"@tiptap/extension-bubble-menu@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-3.6.2.tgz#237d84f217c8da52c0bc5265a36557fb27d64eaf" + integrity sha512-OF5CxCmYExcXZjcectwAeujSeDZ4IltPy+SsqBZLbQRDts9PQhzv5azGDvYdL2eMMkT3yhO2gWkXxSHMxI3O6w== + dependencies: + "@floating-ui/dom" "^1.0.0" + +"@tiptap/extension-bullet-list@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-3.6.2.tgz#be20b6c795c53bc0d199bdc4dd9f01b6270a1bee" + integrity sha512-Y5Uhir+za7xMm6RAe592aNNlLvCayVSQt2HfSckOr+c/v/Zd2bFUHv0ef6l/nUzUhDBs32Bg9SvfWx/yyMyNEw== + +"@tiptap/extension-code-block@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-3.6.2.tgz#cb3f6f607dcfb36e3eff25255fdcfdedfb3940a7" + integrity sha512-5jfoiQ/3AUrIyuVU1NmEXar6sZFnY7wDFf3ZU2zpcBUG++yg/CmpOe5bXpoolczhl58cM/jyBG5gumQjyOxLNg== + +"@tiptap/extension-code@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-3.6.2.tgz#5c6500d748fd4f52ddbe01ff114d4933c7a09e8f" + integrity sha512-U6jilbcpCxtLZAgJrTapXzzVJTXnS78kJITFSOLyGCTyGSm6PXatQ4hnaxVGmNet66GySONGjhwAVZ8+l94Rwg== + +"@tiptap/extension-document@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-3.6.2.tgz#5c3f3a85d12868f5d4e6d6d258b8fa0b8000b778" + integrity sha512-4qg3KWL3aO1M7hfDpZR6/vSo7Cfqr3McyGUfqb/BXqYDW1DwT8jJkDTcHrGU7WUKRlWgoyPyzM8pZiGlP0uQHg== + +"@tiptap/extension-dropcursor@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-dropcursor/-/extension-dropcursor-3.6.2.tgz#22a64a4da25ac17cf0cd33e1e924762000152817" + integrity sha512-6R5sma/i2TKd5h9OpIcy3a0wOGp5BNT/zIgnE/1HTmKi40eNcCAVe8sxd6+iWA5ETONP1E48kDy4hqA5ZzZCiQ== + +"@tiptap/extension-floating-menu@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-3.6.2.tgz#cc9c97cdd5fa55407631d3135e00ca8051516444" + integrity sha512-ym7YMKGY3QhFUKUS6JYOwtdi8s2PeGmOhu7TwI9/U0LmGbELeKJBJl2BP1yB+Sjpv25pVL++CwJQ6dsrjDlZ8g== + +"@tiptap/extension-gapcursor@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-3.6.2.tgz#790c94d20a5b8ded4c0d38960254d24704a2bc08" + integrity sha512-gXg+EvUKlv3ZO1GxKkRmAsi/V4yyA8AzLW6ppOcYrM2CKf6epmPaVRgAjdwHCA6cm3QuCBJyWeGTCAjhjNakhw== + +"@tiptap/extension-hard-break@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-3.6.2.tgz#3c379d9104cd7d9e942277f22ba62c57fae267ad" + integrity sha512-ncuPBHhGY58QjluJvEH6vXotaa1QZ/vphXBGAr55kiATZwMIEHgwh2Hgc6AiFTcw057gabGn6jNFDfRB+HjbmA== + +"@tiptap/extension-heading@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-3.6.2.tgz#3884c309de60c9d61f1bb60c521410b3a0d88ed7" + integrity sha512-JQ2yjwXGAiwGc+MhS1mULBr354MHfmWqVDQLRg8ey6LkdXggTDDJ1Ni3GrUS7B5YcA/ICdhr4krXaQpNkT5Syw== + +"@tiptap/extension-horizontal-rule@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.6.2.tgz#f5680b3209bc48bf8635f3674355bd3d47f15622" + integrity sha512-3TlPqedPDM9QkRTUPhOTxNxQVPSsBwlsuLrAZOgyM1y871Xi7M1DFX0h9LLXuqzPndYzUY16NjrfBGFJX+O56w== + +"@tiptap/extension-italic@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-3.6.2.tgz#ea314f5e723499c9e7a1021ad7836693db9c653c" + integrity sha512-46zYKqM3o9w1A2G9hWr0ERGbJpqIncoH45XIfLdAI6ZldZVVf+NeXMGwjOPf4+03cZ5/emk3MRTnVp9vF4ToIg== + +"@tiptap/extension-link@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-3.6.2.tgz#5577d100cd3b735247db327b15d91de025cc76b6" + integrity sha512-3yiRDWa187h30e6iUOJeejZLsbzbJthLfBwTeJGx7pHh7RngsEW82npBRuqLoI3udhJGTkXbzwAFZ9qOGOjl1Q== + dependencies: + linkifyjs "^4.3.2" + +"@tiptap/extension-list-item@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-3.6.2.tgz#705f782a872e4bbb6f0e125fe277c45aeefe8161" + integrity sha512-ma/D2GKylpNB04FfNI3tDMY+C9nz7Yk85H21YTIGv8QL5KlDK97L6orydmx6IVRc2nNMZQVitBIEKDOXcczX9w== + +"@tiptap/extension-list-keymap@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-list-keymap/-/extension-list-keymap-3.6.2.tgz#f14e173325b443a89dbbca7f418b76ec3d5c9a21" + integrity sha512-1kl/lggH+LL/FUwcSx8p761ebk9L5ZGK06mGyDDU9XiGLS310CktZYLnpEuFgn/oMPbRHo26oNl9SXLn1/U53A== + +"@tiptap/extension-list@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-list/-/extension-list-3.6.2.tgz#beb4d965f48085fa7f69197e10109cde8c175046" + integrity sha512-ZLaEHGVq4eL26hZZFE9e7RArk2rEjcVstN/YTRTKElTnLaf58kLTKN3nlgy1PWGwzfWGUuXURBuEBLaq5l6djg== + +"@tiptap/extension-ordered-list@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-3.6.2.tgz#43b83757f67264ff0050c03825e780da43680c1d" + integrity sha512-KdJ5MLIw19N+XiqQ2COXGtaq9TzUbtlLE5dgYCJQ2EumeZKIGELvUnHjrnIB9gH/gRlMs+hprLTh23xVUDJovg== + +"@tiptap/extension-paragraph@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-3.6.2.tgz#d6cc89cdc369e463dd7dd4eb9121718441c984a0" + integrity sha512-jeJWj2xKib3392iHQEcB7wYZ30dUgXuwqpCTwtN9eANor+Zvv6CpDKBs1R2al6BYFbIJCgKeTulqxce0yoC80g== + +"@tiptap/extension-strike@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-3.6.2.tgz#2dab3f253a4ecfd525c5609ab5edb9325a6364c2" + integrity sha512-976u5WaioIN/0xCjl/UIEypmzACzxgVz6OGgfIsYyreMUiPjhhgzXb0A/2Po5p3nZpKcaMcxifOdhqdw+lDpIQ== + +"@tiptap/extension-text@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-3.6.2.tgz#77313173a9f91208e40d298bc2d40b39371b8fca" + integrity sha512-fFSUEv1H3lM92yr6jZdELk0gog8rPTK5hTf08kP8RsY8pA80Br1ADVenejrMV4UNTmT1JWTXGBGhMqfQFHUvAQ== + +"@tiptap/extension-underline@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-underline/-/extension-underline-3.6.2.tgz#9f0dfb9722bd3d0cd144fc955bcb94a3fcf5eac2" + integrity sha512-IrG6vjxTMI2EeyhZCtx0sNTEu83PsAvzIh4vxmG1fUi/RYokks+sFbgGMuq0jtO96iVNEszlpAC/vaqfxFJwew== + +"@tiptap/extensions@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extensions/-/extensions-3.6.2.tgz#591fbd5b9fa41f98f69dbd7d21d5d38a2241d94b" + integrity sha512-tg7/DgaI6SpkeawryapUtNoBxsJUMJl3+nSjTfTvsaNXed+BHzLPsvmPbzlF9ScrAbVEx8nj6CCkneECYIQ4CQ== + +"@tiptap/pm@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/pm/-/pm-3.6.2.tgz#2121d4917f92d11229529a26955a7033aa8a8843" + integrity sha512-g+NXjqjbj6NfHOMl22uNWVYIu8oCq7RFfbnpohPMsSKJLaHYE8mJR++7T6P5R9FoqhIFdwizg1jTpwRU5CHqXQ== + dependencies: + prosemirror-changeset "^2.3.0" + prosemirror-collab "^1.3.1" + prosemirror-commands "^1.6.2" + prosemirror-dropcursor "^1.8.1" + prosemirror-gapcursor "^1.3.2" + prosemirror-history "^1.4.1" + prosemirror-inputrules "^1.4.0" + prosemirror-keymap "^1.2.2" + prosemirror-markdown "^1.13.1" + prosemirror-menu "^1.2.4" + prosemirror-model "^1.24.1" + prosemirror-schema-basic "^1.2.3" + prosemirror-schema-list "^1.5.0" + prosemirror-state "^1.4.3" + prosemirror-tables "^1.6.4" + prosemirror-trailing-node "^3.0.0" + prosemirror-transform "^1.10.2" + prosemirror-view "^1.38.1" + +"@tiptap/react@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/react/-/react-3.6.2.tgz#5495776c9051a60ece7522da176c9f211a67c7df" + integrity sha512-jgG+bM/GDvI6jnqW3YyLtr/vOR6iO2ta9PYVzoWqNYIxISsMOJeRfinsIqB8l6hkiGZApn9bQji6oUXTc59fgA== + dependencies: + "@types/use-sync-external-store" "^0.0.6" + fast-deep-equal "^3.1.3" + use-sync-external-store "^1.4.0" + optionalDependencies: + "@tiptap/extension-bubble-menu" "^3.6.2" + "@tiptap/extension-floating-menu" "^3.6.2" + +"@tiptap/starter-kit@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/starter-kit/-/starter-kit-3.6.2.tgz#ddd5612d4836a87082254779c9f152bb51e757bc" + integrity sha512-nPzraIx/f1cOUNqG1LSC0OTnEu3mudcN3jQVuyGh3dvdOnik7FUciJEVfHKnloAyeoijidEeiLpiGHInp2uREg== + dependencies: + "@tiptap/core" "^3.6.2" + "@tiptap/extension-blockquote" "^3.6.2" + "@tiptap/extension-bold" "^3.6.2" + "@tiptap/extension-bullet-list" "^3.6.2" + "@tiptap/extension-code" "^3.6.2" + "@tiptap/extension-code-block" "^3.6.2" + "@tiptap/extension-document" "^3.6.2" + "@tiptap/extension-dropcursor" "^3.6.2" + "@tiptap/extension-gapcursor" "^3.6.2" + "@tiptap/extension-hard-break" "^3.6.2" + "@tiptap/extension-heading" "^3.6.2" + "@tiptap/extension-horizontal-rule" "^3.6.2" + "@tiptap/extension-italic" "^3.6.2" + "@tiptap/extension-link" "^3.6.2" + "@tiptap/extension-list" "^3.6.2" + "@tiptap/extension-list-item" "^3.6.2" + "@tiptap/extension-list-keymap" "^3.6.2" + "@tiptap/extension-ordered-list" "^3.6.2" + "@tiptap/extension-paragraph" "^3.6.2" + "@tiptap/extension-strike" "^3.6.2" + "@tiptap/extension-text" "^3.6.2" + "@tiptap/extension-underline" "^3.6.2" + "@tiptap/extensions" "^3.6.2" + "@tiptap/pm" "^3.6.2" + "@tokenizer/inflate@^0.2.6": version "0.2.7" resolved "https://registry.yarnpkg.com/@tokenizer/inflate/-/inflate-0.2.7.tgz#32dd9dfc9abe457c89b3d9b760fc0690c85a103b" @@ -3014,6 +3214,11 @@ dependencies: "@types/node" "*" +"@types/linkify-it@^5": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-5.0.0.tgz#21413001973106cda1c3a9b91eedd4ccd5469d76" + integrity sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q== + "@types/lodash-es@^4.17.12": version "4.17.12" resolved "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz" @@ -3033,6 +3238,19 @@ dependencies: "@types/node" "*" +"@types/markdown-it@^14.0.0": + version "14.1.2" + resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-14.1.2.tgz#57f2532a0800067d9b934f3521429a2e8bfb4c61" + integrity sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog== + dependencies: + "@types/linkify-it" "^5" + "@types/mdurl" "^2" + +"@types/mdurl@^2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-2.0.0.tgz#d43878b5b20222682163ae6f897b20447233bdfd" + integrity sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg== + "@types/minimatch@*": version "5.1.2" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca" @@ -3125,6 +3343,11 @@ resolved "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz" integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA== +"@types/use-sync-external-store@^0.0.6": + version "0.0.6" + resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz#60be8d21baab8c305132eb9cb912ed497852aadc" + integrity sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg== + "@types/user-agents@^1.0.4": version "1.0.4" resolved "https://registry.npmjs.org/@types/user-agents/-/user-agents-1.0.4.tgz" @@ -4209,6 +4432,11 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== +crelt@^1.0.0: + version "1.0.6" + resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.6.tgz#7cc898ea74e190fb6ef9dae57f8f81cf7302df72" + integrity sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g== + cross-fetch-ponyfill@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/cross-fetch-ponyfill/-/cross-fetch-ponyfill-1.0.3.tgz#5c5524e3bd3374e71d5016c2327e416369a57527" @@ -6614,6 +6842,18 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== +linkify-it@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-5.0.0.tgz#9ef238bfa6dc70bd8e7f9572b52d369af569b421" + integrity sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ== + dependencies: + uc.micro "^2.0.0" + +linkifyjs@^4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-4.3.2.tgz#d97eb45419aabf97ceb4b05a7adeb7b8c8ade2b1" + integrity sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA== + locate-path@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" @@ -6779,6 +7019,11 @@ lru-cache@^7.7.1: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== +lucide-react@^0.544.0: + version "0.544.0" + resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.544.0.tgz#4719953c10fd53a64dd8343bb0ed16ec79f3eeef" + integrity sha512-t5tS44bqd825zAW45UQxpG2CvcC4urOwn2TrwSH8u+MjeE+1NnWl6QqeQ/6NdjMqdOygyiT9p3Ev0p1NJykxjw== + magic-string@^0.30.17: version "0.30.17" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453" @@ -6822,6 +7067,18 @@ make-fetch-happen@^10.2.1: socks-proxy-agent "^7.0.0" ssri "^9.0.0" +markdown-it@^14.0.0: + version "14.1.0" + resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-14.1.0.tgz#3c3c5992883c633db4714ccb4d7b5935d98b7d45" + integrity sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg== + dependencies: + argparse "^2.0.1" + entities "^4.4.0" + linkify-it "^5.0.0" + mdurl "^2.0.0" + punycode.js "^2.3.1" + uc.micro "^2.1.0" + matcher@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca" @@ -6844,6 +7101,11 @@ maybe-combine-errors@^1.0.0: resolved "https://registry.yarnpkg.com/maybe-combine-errors/-/maybe-combine-errors-1.0.0.tgz#e9592832e61fc47643a92cff3c1f33e27211e5be" integrity sha512-eefp6IduNPT6fVdwPp+1NgD0PML1NU5P6j1Mj5nz1nidX8/sWY7119WL8vTAHgqfsY74TzW0w1XPgdYEKkGZ5A== +mdurl@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-2.0.0.tgz#80676ec0433025dd3e17ee983d0fe8de5a2237e0" + integrity sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w== + meow@^12.0.1: version "12.1.1" resolved "https://registry.yarnpkg.com/meow/-/meow-12.1.1.tgz#e558dddbab12477b69b2e9a2728c327f191bace6" @@ -7264,6 +7526,11 @@ ora@^5.1.0: strip-ansi "^6.0.0" wcwidth "^1.0.1" +orderedmap@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/orderedmap/-/orderedmap-2.1.1.tgz#61481269c44031c449915497bf5a4ad273c512d2" + integrity sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g== + own-keys@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/own-keys/-/own-keys-1.0.1.tgz#e4006910a2bf913585289676eebd6f390cf51358" @@ -7499,6 +7766,160 @@ property-expr@^2.0.5: resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.6.tgz#f77bc00d5928a6c748414ad12882e83f24aec1e8" integrity sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA== +prosemirror-changeset@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/prosemirror-changeset/-/prosemirror-changeset-2.3.1.tgz#eee3299cfabc7a027694e9abdc4e85505e9dd5e7" + integrity sha512-j0kORIBm8ayJNl3zQvD1TTPHJX3g042et6y/KQhZhnPrruO8exkTgG8X+NRpj7kIyMMEx74Xb3DyMIBtO0IKkQ== + dependencies: + prosemirror-transform "^1.0.0" + +prosemirror-collab@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz#0e8c91e76e009b53457eb3b3051fb68dad029a33" + integrity sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ== + dependencies: + prosemirror-state "^1.0.0" + +prosemirror-commands@^1.0.0, prosemirror-commands@^1.6.2: + version "1.7.1" + resolved "https://registry.yarnpkg.com/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz#d101fef85618b1be53d5b99ea17bee5600781b38" + integrity sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w== + dependencies: + prosemirror-model "^1.0.0" + prosemirror-state "^1.0.0" + prosemirror-transform "^1.10.2" + +prosemirror-dropcursor@^1.8.1: + version "1.8.2" + resolved "https://registry.yarnpkg.com/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz#2ed30c4796109ddeb1cf7282372b3850528b7228" + integrity sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw== + dependencies: + prosemirror-state "^1.0.0" + prosemirror-transform "^1.1.0" + prosemirror-view "^1.1.0" + +prosemirror-gapcursor@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/prosemirror-gapcursor/-/prosemirror-gapcursor-1.3.2.tgz#5fa336b83789c6199a7341c9493587e249215cb4" + integrity sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ== + dependencies: + prosemirror-keymap "^1.0.0" + prosemirror-model "^1.0.0" + prosemirror-state "^1.0.0" + prosemirror-view "^1.0.0" + +prosemirror-history@^1.0.0, prosemirror-history@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/prosemirror-history/-/prosemirror-history-1.4.1.tgz#cc370a46fb629e83a33946a0e12612e934ab8b98" + integrity sha512-2JZD8z2JviJrboD9cPuX/Sv/1ChFng+xh2tChQ2X4bB2HeK+rra/bmJ3xGntCcjhOqIzSDG6Id7e8RJ9QPXLEQ== + dependencies: + prosemirror-state "^1.2.2" + prosemirror-transform "^1.0.0" + prosemirror-view "^1.31.0" + rope-sequence "^1.3.0" + +prosemirror-inputrules@^1.4.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/prosemirror-inputrules/-/prosemirror-inputrules-1.5.0.tgz#e22bfaf1d6ea4fe240ad447c184af3d520d43c37" + integrity sha512-K0xJRCmt+uSw7xesnHmcn72yBGTbY45vm8gXI4LZXbx2Z0jwh5aF9xrGQgrVPu0WbyFVFF3E/o9VhJYz6SQWnA== + dependencies: + prosemirror-state "^1.0.0" + prosemirror-transform "^1.0.0" + +prosemirror-keymap@^1.0.0, prosemirror-keymap@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz#c0f6ab95f75c0b82c97e44eb6aaf29cbfc150472" + integrity sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw== + dependencies: + prosemirror-state "^1.0.0" + w3c-keyname "^2.2.0" + +prosemirror-markdown@^1.13.1: + version "1.13.2" + resolved "https://registry.yarnpkg.com/prosemirror-markdown/-/prosemirror-markdown-1.13.2.tgz#863eb3fd5f57a444e4378174622b562735b1c503" + integrity sha512-FPD9rHPdA9fqzNmIIDhhnYQ6WgNoSWX9StUZ8LEKapaXU9i6XgykaHKhp6XMyXlOWetmaFgGDS/nu/w9/vUc5g== + dependencies: + "@types/markdown-it" "^14.0.0" + markdown-it "^14.0.0" + prosemirror-model "^1.25.0" + +prosemirror-menu@^1.2.4: + version "1.2.5" + resolved "https://registry.yarnpkg.com/prosemirror-menu/-/prosemirror-menu-1.2.5.tgz#dea00e7b623cea89f4d76963bee22d2ac2343250" + integrity sha512-qwXzynnpBIeg1D7BAtjOusR+81xCp53j7iWu/IargiRZqRjGIlQuu1f3jFi+ehrHhWMLoyOQTSRx/IWZJqOYtQ== + dependencies: + crelt "^1.0.0" + prosemirror-commands "^1.0.0" + prosemirror-history "^1.0.0" + prosemirror-state "^1.0.0" + +prosemirror-model@^1.0.0, prosemirror-model@^1.20.0, prosemirror-model@^1.21.0, prosemirror-model@^1.24.1, prosemirror-model@^1.25.0: + version "1.25.3" + resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.25.3.tgz#c657c60a361cb1e9c9f683d19118c0af50a6f7a9" + integrity sha512-dY2HdaNXlARknJbrManZ1WyUtos+AP97AmvqdOQtWtrrC5g4mohVX5DTi9rXNFSk09eczLq9GuNTtq3EfMeMGA== + dependencies: + orderedmap "^2.0.0" + +prosemirror-schema-basic@^1.2.3: + version "1.2.4" + resolved "https://registry.yarnpkg.com/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.4.tgz#389ce1ec09b8a30ea9bbb92c58569cb690c2d695" + integrity sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ== + dependencies: + prosemirror-model "^1.25.0" + +prosemirror-schema-list@^1.5.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz#5869c8f749e8745c394548bb11820b0feb1e32f5" + integrity sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q== + dependencies: + prosemirror-model "^1.0.0" + prosemirror-state "^1.0.0" + prosemirror-transform "^1.7.3" + +prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/prosemirror-state/-/prosemirror-state-1.4.3.tgz#94aecf3ffd54ec37e87aa7179d13508da181a080" + integrity sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q== + dependencies: + prosemirror-model "^1.0.0" + prosemirror-transform "^1.0.0" + prosemirror-view "^1.27.0" + +prosemirror-tables@^1.6.4: + version "1.8.1" + resolved "https://registry.yarnpkg.com/prosemirror-tables/-/prosemirror-tables-1.8.1.tgz#896a234e3e18240b629b747a871369dae78c8a9a" + integrity sha512-DAgDoUYHCcc6tOGpLVPSU1k84kCUWTWnfWX3UDy2Delv4ryH0KqTD6RBI6k4yi9j9I8gl3j8MkPpRD/vWPZbug== + dependencies: + prosemirror-keymap "^1.2.2" + prosemirror-model "^1.25.0" + prosemirror-state "^1.4.3" + prosemirror-transform "^1.10.3" + prosemirror-view "^1.39.1" + +prosemirror-trailing-node@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz#5bc223d4fc1e8d9145e4079ec77a932b54e19e04" + integrity sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ== + dependencies: + "@remirror/core-constants" "3.0.0" + escape-string-regexp "^4.0.0" + +prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transform@^1.10.2, prosemirror-transform@^1.10.3, prosemirror-transform@^1.7.3: + version "1.10.4" + resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.10.4.tgz#56419eac14f9f56612c806ae46f9238648f3f02e" + integrity sha512-pwDy22nAnGqNR1feOQKHxoFkkUtepoFAd3r2hbEDsnf4wp57kKA36hXsB3njA9FtONBEwSDnDeCiJe+ItD+ykw== + dependencies: + prosemirror-model "^1.21.0" + +prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.27.0, prosemirror-view@^1.31.0, prosemirror-view@^1.38.1, prosemirror-view@^1.39.1: + version "1.41.2" + resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.41.2.tgz#e69ad3883bfd3c9f3c9cf6da5cee940210df0b6f" + integrity sha512-PGS/jETmh+Qjmre/6vcG7SNHAKiGc4vKOJmHMPRmvcUl7ISuVtrtHmH06UDUwaim4NDJfZfVMl7U7JkMMETa6g== + dependencies: + prosemirror-model "^1.20.0" + prosemirror-state "^1.0.0" + prosemirror-transform "^1.1.0" + proxy-from-env@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" @@ -7517,6 +7938,11 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" +punycode.js@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode.js/-/punycode.js-2.3.1.tgz#6b53e56ad75588234e79f4affa90972c7dd8cdb7" + integrity sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA== + punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" @@ -7901,6 +8327,11 @@ rollup@^4.20.0: "@rollup/rollup-win32-x64-msvc" "4.23.0" fsevents "~2.3.2" +rope-sequence@^1.3.0: + version "1.3.4" + resolved "https://registry.yarnpkg.com/rope-sequence/-/rope-sequence-1.3.4.tgz#df85711aaecd32f1e756f76e43a415171235d425" + integrity sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ== + rrweb-cssom@^0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz#c73451a484b86dd7cfb1e0b2898df4b703183e4b" @@ -8902,6 +9333,11 @@ typescript@^5.3.3, typescript@^5.4.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.2.tgz#d1de67b6bef77c41823f822df8f0b3bcff60a5a0" integrity sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw== +uc.micro@^2.0.0, uc.micro@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-2.1.0.tgz#f8d3f7d0ec4c3dea35a7e3c8efa4cb8b45c9e7ee" + integrity sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A== + uint8-util@^2.2.2, uint8-util@^2.2.5: version "2.2.5" resolved "https://registry.yarnpkg.com/uint8-util/-/uint8-util-2.2.5.tgz#f1a8ff800e4e10a3ac1c82ee3667c99245123896" @@ -9021,6 +9457,11 @@ use-sync-external-store@^1.0.0: resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz#c3b6390f3a30eba13200d2302dcdf1e7b57b2ef9" integrity sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw== +use-sync-external-store@^1.4.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz#55122e2a3edd2a6c106174c27485e0fd59bcfca0" + integrity sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A== + user-agents@^1.1.387: version "1.1.387" resolved "https://registry.yarnpkg.com/user-agents/-/user-agents-1.1.387.tgz#afc69da00b50eee7ffa17724890e755a6672b99f" @@ -9087,6 +9528,11 @@ void-elements@3.1.0: resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w== +w3c-keyname@^2.2.0: + version "2.2.8" + resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz#7b17c8c6883d4e8b86ac8aba79d39e880f8869c5" + integrity sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ== + w3c-xmlserializer@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz#f925ba26855158594d907313cedd1476c5967f6c" From 461da55070f1bf9384d58519cc53e5cf60be3762 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Thu, 2 Oct 2025 00:45:33 +0300 Subject: [PATCH 002/197] fix: push fix --- .../events/catalogue/check-game-review.ts | 10 +- .../events/catalogue/create-game-review.ts | 2 +- src/main/events/catalogue/delete-review.ts | 2 +- src/main/events/catalogue/get-game-reviews.ts | 2 +- src/main/events/catalogue/vote-review.ts | 9 +- src/preload/index.ts | 10 +- .../src/components/game-card/game-card.tsx | 4 +- src/renderer/src/declaration.d.ts | 2 +- .../game-details/game-details-content.tsx | 284 +++++++++++------- .../game-details/game-details-skeleton.tsx | 6 +- .../src/pages/game-details/game-details.scss | 35 ++- .../game-details/review-prompt-banner.scss | 2 +- .../game-details/review-prompt-banner.tsx | 14 +- .../game-details/review-sort-options.scss | 4 +- .../game-details/review-sort-options.tsx | 21 +- 15 files changed, 242 insertions(+), 165 deletions(-) diff --git a/src/main/events/catalogue/check-game-review.ts b/src/main/events/catalogue/check-game-review.ts index c46ede07..5fa71e29 100644 --- a/src/main/events/catalogue/check-game-review.ts +++ b/src/main/events/catalogue/check-game-review.ts @@ -7,11 +7,9 @@ const checkGameReview = async ( shop: GameShop, objectId: string ) => { - return HydraApi.get( - `/games/${shop}/${objectId}/reviews/check`, - null, - { needsAuth: true } - ); + return HydraApi.get(`/games/${shop}/${objectId}/reviews/check`, null, { + needsAuth: true, + }); }; -registerEvent("checkGameReview", checkGameReview); \ No newline at end of file +registerEvent("checkGameReview", checkGameReview); diff --git a/src/main/events/catalogue/create-game-review.ts b/src/main/events/catalogue/create-game-review.ts index 7f29b639..57c74d45 100644 --- a/src/main/events/catalogue/create-game-review.ts +++ b/src/main/events/catalogue/create-game-review.ts @@ -15,4 +15,4 @@ const createGameReview = async ( }); }; -registerEvent("createGameReview", createGameReview); \ No newline at end of file +registerEvent("createGameReview", createGameReview); diff --git a/src/main/events/catalogue/delete-review.ts b/src/main/events/catalogue/delete-review.ts index 2048b3e7..e617a288 100644 --- a/src/main/events/catalogue/delete-review.ts +++ b/src/main/events/catalogue/delete-review.ts @@ -11,4 +11,4 @@ const deleteReview = async ( return HydraApi.delete(`/games/${shop}/${objectId}/reviews/${reviewId}`); }; -registerEvent("deleteReview", deleteReview); \ No newline at end of file +registerEvent("deleteReview", deleteReview); diff --git a/src/main/events/catalogue/get-game-reviews.ts b/src/main/events/catalogue/get-game-reviews.ts index d3c31780..8f29db3f 100644 --- a/src/main/events/catalogue/get-game-reviews.ts +++ b/src/main/events/catalogue/get-game-reviews.ts @@ -23,4 +23,4 @@ const getGameReviews = async ( ); }; -registerEvent("getGameReviews", getGameReviews); \ No newline at end of file +registerEvent("getGameReviews", getGameReviews); diff --git a/src/main/events/catalogue/vote-review.ts b/src/main/events/catalogue/vote-review.ts index b60062c3..a562eada 100644 --- a/src/main/events/catalogue/vote-review.ts +++ b/src/main/events/catalogue/vote-review.ts @@ -7,9 +7,12 @@ const voteReview = async ( shop: GameShop, objectId: string, reviewId: string, - voteType: 'upvote' | 'downvote' + voteType: "upvote" | "downvote" ) => { - return HydraApi.put(`/games/${shop}/${objectId}/reviews/${reviewId}/${voteType}`, {}); + return HydraApi.put( + `/games/${shop}/${objectId}/reviews/${reviewId}/${voteType}`, + {} + ); }; -registerEvent("voteReview", voteReview); \ No newline at end of file +registerEvent("voteReview", voteReview); diff --git a/src/preload/index.ts b/src/preload/index.ts index eda43369..7596fd11 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -82,7 +82,8 @@ contextBridge.exposeInMainWorld("electron", { objectId: string, reviewHtml: string, score: number - ) => ipcRenderer.invoke("createGameReview", shop, objectId, reviewHtml, score), + ) => + ipcRenderer.invoke("createGameReview", shop, objectId, reviewHtml, score), getGameReviews: ( shop: GameShop, objectId: string, @@ -96,11 +97,8 @@ contextBridge.exposeInMainWorld("electron", { reviewId: string, voteType: "upvote" | "downvote" ) => ipcRenderer.invoke("voteReview", shop, objectId, reviewId, voteType), - deleteReview: ( - shop: GameShop, - objectId: string, - reviewId: string - ) => ipcRenderer.invoke("deleteReview", shop, objectId, reviewId), + deleteReview: (shop: GameShop, objectId: string, reviewId: string) => + ipcRenderer.invoke("deleteReview", shop, objectId, reviewId), checkGameReview: (shop: GameShop, objectId: string) => ipcRenderer.invoke("checkGameReview", shop, objectId), onUpdateAchievements: ( diff --git a/src/renderer/src/components/game-card/game-card.tsx b/src/renderer/src/components/game-card/game-card.tsx index cb9a060c..15b5439b 100644 --- a/src/renderer/src/components/game-card/game-card.tsx +++ b/src/renderer/src/components/game-card/game-card.tsx @@ -110,9 +110,7 @@ export function GameCard({ game, ...props }: GameCardProps) { {stats?.averageScore && (
- - {stats.averageScore.toFixed(1)} - + {stats.averageScore.toFixed(1)}
)}
diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 752a1115..c1e06a89 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -110,7 +110,7 @@ declare global { shop: GameShop, objectId: string, reviewId: string, - voteType: 'upvote' | 'downvote' + voteType: "upvote" | "downvote" ) => Promise; deleteReview: ( shop: GameShop, diff --git a/src/renderer/src/pages/game-details/game-details-content.tsx b/src/renderer/src/pages/game-details/game-details-content.tsx index 48228e8e..0c53177f 100644 --- a/src/renderer/src/pages/game-details/game-details-content.tsx +++ b/src/renderer/src/pages/game-details/game-details-content.tsx @@ -2,11 +2,11 @@ import { useContext, useEffect, useMemo, useRef, useState } from "react"; import { PencilIcon, TrashIcon, ClockIcon } from "@primer/octicons-react"; import { ThumbsUp, ThumbsDown } from "lucide-react"; import { useNavigate } from "react-router-dom"; -import { useEditor, EditorContent } from '@tiptap/react'; -import StarterKit from '@tiptap/starter-kit'; -import Bold from '@tiptap/extension-bold'; -import Italic from '@tiptap/extension-italic'; -import Underline from '@tiptap/extension-underline'; +import { useEditor, EditorContent } from "@tiptap/react"; +import StarterKit from "@tiptap/starter-kit"; +import Bold from "@tiptap/extension-bold"; +import Italic from "@tiptap/extension-italic"; +import Underline from "@tiptap/extension-underline"; import type { GameReview } from "@types"; import { HeroPanel } from "./hero"; @@ -32,8 +32,14 @@ export function GameDetailsContent() { const { t } = useTranslation("game_details"); - const { objectId, shopDetails, game, hasNSFWContentBlocked, updateGame, shop } = - useContext(gameDetailsContext); + const { + objectId, + shopDetails, + game, + hasNSFWContentBlocked, + updateGame, + shop, + } = useContext(gameDetailsContext); const { showHydraCloudModal } = useSubscription(); @@ -93,7 +99,7 @@ export function GameDetailsContent() { const [backdropOpacity, setBackdropOpacity] = useState(1); const [showEditGameModal, setShowEditGameModal] = useState(false); const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false); - + // Reviews state management const [reviews, setReviews] = useState([]); const [reviewsLoading, setReviewsLoading] = useState(false); @@ -102,10 +108,12 @@ export function GameDetailsContent() { const [reviewsSortBy, setReviewsSortBy] = useState("newest"); const [reviewsPage, setReviewsPage] = useState(0); const [hasMoreReviews, setHasMoreReviews] = useState(true); - const [visibleBlockedReviews, setVisibleBlockedReviews] = useState>(new Set()); + const [visibleBlockedReviews, setVisibleBlockedReviews] = useState< + Set + >(new Set()); const [totalReviewCount, setTotalReviewCount] = useState(0); const [showReviewForm, setShowReviewForm] = useState(false); - + // Review prompt banner state const [showReviewPrompt, setShowReviewPrompt] = useState(false); const [hasUserReviewed, setHasUserReviewed] = useState(false); @@ -113,17 +121,12 @@ export function GameDetailsContent() { // Tiptap editor for review input const editor = useEditor({ - extensions: [ - StarterKit, - Bold, - Italic, - Underline, - ], - content: '', + extensions: [StarterKit, Bold, Italic, Underline], + content: "", editorProps: { attributes: { - class: 'game-details__review-editor', - 'data-placeholder': t("write_review_placeholder"), + class: "game-details__review-editor", + "data-placeholder": t("write_review_placeholder"), }, }, }); @@ -164,15 +167,19 @@ export function GameDetailsContent() { // Reviews functions const checkUserReview = async () => { if (!objectId || !userDetails) return; - + setReviewCheckLoading(true); try { const response = await window.electron.checkGameReview(shop, objectId); const hasReviewed = (response as any)?.hasReviewed || false; setHasUserReviewed(hasReviewed); - + // Show prompt only if user hasn't reviewed and has played the game - if (!hasReviewed && game?.playTimeInMilliseconds && game.playTimeInMilliseconds > 0) { + if ( + !hasReviewed && + game?.playTimeInMilliseconds && + game.playTimeInMilliseconds > 0 + ) { setShowReviewPrompt(true); } } catch (error) { @@ -184,7 +191,7 @@ export function GameDetailsContent() { const loadReviews = async (reset = false) => { if (!objectId) return; - + setReviewsLoading(true); try { const skip = reset ? 0 : reviewsPage * 20; @@ -195,19 +202,19 @@ export function GameDetailsContent() { skip, reviewsSortBy ); - + // Handle the response structure: { totalCount: number, reviews: Review[] } const reviewsData = (response as any)?.reviews || []; const reviewCount = (response as any)?.totalCount || 0; - + if (reset) { setReviews(reviewsData); setReviewsPage(0); setTotalReviewCount(reviewCount); } else { - setReviews(prev => [...prev, ...reviewsData]); + setReviews((prev) => [...prev, ...reviewsData]); } - + setHasMoreReviews(reviewsData.length === 20); } catch (error) { console.error("Failed to load reviews:", error); @@ -216,9 +223,12 @@ export function GameDetailsContent() { } }; - const handleVoteReview = async (reviewId: string, voteType: 'upvote' | 'downvote') => { + const handleVoteReview = async ( + reviewId: string, + voteType: "upvote" | "downvote" + ) => { if (!objectId) return; - + try { await window.electron.voteReview(shop, objectId, reviewId, voteType); // Reload reviews to get updated vote counts @@ -230,13 +240,13 @@ export function GameDetailsContent() { const handleDeleteReview = async (reviewId: string) => { if (!objectId) return; - + try { await window.electron.deleteReview(shop, objectId, reviewId); // Reload reviews after deletion loadReviews(true); } catch (error) { - console.error('Failed to delete review:', error); + console.error("Failed to delete review:", error); } }; @@ -244,17 +254,17 @@ export function GameDetailsContent() { console.log("handleSubmitReview called"); console.log("game:", game); console.log("objectId:", objectId); - - const reviewHtml = editor?.getHTML() || ''; + + const reviewHtml = editor?.getHTML() || ""; console.log("reviewHtml:", reviewHtml); console.log("reviewScore:", reviewScore); console.log("submittingReview:", submittingReview); - + if (!objectId || !reviewHtml.trim() || submittingReview) { console.log("Early return - validation failed"); return; } - + console.log("Starting review submission..."); setSubmittingReview(true); try { @@ -265,7 +275,7 @@ export function GameDetailsContent() { reviewHtml, reviewScore ); - + console.log("Review submitted successfully"); editor?.commands.clearContent(); setReviewScore(5); @@ -285,14 +295,16 @@ export function GameDetailsContent() { const handleReviewPromptYes = () => { setShowReviewPrompt(false); setShowReviewForm(true); - + // Scroll to review form setTimeout(() => { - const reviewFormElement = document.querySelector('.game-details__review-form'); + const reviewFormElement = document.querySelector( + ".game-details__review-form" + ); if (reviewFormElement) { - reviewFormElement.scrollIntoView({ - behavior: 'smooth', - block: 'start' + reviewFormElement.scrollIntoView({ + behavior: "smooth", + block: "start", }); } }, 100); @@ -310,7 +322,7 @@ export function GameDetailsContent() { }; const toggleBlockedReview = (reviewId: string) => { - setVisibleBlockedReviews(prev => { + setVisibleBlockedReviews((prev) => { const newSet = new Set(prev); if (newSet.has(reviewId)) { newSet.delete(reviewId); @@ -323,7 +335,7 @@ export function GameDetailsContent() { const loadMoreReviews = () => { if (!reviewsLoading && hasMoreReviews) { - setReviewsPage(prev => prev + 1); + setReviewsPage((prev) => prev + 1); loadReviews(false); } }; @@ -457,13 +469,17 @@ export function GameDetailsContent() {
{/* Review Prompt Banner */} - {showReviewPrompt && userDetails && game?.playTimeInMilliseconds && !hasUserReviewed && !reviewCheckLoading && ( - - )} - + {showReviewPrompt && + userDetails && + game?.playTimeInMilliseconds && + !hasUserReviewed && + !reviewCheckLoading && ( + + )} + @@ -472,10 +488,12 @@ export function GameDetailsContent() { __html: aboutTheGame, }} className={`game-details__description ${ - isDescriptionExpanded ? 'game-details__description--expanded' : 'game-details__description--collapsed' + isDescriptionExpanded + ? "game-details__description--expanded" + : "game-details__description--collapsed" }`} /> - + {aboutTheGame && aboutTheGame.length > 500 && (
- +
- +
- +
@@ -578,7 +610,7 @@ export function GameDetailsContent() { {totalReviewCount} - @@ -589,7 +621,7 @@ export function GameDetailsContent() { {t("loading_reviews")} )} - + {!reviewsLoading && reviews.length === 0 && (
📝
@@ -601,13 +633,14 @@ export function GameDetailsContent() {

)} - + {reviews.map((review, index) => (
- {review.isBlocked && !visibleBlockedReviews.has(review.id) ? ( + {review.isBlocked && + !visibleBlockedReviews.has(review.id) ? (
- Review from blocked user — -
-
- -
{userDetails?.id === review.user?.id && ( - )} - {review.isBlocked && visibleBlockedReviews.has(review.id) && ( - - )} + {review.isBlocked && + visibleBlockedReviews.has(review.id) && ( + + )}
)}
))} - + {hasMoreReviews && !reviewsLoading && (
diff --git a/src/renderer/src/pages/game-details/game-details.scss b/src/renderer/src/pages/game-details/game-details.scss index b82bd6b1..f6b724ab 100644 --- a/src/renderer/src/pages/game-details/game-details.scss +++ b/src/renderer/src/pages/game-details/game-details.scss @@ -86,7 +86,9 @@ $hero-height: 300px; font-size: globals.$body-font-size; font-family: inherit; cursor: pointer; - transition: border-color 0.2s ease, background-color 0.2s ease; + transition: + border-color 0.2s ease, + background-color 0.2s ease; &:focus { outline: none; @@ -126,7 +128,9 @@ $hero-height: 300px; font-size: globals.$body-font-size; font-family: inherit; cursor: pointer; - transition: border-color 0.2s ease, background-color 0.2s ease; + transition: + border-color 0.2s ease, + background-color 0.2s ease; &:focus { outline: none; @@ -148,7 +152,8 @@ $hero-height: 300px; background-color: rgba(255, 255, 255, 0.05); border: 1px solid globals.$border-color; color: globals.$body-color; - padding: calc(globals.$spacing-unit * 0.75) calc(globals.$spacing-unit * 1.5); + padding: calc(globals.$spacing-unit * 0.75) + calc(globals.$spacing-unit * 1.5); border-radius: 6px; cursor: pointer; font-size: globals.$small-font-size; @@ -631,9 +636,9 @@ $hero-height: 300px; max-height: 300px; overflow: hidden; position: relative; - + &::after { - content: ''; + content: ""; position: absolute; bottom: 0; left: 0; @@ -677,7 +682,8 @@ $hero-height: 300px; background: none; border: 1px solid globals.$border-color; color: globals.$body-color; - padding: calc(globals.$spacing-unit * 0.75) calc(globals.$spacing-unit * 1.5); + padding: calc(globals.$spacing-unit * 0.75) + calc(globals.$spacing-unit * 1.5); border-radius: 4px; cursor: pointer; font-size: globals.$body-font-size; @@ -883,8 +889,13 @@ $hero-height: 300px; display: flex; align-items: center; gap: calc(globals.$spacing-unit * 0.5); - padding: calc(globals.$spacing-unit * 0.75) calc(globals.$spacing-unit * 1.5); - background: linear-gradient(135deg, globals.$brand-teal, globals.$brand-blue); + padding: calc(globals.$spacing-unit * 0.75) + calc(globals.$spacing-unit * 1.5); + background: linear-gradient( + 135deg, + globals.$brand-teal, + globals.$brand-blue + ); color: white; border: none; border-radius: 8px; @@ -980,7 +991,9 @@ $hero-height: 300px; font-family: inherit; line-height: 1.5; min-height: 100px; - transition: border-color 0.2s ease, background-color 0.2s ease; + transition: + border-color 0.2s ease, + background-color 0.2s ease; &:focus-within { outline: none; @@ -995,7 +1008,7 @@ $hero-height: 300px; .ProseMirror { outline: none; min-height: 80px; - + &:empty:before { content: attr(data-placeholder); color: rgba(208, 209, 215, 0.6); @@ -1004,7 +1017,7 @@ $hero-height: 300px; p { margin: 0 0 8px 0; - + &:last-child { margin-bottom: 0; } diff --git a/src/renderer/src/pages/game-details/review-prompt-banner.scss b/src/renderer/src/pages/game-details/review-prompt-banner.scss index 28ba1e47..b8f7557b 100644 --- a/src/renderer/src/pages/game-details/review-prompt-banner.scss +++ b/src/renderer/src/pages/game-details/review-prompt-banner.scss @@ -43,4 +43,4 @@ gap: globals.$spacing-unit; align-items: center; } -} \ No newline at end of file +} diff --git a/src/renderer/src/pages/game-details/review-prompt-banner.tsx b/src/renderer/src/pages/game-details/review-prompt-banner.tsx index 87c1b170..7bd96613 100644 --- a/src/renderer/src/pages/game-details/review-prompt-banner.tsx +++ b/src/renderer/src/pages/game-details/review-prompt-banner.tsx @@ -18,27 +18,21 @@ export function ReviewPromptBanner({
- You've seemed to enjoy this game + You've seemed to enjoy this game {t("would_you_recommend_this_game")}
- -
); -} \ No newline at end of file +} diff --git a/src/renderer/src/pages/game-details/review-sort-options.scss b/src/renderer/src/pages/game-details/review-sort-options.scss index e982cb24..5b374728 100644 --- a/src/renderer/src/pages/game-details/review-sort-options.scss +++ b/src/renderer/src/pages/game-details/review-sort-options.scss @@ -50,7 +50,7 @@ span { display: inline-block; - + @media (max-width: 480px) { display: none; } @@ -69,4 +69,4 @@ display: none; } } -} \ No newline at end of file +} diff --git a/src/renderer/src/pages/game-details/review-sort-options.tsx b/src/renderer/src/pages/game-details/review-sort-options.tsx index 5ec25c31..858faefd 100644 --- a/src/renderer/src/pages/game-details/review-sort-options.tsx +++ b/src/renderer/src/pages/game-details/review-sort-options.tsx @@ -1,15 +1,28 @@ -import { CalendarIcon, StarIcon, ThumbsupIcon, ClockIcon } from "@primer/octicons-react"; +import { + CalendarIcon, + StarIcon, + ThumbsupIcon, + ClockIcon, +} from "@primer/octicons-react"; import { useTranslation } from "react-i18next"; import "./review-sort-options.scss"; -type ReviewSortOption = "newest" | "oldest" | "score_high" | "score_low" | "most_voted"; +type ReviewSortOption = + | "newest" + | "oldest" + | "score_high" + | "score_low" + | "most_voted"; interface ReviewSortOptionsProps { sortBy: ReviewSortOption; onSortChange: (sortBy: ReviewSortOption) => void; } -export function ReviewSortOptions({ sortBy, onSortChange }: ReviewSortOptionsProps) { +export function ReviewSortOptions({ + sortBy, + onSortChange, +}: ReviewSortOptionsProps) { const { t } = useTranslation("game_details"); return ( @@ -57,4 +70,4 @@ export function ReviewSortOptions({ sortBy, onSortChange }: ReviewSortOptionsPro ); -} \ No newline at end of file +} From 19cf24ef489e04045c9b9d3c8e3facb8e4c23f06 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Thu, 2 Oct 2025 01:30:44 +0300 Subject: [PATCH 003/197] feat: changed profile pictures in reviews to squares, changed sorting buttons --- .../game-details/game-details-content.tsx | 2 +- .../src/pages/game-details/game-details.scss | 2 +- .../game-details/review-sort-options.scss | 15 +++++ .../game-details/review-sort-options.tsx | 59 ++++++++++--------- 4 files changed, 47 insertions(+), 31 deletions(-) diff --git a/src/renderer/src/pages/game-details/game-details-content.tsx b/src/renderer/src/pages/game-details/game-details-content.tsx index 0c53177f..6f2558ef 100644 --- a/src/renderer/src/pages/game-details/game-details-content.tsx +++ b/src/renderer/src/pages/game-details/game-details-content.tsx @@ -666,7 +666,7 @@ export function GameDetailsContent() { navigate(`/profile/${review.user.id}`) } onKeyDown={(e) => { - if (e.key === 'Enter' || e.key === ' ') { + if (e.key === "Enter" || e.key === " ") { e.preventDefault(); review.user?.id && navigate(`/profile/${review.user.id}`); diff --git a/src/renderer/src/pages/game-details/game-details.scss b/src/renderer/src/pages/game-details/game-details.scss index f6b724ab..fe097839 100644 --- a/src/renderer/src/pages/game-details/game-details.scss +++ b/src/renderer/src/pages/game-details/game-details.scss @@ -252,7 +252,7 @@ $hero-height: 300px; &__review-avatar { width: 32px; height: 32px; - border-radius: 50%; + border-radius: 4px; object-fit: cover; border: 2px solid rgba(255, 255, 255, 0.1); } diff --git a/src/renderer/src/pages/game-details/review-sort-options.scss b/src/renderer/src/pages/game-details/review-sort-options.scss index 5b374728..eafe9972 100644 --- a/src/renderer/src/pages/game-details/review-sort-options.scss +++ b/src/renderer/src/pages/game-details/review-sort-options.scss @@ -61,6 +61,21 @@ } } + &__toggle-option { + &.active { + background: rgba(255, 255, 255, 0.05); + border-radius: 4px; + padding: 6px 8px; + border: 1px solid rgba(255, 255, 255, 0.1); + } + + &:hover:not(:disabled) { + background: rgba(255, 255, 255, 0.03); + border-radius: 4px; + padding: 6px 8px; + } + } + &__separator { color: rgba(255, 255, 255, 0.3); font-size: 14px; diff --git a/src/renderer/src/pages/game-details/review-sort-options.tsx b/src/renderer/src/pages/game-details/review-sort-options.tsx index 858faefd..fc7f431a 100644 --- a/src/renderer/src/pages/game-details/review-sort-options.tsx +++ b/src/renderer/src/pages/game-details/review-sort-options.tsx @@ -1,8 +1,7 @@ import { - CalendarIcon, - StarIcon, ThumbsupIcon, - ClockIcon, + ChevronUpIcon, + ChevronDownIcon, } from "@primer/octicons-react"; import { useTranslation } from "react-i18next"; import "./review-sort-options.scss"; @@ -25,44 +24,46 @@ export function ReviewSortOptions({ }: ReviewSortOptionsProps) { const { t } = useTranslation("game_details"); + const handleDateToggle = () => { + const newSort = sortBy === "newest" ? "oldest" : "newest"; + onSortChange(newSort); + }; + + const handleScoreToggle = () => { + const newSort = sortBy === "score_high" ? "score_low" : "score_high"; + onSortChange(newSort); + }; + + const handleMostVotedClick = () => { + onSortChange("most_voted"); + }; + + const isDateActive = sortBy === "newest" || sortBy === "oldest"; + const isScoreActive = sortBy === "score_high" || sortBy === "score_low"; + const isMostVotedActive = sortBy === "most_voted"; + return (
| | - | - - | - )} -
- {showReviewForm && ( - <> -
-

- {t("leave_a_review")} -

-
+ {game?.shop !== "custom" && ( +
+ {showReviewForm && ( + <> +
+

+ {t("leave_a_review")} +

+
+ +
+
+ +
+
+ + + +
-
-
- -
-
- -
+
- +
+
+ + +
+ + )} -
-
- - -
+ {showReviewForm && ( +
+ )} + +
+
+
+

+ {t("reviews")} +

+ + {totalReviewCount} +
- - )} - - {showReviewForm && ( -
- )} - -
-
-
-

- {t("reviews")} -

- - {totalReviewCount} - -
-
- {reviewsLoading && reviews.length === 0 && ( -
- {t("loading_reviews")} -
- )} + {reviewsLoading && reviews.length === 0 && ( +
+ {t("loading_reviews")} +
+ )} - {!reviewsLoading && reviews.length === 0 && ( -
-
📝
-

- {t("no_reviews_yet")} -

-

- {t("be_first_to_review")} -

-
- )} + {!reviewsLoading && reviews.length === 0 && ( +
+
📝
+

+ {t("no_reviews_yet")} +

+

+ {t("be_first_to_review")} +

+
+ )} - {reviews.map((review, index) => ( -
- {review.isBlocked && - !visibleBlockedReviews.has(review.id) ? ( -
- Review from blocked user — - -
- ) : ( - <> -
-
- {review.user?.profileImageUrl && ( - {review.user.displayName - )} -
-
- review.user?.id && - navigate(`/profile/${review.user.id}`) - } - onKeyDown={(e) => { - if (e.key === "Enter" || e.key === " ") { - e.preventDefault(); + {reviews.map((review, index) => ( +
+ {review.isBlocked && + !visibleBlockedReviews.has(review.id) ? ( +
+ Review from blocked user — + +
+ ) : ( + <> +
+
+ {review.user?.profileImageUrl && ( + {review.user.displayName + )} +
+
review.user?.id && - navigate(`/profile/${review.user.id}`); + navigate(`/profile/${review.user.id}`) } - }} - role="button" - tabIndex={0} - > - {review.user?.displayName || "Anonymous"} -
-
- - {formatDistance( - new Date(review.createdAt), - new Date(), - { addSuffix: true } - )} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + review.user?.id && + navigate(`/profile/${review.user.id}`); + } + }} + role="button" + tabIndex={0} + > + {review.user?.displayName || "Anonymous"} +
+
+ + {formatDistance( + new Date(review.createdAt), + new Date(), + { addSuffix: true } + )} +
+
+ {review.score}/10 +
-
- {review.score}/10 -
-
-
-
-
- - -
- {userDetails?.id === review.user?.id && ( - - )} - {review.isBlocked && - visibleBlockedReviews.has(review.id) && ( +
+
+
+ +
+ {userDetails?.id === review.user?.id && ( + )} -
- - )} -
- ))} + {review.isBlocked && + visibleBlockedReviews.has(review.id) && ( + + )} +
+ + )} +
+ ))} - {hasMoreReviews && !reviewsLoading && ( - - )} + {hasMoreReviews && !reviewsLoading && ( + + )} - {reviewsLoading && reviews.length > 0 && ( -
- {t("loading_more_reviews")} -
- )} + {reviewsLoading && reviews.length > 0 && ( +
+ {t("loading_more_reviews")} +
+ )} +
-
+ )}
{game?.shop !== "custom" && } @@ -773,6 +782,15 @@ export function GameDetailsContent() { onGameUpdated={handleGameUpdated} /> )} + + { + setShowDeleteReviewModal(false); + setReviewToDelete(null); + }} + onConfirm={confirmDeleteReview} + />
); } diff --git a/src/renderer/src/pages/game-details/game-details.scss b/src/renderer/src/pages/game-details/game-details.scss index fe097839..da3745e9 100644 --- a/src/renderer/src/pages/game-details/game-details.scss +++ b/src/renderer/src/pages/game-details/game-details.scss @@ -196,7 +196,6 @@ $hero-height: 300px; display: flex; justify-content: space-between; align-items: center; - margin-bottom: calc(globals.$spacing-unit * 2); padding-bottom: calc(globals.$spacing-unit * 1); } @@ -850,7 +849,6 @@ $hero-height: 300px; justify-content: space-between; align-items: center; margin-bottom: calc(globals.$spacing-unit * 2); - gap: calc(globals.$spacing-unit * 2); @media (max-width: 768px) { flex-direction: column; diff --git a/src/renderer/src/pages/game-details/modals/delete-review-modal.scss b/src/renderer/src/pages/game-details/modals/delete-review-modal.scss new file mode 100644 index 00000000..40ad6e59 --- /dev/null +++ b/src/renderer/src/pages/game-details/modals/delete-review-modal.scss @@ -0,0 +1,19 @@ +@use "../../../scss/globals.scss"; + +.delete-review-modal { + &__karma-warning { + background-color: rgba(255, 193, 7, 0.1); + border: 1px solid rgba(255, 193, 7, 0.3); + border-radius: 4px; + padding: 12px; + margin-bottom: 16px; + color: #ffc107; + font-size: 14px; + font-weight: 500; + } + + &__actions { + display: flex; + justify-content: flex-end; + } +} \ No newline at end of file diff --git a/src/renderer/src/pages/game-details/modals/delete-review-modal.tsx b/src/renderer/src/pages/game-details/modals/delete-review-modal.tsx new file mode 100644 index 00000000..45501b88 --- /dev/null +++ b/src/renderer/src/pages/game-details/modals/delete-review-modal.tsx @@ -0,0 +1,45 @@ +import { useTranslation } from "react-i18next"; +import { Button, Modal } from "@renderer/components"; +import "./delete-review-modal.scss"; + +interface DeleteReviewModalProps { + visible: boolean; + onClose: () => void; + onConfirm: () => void; +} + +export function DeleteReviewModal({ + visible, + onClose, + onConfirm, +}: DeleteReviewModalProps) { + const { t } = useTranslation("game_details"); + + const handleDeleteReview = () => { + onConfirm(); + onClose(); + }; + + return ( + +
+ {t("delete_review_karma_warning")} +
+ +
+ + + +
+
+ ); +} \ No newline at end of file diff --git a/src/renderer/src/pages/game-details/modals/index.ts b/src/renderer/src/pages/game-details/modals/index.ts index 724e0003..d0f27b0f 100644 --- a/src/renderer/src/pages/game-details/modals/index.ts +++ b/src/renderer/src/pages/game-details/modals/index.ts @@ -2,3 +2,4 @@ export * from "./repacks-modal"; export * from "./download-settings-modal"; export * from "./game-options-modal"; export * from "./edit-game-modal"; +export * from "./delete-review-modal"; diff --git a/src/renderer/src/pages/game-details/review-sort-options.scss b/src/renderer/src/pages/game-details/review-sort-options.scss index eafe9972..fba6c50f 100644 --- a/src/renderer/src/pages/game-details/review-sort-options.scss +++ b/src/renderer/src/pages/game-details/review-sort-options.scss @@ -6,6 +6,7 @@ flex-direction: column; align-items: flex-start; gap: calc(globals.$spacing-unit); + margin-bottom: calc(globals.$spacing-unit * 3); } &__label { @@ -37,7 +38,7 @@ transition: all ease 0.2s; display: flex; align-items: center; - gap: 6px; + gap: 4px; &:hover:not(:disabled) { color: rgba(255, 255, 255, 0.6); @@ -61,21 +62,6 @@ } } - &__toggle-option { - &.active { - background: rgba(255, 255, 255, 0.05); - border-radius: 4px; - padding: 6px 8px; - border: 1px solid rgba(255, 255, 255, 0.1); - } - - &:hover:not(:disabled) { - background: rgba(255, 255, 255, 0.03); - border-radius: 4px; - padding: 6px 8px; - } - } - &__separator { color: rgba(255, 255, 255, 0.3); font-size: 14px; diff --git a/src/renderer/src/pages/game-details/review-sort-options.tsx b/src/renderer/src/pages/game-details/review-sort-options.tsx index fc7f431a..ca11d056 100644 --- a/src/renderer/src/pages/game-details/review-sort-options.tsx +++ b/src/renderer/src/pages/game-details/review-sort-options.tsx @@ -49,16 +49,30 @@ export function ReviewSortOptions({ className={`review-sort-options__option review-sort-options__toggle-option ${isDateActive ? "active" : ""}`} onClick={handleDateToggle} > - {sortBy === "newest" ? : } - {sortBy === "oldest" ? t("sort_oldest") : t("sort_newest")} + {sortBy === "newest" ? ( + + ) : ( + + )} + + {sortBy === "oldest" ? t("sort_oldest") : t("sort_newest")} + | |
); -} \ No newline at end of file +} diff --git a/src/renderer/src/pages/profile/profile-content/user-karma-box.scss b/src/renderer/src/pages/profile/profile-content/user-karma-box.scss index 5f5610e3..63015b4d 100644 --- a/src/renderer/src/pages/profile/profile-content/user-karma-box.scss +++ b/src/renderer/src/pages/profile/profile-content/user-karma-box.scss @@ -44,4 +44,4 @@ font-size: 0.85rem; line-height: 1.4; } -} \ No newline at end of file +} diff --git a/src/renderer/src/pages/profile/profile-content/user-karma-box.tsx b/src/renderer/src/pages/profile/profile-content/user-karma-box.tsx index 4668bf28..fa69d88f 100644 --- a/src/renderer/src/pages/profile/profile-content/user-karma-box.tsx +++ b/src/renderer/src/pages/profile/profile-content/user-karma-box.tsx @@ -25,7 +25,8 @@ export function UserKarmaBox() {

- {numberFormatter.format(userDetails.karma)} {t("karma_count")} + {numberFormatter.format(userDetails.karma)}{" "} + {t("karma_count")}

@@ -37,4 +38,4 @@ export function UserKarmaBox() {
); -} \ No newline at end of file +} From 80275dc08fb466d5983f029c695818031445c2a1 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Thu, 2 Oct 2025 19:29:50 +0300 Subject: [PATCH 006/197] Fix: Review delete modal button color + added missing translation --- src/locales/en/translation.json | 5 +++-- .../src/pages/game-details/modals/delete-review-modal.tsx | 8 ++------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 5326b24b..670dda4f 100755 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -319,8 +319,9 @@ "filter_by_source": "Filter by source", "no_repacks_found": "No sources found for this game", "delete_review": "Delete review", - "delete_review_modal_title": "Delete Review", - "delete_review_modal_description": "Are you sure you want to delete your review? This action cannot be undone.", + "delete_review_modal_title": "Are you sure you want to delete your review?", + "delete_review_modal_description": "This action cannot be undone.", + "delete_review_button": "Delete", "delete_review_karma_warning": "You will lose any karma points earned from this review." }, "activation": { diff --git a/src/renderer/src/pages/game-details/modals/delete-review-modal.tsx b/src/renderer/src/pages/game-details/modals/delete-review-modal.tsx index fb1ef992..958c23db 100644 --- a/src/renderer/src/pages/game-details/modals/delete-review-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/delete-review-modal.tsx @@ -27,17 +27,13 @@ export function DeleteReviewModal({ description={t("delete_review_modal_description")} onClose={onClose} > -
- {t("delete_review_karma_warning")} -
-
-
From 8d5b169166ae984bc7f37da26bcd0d1dc04b15b2 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Thu, 2 Oct 2025 21:22:09 +0300 Subject: [PATCH 007/197] Feat: updated input field design, fixed text overflow --- .../confirm-modal/confirm-modal.tsx | 13 +- .../game-details/game-details.context.tsx | 3 +- .../game-details/game-details-content.tsx | 68 +++++-- .../src/pages/game-details/game-details.scss | 185 ++++++------------ .../game-details/hero/hero-panel-actions.tsx | 32 ++- .../game-details/modals/repacks-modal.tsx | 2 +- 6 files changed, 153 insertions(+), 150 deletions(-) diff --git a/src/renderer/src/components/confirm-modal/confirm-modal.tsx b/src/renderer/src/components/confirm-modal/confirm-modal.tsx index d210c035..75a8f5c9 100644 --- a/src/renderer/src/components/confirm-modal/confirm-modal.tsx +++ b/src/renderer/src/components/confirm-modal/confirm-modal.tsx @@ -33,9 +33,18 @@ export function ConfirmModal({ }; return ( - +
- diff --git a/src/renderer/src/context/game-details/game-details.context.tsx b/src/renderer/src/context/game-details/game-details.context.tsx index 23ea3845..5be5cf98 100644 --- a/src/renderer/src/context/game-details/game-details.context.tsx +++ b/src/renderer/src/context/game-details/game-details.context.tsx @@ -201,7 +201,8 @@ export function GameDetailsContextProvider({ }, [objectId, gameTitle, dispatch]); useEffect(() => { - const state = (location && (location.state as Record)) || {}; + const state = + (location && (location.state as Record)) || {}; if (state.openRepacks) { setShowRepacksModal(true); try { diff --git a/src/renderer/src/pages/game-details/game-details-content.tsx b/src/renderer/src/pages/game-details/game-details-content.tsx index de66e5a6..bb6c2e85 100644 --- a/src/renderer/src/pages/game-details/game-details-content.tsx +++ b/src/renderer/src/pages/game-details/game-details-content.tsx @@ -104,6 +104,8 @@ export function GameDetailsContent() { const [reviewsLoading, setReviewsLoading] = useState(false); const [reviewScore, setReviewScore] = useState(5); const [submittingReview, setSubmittingReview] = useState(false); + const [reviewCharCount, setReviewCharCount] = useState(0); + const MAX_REVIEW_CHARS = 1000; const [reviewsSortBy, setReviewsSortBy] = useState("newest"); const [reviewsPage, setReviewsPage] = useState(0); const [hasMoreReviews, setHasMoreReviews] = useState(true); @@ -127,6 +129,31 @@ export function GameDetailsContent() { class: "game-details__review-editor", "data-placeholder": t("write_review_placeholder"), }, + handlePaste: (view, event) => { + // Strip formatting from pasted content to prevent overflow issues + const text = event.clipboardData?.getData('text/plain') || ''; + const currentText = view.state.doc.textContent; + const remainingChars = MAX_REVIEW_CHARS - currentText.length; + + if (text && remainingChars > 0) { + event.preventDefault(); + const truncatedText = text.slice(0, remainingChars); + view.dispatch(view.state.tr.insertText(truncatedText)); + return true; + } + return false; + }, + }, + onUpdate: ({ editor }) => { + const text = editor.getText(); + setReviewCharCount(text.length); + + // Prevent typing beyond character limit + if (text.length > MAX_REVIEW_CHARS) { + const truncatedContent = text.slice(0, MAX_REVIEW_CHARS); + editor.commands.setContent(truncatedContent); + setReviewCharCount(MAX_REVIEW_CHARS); + } }, }); @@ -266,7 +293,7 @@ export function GameDetailsContent() { console.log("reviewScore:", reviewScore); console.log("submittingReview:", submittingReview); - if (!objectId || !reviewHtml.trim() || submittingReview) { + if (!objectId || !reviewHtml.trim() || submittingReview || reviewCharCount > MAX_REVIEW_CHARS) { console.log("Early return - validation failed"); return; } @@ -523,11 +550,7 @@ export function GameDetailsContent() {
- -
+
- - +
+ MAX_REVIEW_CHARS ? "over-limit" : ""}> + {reviewCharCount}/{MAX_REVIEW_CHARS} + +
+
@@ -599,6 +619,18 @@ export function GameDetailsContent() {
+ +
diff --git a/src/renderer/src/pages/game-details/game-details.scss b/src/renderer/src/pages/game-details/game-details.scss index da3745e9..e9e94aea 100644 --- a/src/renderer/src/pages/game-details/game-details.scss +++ b/src/renderer/src/pages/game-details/game-details.scss @@ -30,12 +30,8 @@ $hero-height: 300px; &__review-form { display: flex; flex-direction: column; - gap: calc(globals.$spacing-unit * 2); - margin-bottom: calc(globals.$spacing-unit * 3); - padding: calc(globals.$spacing-unit * 2); - background: rgba(255, 255, 255, 0.02); - border-radius: 8px; - border: 1px solid rgba(255, 255, 255, 0.05); + gap: 16px; + margin-bottom: 24px; } &__review-form-controls { @@ -54,55 +50,35 @@ $hero-height: 300px; &__review-form-bottom { display: flex; justify-content: space-between; - align-items: flex-end; - gap: calc(globals.$spacing-unit * 2); - - @media (max-width: 768px) { - flex-direction: column; - align-items: stretch; - gap: calc(globals.$spacing-unit * 1.5); - } + align-items: center; + gap: 16px; + flex-wrap: wrap; } &__review-score-container { display: flex; - flex-direction: column; - gap: calc(globals.$spacing-unit * 0.75); - min-width: 120px; + align-items: center; + gap: 8px; } &__review-score-label { - display: block; - font-size: globals.$body-font-size; - color: globals.$body-color; + font-size: 14px; + color: #ffffff; + font-weight: 500; } &__review-score-select { - background-color: rgba(255, 255, 255, 0.05); - border: 1px solid globals.$border-color; + background-color: #2a2a2a; + border: 1px solid #3a3a3a; border-radius: 4px; - padding: calc(globals.$spacing-unit * 0.75) calc(globals.$spacing-unit * 1); - color: globals.$body-color; - font-size: globals.$body-font-size; - font-family: inherit; + color: #ffffff; + padding: 6px 12px; + font-size: 14px; cursor: pointer; - transition: - border-color 0.2s ease, - background-color 0.2s ease; &:focus { outline: none; - background-color: rgba(255, 255, 255, 0.08); - border-color: globals.$brand-teal; - } - - &:hover { - border-color: rgba(255, 255, 255, 0.15); - } - - option { - background-color: globals.$dark-background-color; - color: globals.$body-color; + border-color: #0078d4; } } @@ -150,19 +126,14 @@ $hero-height: 300px; &__review-submit-button { background-color: rgba(255, 255, 255, 0.05); - border: 1px solid globals.$border-color; - color: globals.$body-color; - padding: calc(globals.$spacing-unit * 0.75) - calc(globals.$spacing-unit * 1.5); + border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 6px; + color: #ffffff; + padding: 10px 20px; + font-size: 14px; + font-weight: 500; cursor: pointer; - font-size: globals.$small-font-size; - font-family: inherit; - transition: all ease 0.2s; - height: 32px; - display: flex; - align-items: center; - justify-content: center; + transition: all 0.2s ease; white-space: nowrap; &:hover:not(:disabled) { @@ -170,12 +141,8 @@ $hero-height: 300px; border-color: rgba(255, 255, 255, 0.15); } - &:active { - opacity: 0.9; - } - &:disabled { - background: rgba(255, 255, 255, 0.1); + background-color: rgba(255, 255, 255, 0.1); cursor: not-allowed; color: rgba(255, 255, 255, 0.5); } @@ -918,111 +885,87 @@ $hero-height: 300px; } &__review-input-container { - width: 100%; - position: relative; + display: flex; + flex-direction: column; + border: 1px solid #3a3a3a; + border-radius: 8px; + background-color: #1e1e1e; + overflow: hidden; } - &__review-input-bottom { - position: absolute; - bottom: 8px; - left: 8px; - right: 8px; + &__review-input-header { display: flex; justify-content: space-between; align-items: center; - gap: 8px; - z-index: 10; + padding: 8px 12px; + background-color: #2a2a2a; + border-bottom: 1px solid #3a3a3a; } &__review-editor-toolbar { display: flex; gap: 4px; - border: 1px solid rgba(255, 255, 255, 0.1); - border-radius: 4px; - padding: 4px; - backdrop-filter: blur(10px); - background: rgba(0, 0, 0, 0.3); } &__editor-button { - background: rgba(255, 255, 255, 0.05); - border: 1px solid rgba(255, 255, 255, 0.1); - border-radius: 3px; - color: globals.$body-color; - padding: 4px 6px; + background: none; + border: 1px solid #4a4a4a; + border-radius: 4px; + color: #ffffff; + padding: 4px 8px; cursor: pointer; font-size: 12px; - font-weight: 600; transition: all 0.2s ease; - min-width: 24px; - height: 24px; - display: flex; - align-items: center; - justify-content: center; - &:hover:not(:disabled) { - background: rgba(255, 255, 255, 0.1); - border-color: rgba(255, 255, 255, 0.2); + &:hover { + background-color: #3a3a3a; + border-color: #5a5a5a; + } + + &.is-active { + background-color: #0078d4; + border-color: #0078d4; } &:disabled { opacity: 0.5; cursor: not-allowed; } + } - &.is-active { - background: globals.$brand-teal; - border-color: globals.$brand-teal; - color: white; + &__review-char-counter { + font-size: 12px; + color: #888888; + + .over-limit { + color: #ff6b6b; } } &__review-input { - width: 100%; - background-color: rgba(255, 255, 255, 0.05); - border: 1px solid globals.$border-color; - border-radius: 6px; - padding: calc(globals.$spacing-unit * 1.5); - padding-bottom: calc(globals.$spacing-unit * 3.5); - color: globals.$body-color; - font-size: globals.$body-font-size; - font-family: inherit; - line-height: 1.5; - min-height: 100px; - transition: - border-color 0.2s ease, - background-color 0.2s ease; - - &:focus-within { - outline: none; - background-color: rgba(255, 255, 255, 0.08); - border-color: globals.$brand-teal; - } - - &:hover { - border-color: rgba(255, 255, 255, 0.15); - } - + min-height: 120px; + padding: 12px; + .ProseMirror { outline: none; - min-height: 80px; - - &:empty:before { - content: attr(data-placeholder); - color: rgba(208, 209, 215, 0.6); - pointer-events: none; + color: #ffffff; + font-size: 14px; + line-height: 1.5; + + &:focus { + outline: none; } p { margin: 0 0 8px 0; - + &:last-child { margin-bottom: 0; } } strong { - font-weight: 700; + font-weight: bold; } em { diff --git a/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx b/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx index ac8a1615..e23120a8 100644 --- a/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx +++ b/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx @@ -69,14 +69,32 @@ export function HeroPanelActions() { updateGame(); }; - window.addEventListener("hydra:game-favorite-toggled", onFavoriteToggled as EventListener); - window.addEventListener("hydra:game-removed-from-library", onGameRemoved as EventListener); - window.addEventListener("hydra:game-files-removed", onFilesRemoved as EventListener); + window.addEventListener( + "hydra:game-favorite-toggled", + onFavoriteToggled as EventListener + ); + window.addEventListener( + "hydra:game-removed-from-library", + onGameRemoved as EventListener + ); + window.addEventListener( + "hydra:game-files-removed", + onFilesRemoved as EventListener + ); return () => { - window.removeEventListener("hydra:game-favorite-toggled", onFavoriteToggled as EventListener); - window.removeEventListener("hydra:game-removed-from-library", onGameRemoved as EventListener); - window.removeEventListener("hydra:game-files-removed", onFilesRemoved as EventListener); + window.removeEventListener( + "hydra:game-favorite-toggled", + onFavoriteToggled as EventListener + ); + window.removeEventListener( + "hydra:game-removed-from-library", + onGameRemoved as EventListener + ); + window.removeEventListener( + "hydra:game-files-removed", + onFilesRemoved as EventListener + ); }; }, [updateLibrary, updateGame]); @@ -226,7 +244,7 @@ export function HeroPanelActions() { onClick={() => setShowRepacksModal(true)} theme="outline" disabled={isGameDownloading} - className={`hero-panel-actions__action ${!repacks.length ? 'hero-panel-actions__action--disabled' : ''}`} + className={`hero-panel-actions__action ${!repacks.length ? "hero-panel-actions__action--disabled" : ""}`} > {t("download")} diff --git a/src/renderer/src/pages/game-details/modals/repacks-modal.tsx b/src/renderer/src/pages/game-details/modals/repacks-modal.tsx index ec7dc3f8..97b8b1b5 100644 --- a/src/renderer/src/pages/game-details/modals/repacks-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/repacks-modal.tsx @@ -277,4 +277,4 @@ export function RepacksModal({ ); -} \ No newline at end of file +} From fab02c4d16daafffba34bf006777d1f5a2fc3f1a Mon Sep 17 00:00:00 2001 From: Moyasee Date: Fri, 3 Oct 2025 02:55:41 +0300 Subject: [PATCH 008/197] Fix: Format-check fail and translations. Feat: added animations to upvote and downvote buttons --- src/locales/en/translation.json | 6 +- .../game-details/game-details-content.tsx | 68 ++++++++++++++++--- .../src/pages/game-details/game-details.scss | 8 +-- .../modals/delete-review-modal.scss | 1 + .../modals/delete-review-modal.tsx | 4 +- 5 files changed, 66 insertions(+), 21 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 22c54234..22bb9380 100755 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -213,7 +213,6 @@ "leave_a_review": "Leave a Review", "write_review_placeholder": "Share your thoughts about this game...", "sort_newest": "Newest", - "sort_by": "Sort by", "no_reviews_yet": "No reviews yet", "be_first_to_review": "Be the first to share your thoughts about this game!", "sort_oldest": "Oldest", @@ -226,7 +225,6 @@ "loading_reviews": "Loading reviews...", "loading_more_reviews": "Loading more reviews...", "load_more_reviews": "Load More Reviews", - "youve_played_for_hours": "You've played for {{hours}} hours", "would_you_recommend_this_game": "Would you like to leave a review to this game?", "yes": "Yes", "maybe_later": "Maybe Later", @@ -330,8 +328,8 @@ "delete_review": "Delete review", "delete_review_modal_title": "Are you sure you want to delete your review?", "delete_review_modal_description": "This action cannot be undone.", - "delete_review_button": "Delete", - "delete_review_karma_warning": "You will lose any karma points earned from this review." + "delete_review_modal_delete_button": "Delete", + "delete_review_modal_cancel_button": "Cancel" }, "activation": { "title": "Activate Hydra", diff --git a/src/renderer/src/pages/game-details/game-details-content.tsx b/src/renderer/src/pages/game-details/game-details-content.tsx index bb6c2e85..f3db5164 100644 --- a/src/renderer/src/pages/game-details/game-details-content.tsx +++ b/src/renderer/src/pages/game-details/game-details-content.tsx @@ -4,6 +4,7 @@ import { ThumbsUp, ThumbsDown } from "lucide-react"; import { useNavigate } from "react-router-dom"; import { useEditor, EditorContent } from "@tiptap/react"; import StarterKit from "@tiptap/starter-kit"; +import { motion } from "framer-motion"; import type { GameReview } from "@types"; import { HeroPanel } from "./hero"; @@ -131,10 +132,10 @@ export function GameDetailsContent() { }, handlePaste: (view, event) => { // Strip formatting from pasted content to prevent overflow issues - const text = event.clipboardData?.getData('text/plain') || ''; + const text = event.clipboardData?.getData("text/plain") || ""; const currentText = view.state.doc.textContent; const remainingChars = MAX_REVIEW_CHARS - currentText.length; - + if (text && remainingChars > 0) { event.preventDefault(); const truncatedText = text.slice(0, remainingChars); @@ -147,7 +148,7 @@ export function GameDetailsContent() { onUpdate: ({ editor }) => { const text = editor.getText(); setReviewCharCount(text.length); - + // Prevent typing beyond character limit if (text.length > MAX_REVIEW_CHARS) { const truncatedContent = text.slice(0, MAX_REVIEW_CHARS); @@ -293,7 +294,12 @@ export function GameDetailsContent() { console.log("reviewScore:", reviewScore); console.log("submittingReview:", submittingReview); - if (!objectId || !reviewHtml.trim() || submittingReview || reviewCharCount > MAX_REVIEW_CHARS) { + if ( + !objectId || + !reviewHtml.trim() || + submittingReview || + reviewCharCount > MAX_REVIEW_CHARS + ) { console.log("Early return - validation failed"); return; } @@ -584,7 +590,13 @@ export function GameDetailsContent() {
- MAX_REVIEW_CHARS ? "over-limit" : ""}> + MAX_REVIEW_CHARS + ? "over-limit" + : "" + } + > {reviewCharCount}/{MAX_REVIEW_CHARS}
@@ -619,12 +631,14 @@ export function GameDetailsContent() {
- + - +
{userDetails?.id === review.user?.id && (
From 1b5f70a075bea27415e76fa907b7204a534b0268 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Fri, 3 Oct 2025 03:01:37 +0300 Subject: [PATCH 009/197] Fix: replaced array index with review.id and marked props as read-only --- .../pages/game-details/game-details-content.tsx | 17 ++++------------- .../game-details/modals/delete-review-modal.tsx | 2 +- .../pages/game-details/review-prompt-banner.tsx | 2 +- .../pages/game-details/review-sort-options.tsx | 2 +- 4 files changed, 7 insertions(+), 16 deletions(-) diff --git a/src/renderer/src/pages/game-details/game-details-content.tsx b/src/renderer/src/pages/game-details/game-details-content.tsx index f3db5164..8a09712e 100644 --- a/src/renderer/src/pages/game-details/game-details-content.tsx +++ b/src/renderer/src/pages/game-details/game-details-content.tsx @@ -688,8 +688,8 @@ export function GameDetailsContent() {
)} - {reviews.map((review, index) => ( -
+ {reviews.map((review) => ( +
{review.isBlocked && !visibleBlockedReviews.has(review.id) ? (
@@ -713,24 +713,15 @@ export function GameDetailsContent() { /> )}
-
review.user?.id && navigate(`/profile/${review.user.id}`) } - onKeyDown={(e) => { - if (e.key === "Enter" || e.key === " ") { - e.preventDefault(); - review.user?.id && - navigate(`/profile/${review.user.id}`); - } - }} - role="button" - tabIndex={0} > {review.user?.displayName || "Anonymous"} -
+
{formatDistance( diff --git a/src/renderer/src/pages/game-details/modals/delete-review-modal.tsx b/src/renderer/src/pages/game-details/modals/delete-review-modal.tsx index 2ed352c5..fe612bbd 100644 --- a/src/renderer/src/pages/game-details/modals/delete-review-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/delete-review-modal.tsx @@ -12,7 +12,7 @@ export function DeleteReviewModal({ visible, onClose, onConfirm, -}: DeleteReviewModalProps) { +}: Readonly) { const { t } = useTranslation("game_details"); const handleDeleteReview = () => { diff --git a/src/renderer/src/pages/game-details/review-prompt-banner.tsx b/src/renderer/src/pages/game-details/review-prompt-banner.tsx index 7bd96613..aeddaaad 100644 --- a/src/renderer/src/pages/game-details/review-prompt-banner.tsx +++ b/src/renderer/src/pages/game-details/review-prompt-banner.tsx @@ -10,7 +10,7 @@ interface ReviewPromptBannerProps { export function ReviewPromptBanner({ onYesClick, onLaterClick, -}: ReviewPromptBannerProps) { +}: Readonly) { const { t } = useTranslation("game_details"); return ( diff --git a/src/renderer/src/pages/game-details/review-sort-options.tsx b/src/renderer/src/pages/game-details/review-sort-options.tsx index ca11d056..75ec0f39 100644 --- a/src/renderer/src/pages/game-details/review-sort-options.tsx +++ b/src/renderer/src/pages/game-details/review-sort-options.tsx @@ -21,7 +21,7 @@ interface ReviewSortOptionsProps { export function ReviewSortOptions({ sortBy, onSortChange, -}: ReviewSortOptionsProps) { +}: Readonly) { const { t } = useTranslation("game_details"); const handleDateToggle = () => { From 899f68318fd6fc8bb60315fe1a3e58f136b4e544 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Fri, 3 Oct 2025 03:06:37 +0300 Subject: [PATCH 010/197] Fix: multiple imports, ambigious spacing and unexpected negated condition --- src/renderer/src/pages/game-details/game-details-content.tsx | 2 +- .../src/pages/game-details/hero/hero-panel-actions.tsx | 2 +- .../src/pages/profile/profile-content/user-karma-box.tsx | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/renderer/src/pages/game-details/game-details-content.tsx b/src/renderer/src/pages/game-details/game-details-content.tsx index 8a09712e..a66971b6 100644 --- a/src/renderer/src/pages/game-details/game-details-content.tsx +++ b/src/renderer/src/pages/game-details/game-details-content.tsx @@ -693,7 +693,7 @@ export function GameDetailsContent() { {review.isBlocked && !visibleBlockedReviews.has(review.id) ? (
- Review from blocked user — + Review from blocked user —{" "}
@@ -618,12 +631,23 @@ export function GameDetailsContent() { {t("rating")} - setReviewScore(e.target.value ? Number(e.target.value) : null) + setReviewScore( + e.target.value ? Number(e.target.value) : null + ) } >
-
+
{review.score}/10
diff --git a/src/renderer/src/pages/game-details/game-details.scss b/src/renderer/src/pages/game-details/game-details.scss index 76b6cdcb..83758524 100644 --- a/src/renderer/src/pages/game-details/game-details.scss +++ b/src/renderer/src/pages/game-details/game-details.scss @@ -75,7 +75,9 @@ $hero-height: 300px; padding: 6px 12px; font-size: 14px; cursor: pointer; - transition: border-color 0.2s ease, background-color 0.2s ease; + transition: + border-color 0.2s ease, + background-color 0.2s ease; &:focus { outline: none; diff --git a/src/renderer/src/pages/game-details/sidebar/sidebar.scss b/src/renderer/src/pages/game-details/sidebar/sidebar.scss index b48e8a8f..1330d278 100755 --- a/src/renderer/src/pages/game-details/sidebar/sidebar.scss +++ b/src/renderer/src/pages/game-details/sidebar/sidebar.scss @@ -115,7 +115,6 @@ @media (min-width: 1024px) { flex-direction: column; } - } &__category-title { From 1f7947f50f31a427ab6672eb88fb3a52391084fc Mon Sep 17 00:00:00 2001 From: Moyasee Date: Sat, 4 Oct 2025 20:20:48 +0300 Subject: [PATCH 019/197] fix: refactoring function, using proper attributes and extracted ternary operation --- .../game-details/game-details-content.tsx | 80 ++++++++++--------- 1 file changed, 42 insertions(+), 38 deletions(-) diff --git a/src/renderer/src/pages/game-details/game-details-content.tsx b/src/renderer/src/pages/game-details/game-details-content.tsx index 9e0ea490..7cf67e4c 100644 --- a/src/renderer/src/pages/game-details/game-details-content.tsx +++ b/src/renderer/src/pages/game-details/game-details-content.tsx @@ -32,6 +32,45 @@ const getScoreColorClass = (score: number): string => { return ""; }; +// Helper function to process media elements for responsive display +const processMediaElements = (document: Document) => { + const $images = Array.from(document.querySelectorAll("img")); + $images.forEach(($image) => { + $image.loading = "lazy"; + // Remove any inline width/height styles that might cause overflow + $image.removeAttribute("width"); + $image.removeAttribute("height"); + $image.removeAttribute("style"); + // Set max-width to prevent overflow + $image.style.maxWidth = "100%"; + $image.style.width = "auto"; + $image.style.height = "auto"; + $image.style.boxSizing = "border-box"; + }); + + // Handle videos the same way + const $videos = Array.from(document.querySelectorAll("video")); + $videos.forEach(($video) => { + // Remove any inline width/height styles that might cause overflow + $video.removeAttribute("width"); + $video.removeAttribute("height"); + $video.removeAttribute("style"); + // Set max-width to prevent overflow + $video.style.maxWidth = "100%"; + $video.style.width = "auto"; + $video.style.height = "auto"; + $video.style.boxSizing = "border-box"; + }); +}; + +// Helper function to get score color class for select element +const getSelectScoreColorClass = (score: number): string => { + if (score >= 0 && score <= 3) return "game-details__review-score-select--red"; + if (score >= 4 && score <= 7) return "game-details__review-score-select--yellow"; + if (score >= 8 && score <= 10) return "game-details__review-score-select--green"; + return ""; +}; + export function GameDetailsContent() { const heroRef = useRef(null); const navigate = useNavigate(); @@ -64,33 +103,7 @@ export function GameDetailsContent() { "text/html" ); - const $images = Array.from(document.querySelectorAll("img")); - $images.forEach(($image) => { - $image.loading = "lazy"; - // Remove any inline width/height styles that might cause overflow - $image.removeAttribute("width"); - $image.removeAttribute("height"); - $image.removeAttribute("style"); - // Set max-width to prevent overflow - $image.style.maxWidth = "100%"; - $image.style.width = "auto"; - $image.style.height = "auto"; - $image.style.boxSizing = "border-box"; - }); - - // Handle videos the same way - const $videos = Array.from(document.querySelectorAll("video")); - $videos.forEach(($video) => { - // Remove any inline width/height styles that might cause overflow - $video.removeAttribute("width"); - $video.removeAttribute("height"); - $video.removeAttribute("style"); - // Set max-width to prevent overflow - $video.style.maxWidth = "100%"; - $video.style.width = "auto"; - $video.style.height = "auto"; - $video.style.boxSizing = "border-box"; - }); + processMediaElements(document); return document.body.outerHTML; } @@ -619,14 +632,11 @@ export function GameDetailsContent() { className="game-details__review-input" onClick={() => editor?.commands.focus()} onKeyDown={(e) => { - if (e.key === 'Enter' || e.key === ' ') { + if (e.key === "Enter" || e.key === " ") { e.preventDefault(); editor?.commands.focus(); } }} - role="textbox" - tabIndex={0} - aria-label={t("write_review_placeholder")} >
@@ -639,13 +649,7 @@ export function GameDetailsContent() { From 8653e62dce475d1f91aeff2e3820e83f9e83a8eb Mon Sep 17 00:00:00 2001 From: Moyasee Date: Sat, 4 Oct 2025 20:25:28 +0300 Subject: [PATCH 021/197] fix: using proper input type instead of the button role --- .../pages/game-details/game-details-content.tsx | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/renderer/src/pages/game-details/game-details-content.tsx b/src/renderer/src/pages/game-details/game-details-content.tsx index 13a41121..9da59d4c 100644 --- a/src/renderer/src/pages/game-details/game-details-content.tsx +++ b/src/renderer/src/pages/game-details/game-details-content.tsx @@ -630,19 +630,7 @@ export function GameDetailsContent() {
-
editor?.commands.focus()} - onKeyDown={(e) => { - if (e.key === "Enter" || e.key === " ") { - e.preventDefault(); - editor?.commands.focus(); - } - }} - role="button" - tabIndex={0} - aria-label="Click to focus review editor" - > +
From 6667e00c9104737434361398ff4cf103be53b881 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Sun, 5 Oct 2025 20:32:41 +0300 Subject: [PATCH 022/197] Feat: added rating showing in game card in categories, fixed maybe later button, changed empty state, fixed copy issue, added karma showing, added remove review text, added empty state for games with no reviews, fixed sorting buttons, fixed shift in the page --- src/locales/en/translation.json | 18 +- src/locales/ru/translation.json | 50 +++- .../src/components/game-card/game-card.scss | 7 +- .../src/components/game-card/game-card.tsx | 17 +- src/renderer/src/components/index.ts | 1 + .../src/components/star-rating/index.ts | 1 + .../components/star-rating/star-rating.scss | 54 +++++ .../components/star-rating/star-rating.tsx | 64 +++++ .../game-details/game-details-content.tsx | 220 ++++++++++-------- .../src/pages/game-details/game-details.scss | 131 ++++++++--- .../game-details/review-prompt-banner.scss | 2 +- .../game-details/review-prompt-banner.tsx | 2 +- .../game-details/review-sort-options.tsx | 4 +- .../pages/game-details/sidebar/sidebar.tsx | 23 +- .../profile-content/user-karma-box.tsx | 11 +- src/types/index.ts | 1 + 16 files changed, 448 insertions(+), 158 deletions(-) create mode 100644 src/renderer/src/components/star-rating/index.ts create mode 100644 src/renderer/src/components/star-rating/star-rating.scss create mode 100644 src/renderer/src/components/star-rating/star-rating.tsx diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index b54fe2fb..1af953fe 100755 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -222,12 +222,22 @@ "sort_most_voted": "Most Voted", "rating": "Rating", "rating_stats": "Rating", - "select_rating": "Select Rating", + "rating_very_negative": "Very Negative", + "rating_negative": "Negative", + "rating_neutral": "Neutral", + "rating_positive": "Positive", + "rating_very_positive": "Very Positive", "submit_review": "Submit Review", "submitting": "Submitting...", + "review_submitted_successfully": "Review submitted successfully!", + "review_submission_failed": "Failed to submit review. Please try again.", + "review_cannot_be_empty": "Review text field cannot be empty.", + "review_deleted_successfully": "Review deleted successfully.", + "review_deletion_failed": "Failed to delete review. Please try again.", "loading_reviews": "Loading reviews...", "loading_more_reviews": "Loading more reviews...", "load_more_reviews": "Load More Reviews", + "you_seemed_to_enjoy_this_game": "You've seemed to enjoy this game", "would_you_recommend_this_game": "Would you like to leave a review to this game?", "yes": "Yes", "maybe_later": "Maybe Later", @@ -329,6 +339,7 @@ "filter_by_source": "Filter by source", "no_repacks_found": "No sources found for this game", "delete_review": "Delete review", + "remove_review": "Remove Review", "delete_review_modal_title": "Are you sure you want to delete your review?", "delete_review_modal_description": "This action cannot be undone.", "delete_review_modal_delete_button": "Delete", @@ -548,7 +559,8 @@ "game_card": { "available_one": "Available", "available_other": "Available", - "no_downloads": "No downloads available" + "no_downloads": "No downloads available", + "calculating": "Calculating" }, "binary_not_found_modal": { "title": "Programs not installed", @@ -654,7 +666,7 @@ "game_added_to_pinned": "Game added to pinned", "karma": "Karma", "karma_count": "karma", - "karma_description": "Earned from positive likes on your reviews" + "karma_description": "Earned from positive likes on reviews" }, "achievement": { "achievement_unlocked": "Achievement unlocked", diff --git a/src/locales/ru/translation.json b/src/locales/ru/translation.json index 8992a4a0..45177eaf 100644 --- a/src/locales/ru/translation.json +++ b/src/locales/ru/translation.json @@ -189,10 +189,14 @@ "refuse_nsfw_content": "Назад", "stats": "Статистика", "player_count": "Активные игроки", + "rating_count": "Рейтинг", "warning": "Внимание:", "hydra_needs_to_remain_open": "Для этой загрузки Hydra должна оставаться открытой до завершения. Если Hydra закроется до завершения, вы потеряете прогресс.", "achievements": "Достижения", "achievements_count": "Достижения {{unlockedCount}}/{{achievementsCount}}", + "show_more": "Показать больше", + "reviews": "Отзывы", + "leave_a_review": "Оставить отзыв", "cloud_save": "Облачное сохранение", "cloud_save_description": "Сохраняйте ваш прогресс в облаке и продолжайте играть на любом устройстве", "backups": "Резервные копии", @@ -271,7 +275,41 @@ "backup_unfrozen": "Резервная копия откреплена", "backup_freeze_failed": "Не удалось закрепить резервную копию", "backup_freeze_failed_description": "Вы должны оставить как минимум один свободный слот для автоматических резервных копий", - "manual_playtime_tooltip": "Это время игры было обновлено вручную" + "manual_playtime_tooltip": "Это время игры было обновлено вручную", + "write_review_placeholder": "Поделитесь своими мыслями об этой игре...", + "sort_newest": "Новые", + "no_reviews_yet": "Пока нет отзывов", + "be_first_to_review": "Будьте первым, кто поделится своими мыслями об этой игре!", + "sort_oldest": "Старые", + "sort_highest_score": "Высший балл", + "sort_lowest_score": "Низший балл", + "sort_most_voted": "Самые популярные", + "rating": "Рейтинг", + "rating_stats": "Рейтинг", + "rating_very_negative": "Очень негативный", + "rating_negative": "Негативный", + "rating_neutral": "Нейтральный", + "rating_positive": "Позитивный", + "rating_very_positive": "Очень позитивный", + "submit_review": "Отправить отзыв", + "submitting": "Отправка...", + "remove_review": "Удалить отзыв", + "delete_review_modal_title": "Вы уверены, что хотите удалить свой отзыв?", + "delete_review_modal_description": "Это действие нельзя отменить.", + "delete_review_modal_delete_button": "Удалить", + "delete_review_modal_cancel_button": "Отмена", + "review_submitted_successfully": "Отзыв успешно отправлен!", + "review_submission_failed": "Не удалось отправить отзыв. Попробуйте еще раз.", + "review_cannot_be_empty": "Поле отзыва не может быть пустым.", + "review_deleted_successfully": "Отзыв успешно удален.", + "review_deletion_failed": "Не удалось удалить отзыв. Попробуйте еще раз.", + "loading_reviews": "Загрузка отзывов...", + "loading_more_reviews": "Загрузка дополнительных отзывов...", + "load_more_reviews": "Загрузить больше отзывов", + "you_seemed_to_enjoy_this_game": "Похоже, вам понравилась эта игра", + "would_you_recommend_this_game": "Хотели бы вы оставить отзыв об этой игре?", + "yes": "Да", + "maybe_later": "Может быть позже" }, "activation": { "title": "Активировать Hydra", @@ -475,7 +513,8 @@ "game_card": { "available_one": "Доступный", "available_other": "Доступный", - "no_downloads": "Нет доступных источников" + "no_downloads": "Нет доступных источников", + "calculating": "Вычисление" }, "binary_not_found_modal": { "title": "Программы не установлены", @@ -572,7 +611,12 @@ "show_achievements_on_profile": "Покажите свои достижения в профиле", "show_points_on_profile": "Показывать заработанные очки в своем профиле", "error_adding_friend": "Не удалось отправить запрос в друзья. Пожалуйста, проверьте код друга", - "friend_code_length_error": "Код друга должен содержать 8 символов" + "friend_code_length_error": "Код друга должен содержать 8 символов", + "game_removed_from_pinned": "Игра удалена из закрепленных", + "game_added_to_pinned": "Игра добавлена в закрепленные", + "karma": "Карма", + "karma_count": "карма", + "karma_description": "Заработано от положительных лайков на отзывах" }, "achievement": { "achievement_unlocked": "Достижение разблокировано", diff --git a/src/renderer/src/components/game-card/game-card.scss b/src/renderer/src/components/game-card/game-card.scss index ee4a22b1..99aa866e 100644 --- a/src/renderer/src/components/game-card/game-card.scss +++ b/src/renderer/src/components/game-card/game-card.scss @@ -72,7 +72,12 @@ display: flex; color: globals.$muted-color; font-size: 12px; - align-items: flex-end; + align-items: center; + + // Ensure star rating is properly aligned + .star-rating { + align-items: center; + } } &__title-container { diff --git a/src/renderer/src/components/game-card/game-card.tsx b/src/renderer/src/components/game-card/game-card.tsx index 15b5439b..1aa58ba7 100644 --- a/src/renderer/src/components/game-card/game-card.tsx +++ b/src/renderer/src/components/game-card/game-card.tsx @@ -1,4 +1,4 @@ -import { DownloadIcon, PeopleIcon, StarIcon } from "@primer/octicons-react"; +import { DownloadIcon, PeopleIcon } from "@primer/octicons-react"; import type { GameStats } from "@types"; import SteamLogo from "@renderer/assets/steam-logo.svg?react"; @@ -7,6 +7,7 @@ import "./game-card.scss"; import { useTranslation } from "react-i18next"; import { Badge } from "../badge/badge"; +import { StarRating } from "../star-rating/star-rating"; import { useCallback, useState, useMemo } from "react"; import { useFormat, useRepacks } from "@renderer/hooks"; @@ -107,12 +108,14 @@ export function GameCard({ game, ...props }: GameCardProps) { {stats ? numberFormatter.format(stats.playerCount) : "…"}
- {stats?.averageScore && ( -
- - {stats.averageScore.toFixed(1)} -
- )} +
+ +
diff --git a/src/renderer/src/components/index.ts b/src/renderer/src/components/index.ts index 9970be42..89dccdbc 100644 --- a/src/renderer/src/components/index.ts +++ b/src/renderer/src/components/index.ts @@ -18,3 +18,4 @@ export * from "./debrid-badge/debrid-badge"; export * from "./context-menu/context-menu"; export * from "./game-context-menu/game-context-menu"; export * from "./game-context-menu/use-game-actions"; +export * from "./star-rating/star-rating"; diff --git a/src/renderer/src/components/star-rating/index.ts b/src/renderer/src/components/star-rating/index.ts new file mode 100644 index 00000000..0f153ca4 --- /dev/null +++ b/src/renderer/src/components/star-rating/index.ts @@ -0,0 +1 @@ +export * from "./star-rating"; \ No newline at end of file diff --git a/src/renderer/src/components/star-rating/star-rating.scss b/src/renderer/src/components/star-rating/star-rating.scss new file mode 100644 index 00000000..4fa7ba2a --- /dev/null +++ b/src/renderer/src/components/star-rating/star-rating.scss @@ -0,0 +1,54 @@ +@use "../../scss/globals.scss"; + +.star-rating { + display: flex; + align-items: center; + gap: 2px; + + &__star { + color: globals.$muted-color; + transition: color ease 0.2s; + + &--filled { + color: #ffffff; + } + + &--empty { + color: globals.$muted-color; + } + + &--half { + color: #ffffff; + position: absolute; + top: 0; + left: 0; + clip-path: polygon(0 0, 50% 0, 50% 100%, 0 100%); + } + } + + &__half-star { + position: relative; + display: inline-block; + } + + &__value { + margin-left: 4px; + font-size: 12px; + color: globals.$muted-color; + font-weight: 500; + } + + &__calculating-text, + &__no-rating-text { + margin-left: 4px; + font-size: 12px; + color: globals.$muted-color; + } + + &--calculating, + &--no-rating { + .star-rating__star { + color: globals.$muted-color; + } + } +} \ No newline at end of file diff --git a/src/renderer/src/components/star-rating/star-rating.tsx b/src/renderer/src/components/star-rating/star-rating.tsx new file mode 100644 index 00000000..5aa2a5ee --- /dev/null +++ b/src/renderer/src/components/star-rating/star-rating.tsx @@ -0,0 +1,64 @@ +import { StarIcon, StarFillIcon } from "@primer/octicons-react"; +import "./star-rating.scss"; + +export interface StarRatingProps { + rating: number | null; + maxStars?: number; + size?: number; + showCalculating?: boolean; + calculatingText?: string; +} + +export function StarRating({ + rating, + maxStars = 5, + size = 12, + showCalculating = false, + calculatingText = "Calculating" +}: StarRatingProps) { + if (rating === null && showCalculating) { + return ( +
+ + {calculatingText} +
+ ); + } + + if (rating === null || rating === undefined) { + return ( +
+ + +
+ ); + } + + const filledStars = Math.floor(rating); + const hasHalfStar = rating % 1 >= 0.5; + const emptyStars = maxStars - filledStars - (hasHalfStar ? 1 : 0); + + return ( +
+ + {Array.from({ length: filledStars }, (_, index) => ( + + ))} + + + {hasHalfStar && ( +
+ + +
+ )} + + + {Array.from({ length: emptyStars }, (_, index) => ( + + ))} + + {rating.toFixed(1)} +
+ ); +} \ No newline at end of file diff --git a/src/renderer/src/pages/game-details/game-details-content.tsx b/src/renderer/src/pages/game-details/game-details-content.tsx index 9da59d4c..2b6de1d8 100644 --- a/src/renderer/src/pages/game-details/game-details-content.tsx +++ b/src/renderer/src/pages/game-details/game-details-content.tsx @@ -1,6 +1,6 @@ import { useContext, useEffect, useMemo, useRef, useState } from "react"; -import { PencilIcon, TrashIcon, ClockIcon } from "@primer/octicons-react"; -import { ThumbsUp, ThumbsDown } from "lucide-react"; +import { PencilIcon, TrashIcon, ClockIcon, NoteIcon } from "@primer/octicons-react"; +import { ThumbsUp, ThumbsDown, Star } from "lucide-react"; import { useNavigate } from "react-router-dom"; import { useEditor, EditorContent } from "@tiptap/react"; import StarterKit from "@tiptap/starter-kit"; @@ -20,28 +20,24 @@ import { useTranslation } from "react-i18next"; import { cloudSyncContext, gameDetailsContext } from "@renderer/context"; import cloudIconAnimated from "@renderer/assets/icons/cloud-animated.gif"; -import { useUserDetails, useLibrary, useDate } from "@renderer/hooks"; +import { useUserDetails, useLibrary, useDate, useToast } from "@renderer/hooks"; import { useSubscription } from "@renderer/hooks/use-subscription"; import "./game-details.scss"; -// Helper function to get score color class const getScoreColorClass = (score: number): string => { - if (score >= 0 && score <= 3) return "game-details__review-score--red"; - if (score >= 4 && score <= 6) return "game-details__review-score--yellow"; - if (score >= 7 && score <= 10) return "game-details__review-score--green"; + if (score >= 1 && score <= 2) return "game-details__review-score--red"; + if (score >= 3 && score <= 3) return "game-details__review-score--yellow"; + if (score >= 4 && score <= 5) return "game-details__review-score--green"; return ""; }; -// Helper function to process media elements for responsive display const processMediaElements = (document: Document) => { const $images = Array.from(document.querySelectorAll("img")); $images.forEach(($image) => { $image.loading = "lazy"; - // Remove any inline width/height styles that might cause overflow $image.removeAttribute("width"); $image.removeAttribute("height"); $image.removeAttribute("style"); - // Set max-width to prevent overflow $image.style.maxWidth = "100%"; $image.style.width = "auto"; $image.style.height = "auto"; @@ -51,11 +47,9 @@ const processMediaElements = (document: Document) => { // Handle videos the same way const $videos = Array.from(document.querySelectorAll("video")); $videos.forEach(($video) => { - // Remove any inline width/height styles that might cause overflow $video.removeAttribute("width"); $video.removeAttribute("height"); $video.removeAttribute("style"); - // Set max-width to prevent overflow $video.style.maxWidth = "100%"; $video.style.width = "auto"; $video.style.height = "auto"; @@ -63,16 +57,26 @@ const processMediaElements = (document: Document) => { }); }; -// Helper function to get score color class for select element const getSelectScoreColorClass = (score: number): string => { - if (score >= 0 && score <= 3) return "game-details__review-score-select--red"; - if (score >= 4 && score <= 7) + if (score >= 1 && score <= 2) return "game-details__review-score-select--red"; + if (score >= 3 && score <= 3) return "game-details__review-score-select--yellow"; - if (score >= 8 && score <= 10) + if (score >= 4 && score <= 5) return "game-details__review-score-select--green"; return ""; }; +const getRatingText = (score: number, t: (key: string) => string): string => { + switch (score) { + case 1: return t("rating_very_negative"); + case 2: return t("rating_negative"); + case 3: return t("rating_neutral"); + case 4: return t("rating_positive"); + case 5: return t("rating_very_positive"); + default: return ""; + } +}; + export function GameDetailsContent() { const heroRef = useRef(null); const navigate = useNavigate(); @@ -93,6 +97,7 @@ export function GameDetailsContent() { const { userDetails, hasActiveSubscription } = useUserDetails(); const { updateLibrary } = useLibrary(); const { formatDistance } = useDate(); + const { showSuccessToast, showErrorToast } = useToast(); const { setShowCloudSyncModal, getGameArtifacts } = useContext(cloudSyncContext); @@ -139,16 +144,13 @@ export function GameDetailsContent() { const [totalReviewCount, setTotalReviewCount] = useState(0); const [showReviewForm, setShowReviewForm] = useState(false); - // Review prompt banner state const [showReviewPrompt, setShowReviewPrompt] = useState(false); const [hasUserReviewed, setHasUserReviewed] = useState(false); const [reviewCheckLoading, setReviewCheckLoading] = useState(false); - // Tiptap editor for review input const editor = useEditor({ extensions: [ StarterKit.configure({ - // Disable link extension to prevent automatic link rendering and XSS link: false, }), ], @@ -159,14 +161,26 @@ export function GameDetailsContent() { "data-placeholder": t("write_review_placeholder"), }, handlePaste: (view, event) => { - // Strip formatting from pasted content to prevent overflow issues - const text = event.clipboardData?.getData("text/plain") || ""; + const htmlContent = event.clipboardData?.getData("text/html") || ""; + const plainText = event.clipboardData?.getData("text/plain") || ""; + const currentText = view.state.doc.textContent; const remainingChars = MAX_REVIEW_CHARS - currentText.length; - if (text && remainingChars > 0) { + if ((htmlContent || plainText) && remainingChars > 0) { event.preventDefault(); - const truncatedText = text.slice(0, remainingChars); + + if (htmlContent) { + const tempDiv = document.createElement("div"); + tempDiv.innerHTML = htmlContent; + const textLength = tempDiv.textContent?.length || 0; + + if (textLength <= remainingChars) { + return false; + } + } + + const truncatedText = plainText.slice(0, remainingChars); view.dispatch(view.state.tr.insertText(truncatedText)); return true; } @@ -177,7 +191,6 @@ export function GameDetailsContent() { const text = editor.getText(); setReviewCharCount(text.length); - // Prevent typing beyond character limit if (text.length > MAX_REVIEW_CHARS) { const truncatedContent = text.slice(0, MAX_REVIEW_CHARS); editor.commands.setContent(truncatedContent); @@ -219,7 +232,6 @@ export function GameDetailsContent() { const isCustomGame = game?.shop === "custom"; - // Reviews functions const checkUserReview = async () => { if (!objectId || !userDetails) return; @@ -229,11 +241,9 @@ export function GameDetailsContent() { const hasReviewed = (response as any)?.hasReviewed || false; setHasUserReviewed(hasReviewed); - // Show prompt only if user hasn't reviewed and has played the game if ( !hasReviewed && - game?.playTimeInMilliseconds && - game.playTimeInMilliseconds > 0 + !sessionStorage.getItem(`reviewPromptDismissed_${objectId}`) ) { setShowReviewPrompt(true); } @@ -258,7 +268,6 @@ export function GameDetailsContent() { reviewsSortBy ); - // Handle the response structure: { totalCount: number, reviews: Review[] } const reviewsData = (response as any)?.reviews || []; const reviewCount = (response as any)?.totalCount || 0; @@ -286,7 +295,6 @@ export function GameDetailsContent() { try { await window.electron.voteReview(shop, objectId, reviewId, voteType); - // Reload reviews to get updated vote counts loadReviews(true); } catch (error) { console.error(`Failed to ${voteType} review:`, error); @@ -303,40 +311,40 @@ export function GameDetailsContent() { try { await window.electron.deleteReview(shop, objectId, reviewToDelete); - // Reload reviews after deletion loadReviews(true); setShowDeleteReviewModal(false); setReviewToDelete(null); + showSuccessToast(t("review_deleted_successfully")); } catch (error) { console.error("Failed to delete review:", error); + showErrorToast(t("review_deletion_failed")); } }; const handleSubmitReview = async () => { - console.log("handleSubmitReview called"); - console.log("game:", game); - console.log("objectId:", objectId); - const reviewHtml = editor?.getHTML() || ""; - console.log("reviewHtml:", reviewHtml); - console.log("reviewScore:", reviewScore); - console.log("submittingReview:", submittingReview); + const reviewText = editor?.getText() || ""; - if ( - !objectId || - !reviewHtml.trim() || - reviewScore === null || - submittingReview || - reviewCharCount > MAX_REVIEW_CHARS - ) { - console.log("Early return - validation failed"); + if (!objectId) { + return; + } + + if (!reviewText.trim()) { + showErrorToast(t("review_cannot_be_empty")); + return; + } + + if (submittingReview || reviewCharCount > MAX_REVIEW_CHARS) { + return; + } + + if (reviewScore === null) { return; } - console.log("Starting review submission..."); setSubmittingReview(true); + try { - console.log("Calling window.electron.createGameReview..."); await window.electron.createGameReview( shop, objectId, @@ -344,27 +352,25 @@ export function GameDetailsContent() { reviewScore ); - console.log("Review submitted successfully"); editor?.commands.clearContent(); setReviewScore(null); - await loadReviews(true); // Reload reviews after submission - setShowReviewForm(false); // Hide the review form after successful submission - setShowReviewPrompt(false); // Hide the prompt banner - setHasUserReviewed(true); // Update the review status + showSuccessToast(t("review_submitted_successfully")); + + await loadReviews(true); + setShowReviewForm(false); + setShowReviewPrompt(false); + setHasUserReviewed(true); } catch (error) { - console.error("Failed to submit review:", error); + showErrorToast(t("review_submission_failed")); } finally { setSubmittingReview(false); - console.log("Review submission completed"); } }; - // Review prompt banner handlers const handleReviewPromptYes = () => { setShowReviewPrompt(false); setShowReviewForm(true); - // Scroll to review form setTimeout(() => { const reviewFormElement = document.querySelector( ".game-details__review-form" @@ -380,13 +386,18 @@ export function GameDetailsContent() { const handleReviewPromptLater = () => { setShowReviewPrompt(false); + if (objectId) { + sessionStorage.setItem(`reviewPromptDismissed_${objectId}`, 'true'); + } }; const handleSortChange = (newSortBy: string) => { - setReviewsSortBy(newSortBy); - setReviewsPage(0); - setHasMoreReviews(true); - loadReviews(true); + if (newSortBy !== reviewsSortBy) { + setReviewsSortBy(newSortBy); + setReviewsPage(0); + setHasMoreReviews(true); + loadReviews(true); + } }; const toggleBlockedReview = (reviewId: string) => { @@ -408,22 +419,19 @@ export function GameDetailsContent() { } }; - // Load reviews when component mounts or sort changes useEffect(() => { if (objectId && (game || shop)) { loadReviews(true); - checkUserReview(); // Check if user has reviewed this game + checkUserReview(); } }, [game, shop, objectId, reviewsSortBy, userDetails]); - // Load more reviews when page changes useEffect(() => { if (reviewsPage > 0) { loadReviews(false); } }, [reviewsPage]); - // Helper function to get image with custom asset priority const getImageWithCustomPriority = ( customUrl: string | null | undefined, originalUrl: string | null | undefined, @@ -540,7 +548,6 @@ export function GameDetailsContent() { {game?.shop !== "custom" && showReviewPrompt && userDetails && - game?.playTimeInMilliseconds && !hasUserReviewed && !reviewCheckLoading && (
- - +
+ {[1, 2, 3, 4, 5].map((starValue) => ( + + ))} +
@@ -716,7 +718,9 @@ export function GameDetailsContent() { {!reviewsLoading && reviews.length === 0 && (
-
📝
+
+ +

{t("no_reviews_yet")}

@@ -770,10 +774,23 @@ export function GameDetailsContent() {
-
- {review.score}/10 +
+ {[1, 2, 3, 4, 5].map((starValue) => ( + + ))}
+ {t("remove_review")} )} {review.isBlocked && diff --git a/src/renderer/src/pages/game-details/game-details.scss b/src/renderer/src/pages/game-details/game-details.scss index 83758524..b0726655 100644 --- a/src/renderer/src/pages/game-details/game-details.scss +++ b/src/renderer/src/pages/game-details/game-details.scss @@ -55,10 +55,31 @@ $hero-height: 300px; flex-wrap: wrap; } + &__review-message { + padding: calc(globals.$spacing-unit * 1); + border-radius: 4px; + font-size: globals.$small-font-size; + font-weight: 500; + margin-top: calc(globals.$spacing-unit * 1); + border: 1px solid; + + &--success { + background: rgba(34, 197, 94, 0.1); + color: #86efac; + border-color: rgba(34, 197, 94, 0.3); + } + + &--error { + background: rgba(239, 68, 68, 0.1); + color: #fca5a5; + border-color: rgba(239, 68, 68, 0.3); + } + } + &__review-score-container { display: flex; align-items: center; - gap: 8px; + gap: 4px; } &__review-score-label { @@ -104,6 +125,59 @@ $hero-height: 300px; } } + &__star-rating { + display: flex; + align-items: center; + gap: 4px; + } + + &__star { + background: none; + border: none; + color: #666666; + cursor: pointer; + padding: 4px; + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s ease; + + &:hover { + color: #ffffff; + background-color: rgba(255, 255, 255, 0.1); + transform: scale(1.1); + } + + &--filled { + color: #ffffff; + + &.game-details__review-score-select--red { + color: #e74c3c; + } + + &.game-details__review-score-select--yellow { + color: #f39c12; + } + + &.game-details__review-score-select--green { + color: #27ae60; + } + } + + &--empty { + color: #666666; + + &:hover { + color: #ffffff; + } + } + + svg { + fill: currentColor; + } + } + &__reviews-sort { display: flex; flex-direction: column; @@ -191,16 +265,13 @@ $hero-height: 300px; &__reviews-empty { text-align: center; padding: calc(globals.$spacing-unit * 4) calc(globals.$spacing-unit * 2); - background: rgba(255, 255, 255, 0.02); - border: 1px solid rgba(255, 255, 255, 0.05); - border-radius: 8px; margin-bottom: calc(globals.$spacing-unit * 2); } &__reviews-empty-icon { font-size: 48px; margin-bottom: calc(globals.$spacing-unit * 2); - opacity: 0.6; + color: rgba(255, 255, 255, 0.6); } &__reviews-empty-title { @@ -341,6 +412,7 @@ $hero-height: 300px; color: #f44336; cursor: pointer; transition: all 0.2s ease; + gap: 6px; &:hover { background: rgba(244, 67, 54, 0.2); @@ -387,32 +459,39 @@ $hero-height: 300px; } } - &__review-score { - background: rgba(255, 255, 255, 0.1); - color: rgba(255, 255, 255, 0.9); - padding: calc(globals.$spacing-unit * 0.5) calc(globals.$spacing-unit * 1); - border-radius: 4px; - font-size: globals.$small-font-size; - font-weight: 600; - border: 1px solid rgba(255, 255, 255, 0.15); + &__review-score-stars { + display: flex; + align-items: center; + gap: 2px; + } - // Color variants based on score - &--red { - background: rgba(239, 68, 68, 0.2); - color: #fca5a5; - border-color: rgba(239, 68, 68, 0.4); + &__review-star { + color: #666666; + transition: color 0.2s ease; + cursor: default; + + &--filled { + color: #ffffff; + + &.game-details__review-score--red { + color: #fca5a5; + } + + &.game-details__review-score--yellow { + color: #fcd34d; + } + + &.game-details__review-score--green { + color: #86efac; + } } - &--yellow { - background: rgba(245, 158, 11, 0.2); - color: #fcd34d; - border-color: rgba(245, 158, 11, 0.4); + &--empty { + color: #666666; } - &--green { - background: rgba(34, 197, 94, 0.2); - color: #86efac; - border-color: rgba(34, 197, 94, 0.4); + svg { + fill: currentColor; } } diff --git a/src/renderer/src/pages/game-details/review-prompt-banner.scss b/src/renderer/src/pages/game-details/review-prompt-banner.scss index b8f7557b..f9358e52 100644 --- a/src/renderer/src/pages/game-details/review-prompt-banner.scss +++ b/src/renderer/src/pages/game-details/review-prompt-banner.scss @@ -4,7 +4,7 @@ background: rgba(255, 255, 255, 0.02); border-radius: 8px; padding: calc(globals.$spacing-unit * 2); - margin-bottom: calc(globals.$spacing-unit * 3); + margin-bottom: calc(globals.$spacing-unit * 1.5); border: 1px solid rgba(255, 255, 255, 0.05); &__content { diff --git a/src/renderer/src/pages/game-details/review-prompt-banner.tsx b/src/renderer/src/pages/game-details/review-prompt-banner.tsx index aeddaaad..01fdd075 100644 --- a/src/renderer/src/pages/game-details/review-prompt-banner.tsx +++ b/src/renderer/src/pages/game-details/review-prompt-banner.tsx @@ -18,7 +18,7 @@ export function ReviewPromptBanner({
- You've seemed to enjoy this game + {t("you_seemed_to_enjoy_this_game")} {t("would_you_recommend_this_game")} diff --git a/src/renderer/src/pages/game-details/review-sort-options.tsx b/src/renderer/src/pages/game-details/review-sort-options.tsx index 75ec0f39..0944e58e 100644 --- a/src/renderer/src/pages/game-details/review-sort-options.tsx +++ b/src/renderer/src/pages/game-details/review-sort-options.tsx @@ -35,7 +35,9 @@ export function ReviewSortOptions({ }; const handleMostVotedClick = () => { - onSortChange("most_voted"); + if (sortBy !== "most_voted") { + onSortChange("most_voted"); + } }; const isDateActive = sortBy === "newest" || sortBy === "oldest"; diff --git a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx index d8aa2128..febb6a8b 100755 --- a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx +++ b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx @@ -5,7 +5,7 @@ import type { UserAchievement, } from "@types"; import { useTranslation } from "react-i18next"; -import { Button, Link } from "@renderer/components"; +import { Button, Link, StarRating } from "@renderer/components"; import { gameDetailsContext } from "@renderer/context"; import { useDate, useFormat, useUserDetails } from "@renderer/hooks"; @@ -227,15 +227,18 @@ export function Sidebar() {

{numberFormatter.format(stats?.playerCount)}

- {stats?.averageScore && ( -
-

- - {t("rating_count")} -

-

{stats.averageScore.toFixed(1)}/10

-
- )} +
+

+ + {t("rating_count")} +

+ +
)} diff --git a/src/renderer/src/pages/profile/profile-content/user-karma-box.tsx b/src/renderer/src/pages/profile/profile-content/user-karma-box.tsx index 8c85217d..d2232276 100644 --- a/src/renderer/src/pages/profile/profile-content/user-karma-box.tsx +++ b/src/renderer/src/pages/profile/profile-content/user-karma-box.tsx @@ -6,13 +6,16 @@ import { Award } from "lucide-react"; import "./user-karma-box.scss"; export function UserKarmaBox() { - const { isMe } = useContext(userProfileContext); + const { isMe, userProfile } = useContext(userProfileContext); const { userDetails } = useUserDetails(); const { t } = useTranslation("user_profile"); const { numberFormatter } = useFormat(); - // Only show karma for the current user (me) - if (!isMe || !userDetails) return null; + // Get karma from userDetails (for current user) or userProfile (for other users) + const karma = isMe ? userDetails?.karma : userProfile?.karma; + + // Don't show if karma is not available + if (karma === undefined || karma === null) return null; return (
@@ -24,7 +27,7 @@ export function UserKarmaBox() {

- {numberFormatter.format(userDetails.karma)}{" "} + {numberFormatter.format(karma)}{" "} {t("karma_count")}

diff --git a/src/types/index.ts b/src/types/index.ts index 17ed08cc..0c6af89b 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -203,6 +203,7 @@ export interface UserProfile { currentGame: UserProfileCurrentGame | null; bio: string; hasActiveSubscription: boolean; + karma: number; quirks: { backupsPerGameLimit: number; }; From 063e97e0ec8a3cdff31c7c1e6e0dc85d73cc529e Mon Sep 17 00:00:00 2001 From: Moyasee Date: Sun, 5 Oct 2025 20:36:20 +0300 Subject: [PATCH 023/197] Fix: marked props read-only and catch error --- src/locales/en/translation.json | 2 +- .../src/components/game-card/game-card.scss | 2 +- .../src/components/game-card/game-card.tsx | 2 +- .../src/components/star-rating/index.ts | 2 +- .../components/star-rating/star-rating.scss | 2 +- .../components/star-rating/star-rating.tsx | 39 ++++++---- .../game-details/game-details-content.tsx | 72 ++++++++++++------- .../src/pages/game-details/game-details.scss | 4 +- 8 files changed, 79 insertions(+), 46 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 1af953fe..d118b20b 100755 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -223,7 +223,7 @@ "rating": "Rating", "rating_stats": "Rating", "rating_very_negative": "Very Negative", - "rating_negative": "Negative", + "rating_negative": "Negative", "rating_neutral": "Neutral", "rating_positive": "Positive", "rating_very_positive": "Very Positive", diff --git a/src/renderer/src/components/game-card/game-card.scss b/src/renderer/src/components/game-card/game-card.scss index 99aa866e..1830762f 100644 --- a/src/renderer/src/components/game-card/game-card.scss +++ b/src/renderer/src/components/game-card/game-card.scss @@ -73,7 +73,7 @@ color: globals.$muted-color; font-size: 12px; align-items: center; - + // Ensure star rating is properly aligned .star-rating { align-items: center; diff --git a/src/renderer/src/components/game-card/game-card.tsx b/src/renderer/src/components/game-card/game-card.tsx index 1aa58ba7..6e790500 100644 --- a/src/renderer/src/components/game-card/game-card.tsx +++ b/src/renderer/src/components/game-card/game-card.tsx @@ -109,7 +109,7 @@ export function GameCard({ game, ...props }: GameCardProps) {
- ) { if (rating === null && showCalculating) { return (
@@ -40,25 +40,36 @@ export function StarRating({ return (
- {Array.from({ length: filledStars }, (_, index) => ( - + ))} - {hasHalfStar && (
- - + +
)} - {Array.from({ length: emptyStars }, (_, index) => ( - + ))} - + {rating.toFixed(1)}
); -} \ No newline at end of file +} diff --git a/src/renderer/src/pages/game-details/game-details-content.tsx b/src/renderer/src/pages/game-details/game-details-content.tsx index 2b6de1d8..9060dc39 100644 --- a/src/renderer/src/pages/game-details/game-details-content.tsx +++ b/src/renderer/src/pages/game-details/game-details-content.tsx @@ -1,5 +1,10 @@ import { useContext, useEffect, useMemo, useRef, useState } from "react"; -import { PencilIcon, TrashIcon, ClockIcon, NoteIcon } from "@primer/octicons-react"; +import { + PencilIcon, + TrashIcon, + ClockIcon, + NoteIcon, +} from "@primer/octicons-react"; import { ThumbsUp, ThumbsDown, Star } from "lucide-react"; import { useNavigate } from "react-router-dom"; import { useEditor, EditorContent } from "@tiptap/react"; @@ -68,12 +73,18 @@ const getSelectScoreColorClass = (score: number): string => { const getRatingText = (score: number, t: (key: string) => string): string => { switch (score) { - case 1: return t("rating_very_negative"); - case 2: return t("rating_negative"); - case 3: return t("rating_neutral"); - case 4: return t("rating_positive"); - case 5: return t("rating_very_positive"); - default: return ""; + case 1: + return t("rating_very_negative"); + case 2: + return t("rating_negative"); + case 3: + return t("rating_neutral"); + case 4: + return t("rating_positive"); + case 5: + return t("rating_very_positive"); + default: + return ""; } }; @@ -163,23 +174,23 @@ export function GameDetailsContent() { handlePaste: (view, event) => { const htmlContent = event.clipboardData?.getData("text/html") || ""; const plainText = event.clipboardData?.getData("text/plain") || ""; - + const currentText = view.state.doc.textContent; const remainingChars = MAX_REVIEW_CHARS - currentText.length; if ((htmlContent || plainText) && remainingChars > 0) { event.preventDefault(); - + if (htmlContent) { const tempDiv = document.createElement("div"); tempDiv.innerHTML = htmlContent; const textLength = tempDiv.textContent?.length || 0; - + if (textLength <= remainingChars) { - return false; + return false; } } - + const truncatedText = plainText.slice(0, remainingChars); view.dispatch(view.state.tr.insertText(truncatedText)); return true; @@ -343,7 +354,7 @@ export function GameDetailsContent() { } setSubmittingReview(true); - + try { await window.electron.createGameReview( shop, @@ -355,12 +366,13 @@ export function GameDetailsContent() { editor?.commands.clearContent(); setReviewScore(null); showSuccessToast(t("review_submitted_successfully")); - - await loadReviews(true); - setShowReviewForm(false); - setShowReviewPrompt(false); + + await loadReviews(true); + setShowReviewForm(false); + setShowReviewPrompt(false); setHasUserReviewed(true); } catch (error) { + console.error("Failed to submit review:", error); showErrorToast(t("review_submission_failed")); } finally { setSubmittingReview(false); @@ -387,7 +399,7 @@ export function GameDetailsContent() { const handleReviewPromptLater = () => { setShowReviewPrompt(false); if (objectId) { - sessionStorage.setItem(`reviewPromptDismissed_${objectId}`, 'true'); + sessionStorage.setItem(`reviewPromptDismissed_${objectId}`, "true"); } }; @@ -422,7 +434,7 @@ export function GameDetailsContent() { useEffect(() => { if (objectId && (game || shop)) { loadReviews(true); - checkUserReview(); + checkUserReview(); } }, [game, shop, objectId, reviewsSortBy, userDetails]); @@ -661,9 +673,13 @@ export function GameDetailsContent() { onClick={() => setReviewScore(starValue)} title={getRatingText(starValue, t)} > - ))} @@ -684,7 +700,6 @@ export function GameDetailsContent() { ? t("submitting") : t("submit_review")} -
@@ -774,12 +789,19 @@ export function GameDetailsContent() {
-
+
{[1, 2, 3, 4, 5].map((starValue) => ( Date: Sun, 5 Oct 2025 21:38:43 +0300 Subject: [PATCH 024/197] Update translation.json --- src/locales/ru/translation.json | 136 ++++++++++++++++++++++---------- 1 file changed, 93 insertions(+), 43 deletions(-) diff --git a/src/locales/ru/translation.json b/src/locales/ru/translation.json index 8992a4a0..c316977a 100644 --- a/src/locales/ru/translation.json +++ b/src/locales/ru/translation.json @@ -6,8 +6,8 @@ "home": { "surprise_me": "Удиви меня", "no_results": "Ничего не найдено", - "hot": "Сейчас популярно", "start_typing": "Начинаю вводить текст...", + "hot": "Сейчас популярно", "weekly": "📅 Лучшие игры недели", "achievements": "🏆 Игры с достижениями" }, @@ -28,6 +28,8 @@ "need_help": "Нужна помощь?", "favorites": "Избранное", "playable_button_title": "Показать только установленные игры.", + "add_custom_game_tooltip": "Добавить пользовательскую игру", + "show_playable_only_tooltip": "Показать только доступные для игры", "custom_game_modal": "Добавить пользовательскую игру", "custom_game_modal_description": "Добавьте пользовательскую игру в библиотеку, выбрав исполняемый файл", "custom_game_modal_executable_path": "Путь к исполняемому файлу", @@ -135,6 +137,7 @@ "amount_minutes": "{{amount}} минут", "accuracy": "точность {{accuracy}}%", "add_to_library": "Добавить в библиотеку", + "already_in_library": "Уже в библиотеке", "remove_from_library": "Удалить из библиотеки", "no_downloads": "Нет доступных источников", "play_time": "Сыграно {{amount}}", @@ -163,11 +166,13 @@ "open_folder": "Открыть папку", "open_download_location": "Просмотреть папку загрузок", "create_shortcut": "Создать ярлык на рабочем столе", + "create_shortcut_simple": "Создать ярлык", "clear": "Очистить", "remove_files": "Удалить файлы", "remove_from_library_title": "Вы уверены?", "remove_from_library_description": "{{game}} будет удалена из вашей библиотеки.", "options": "Настройки", + "properties": "Свойства", "executable_section_title": "Файл", "executable_section_description": "Путь к файлу, который будет запущен при нажатии на \"Play\"", "downloads_section_title": "Загрузки", @@ -177,18 +182,27 @@ "download_in_progress": "Идёт загрузка", "download_paused": "Загрузка приостановлена", "last_downloaded_option": "Последний вариант загрузки", + "create_steam_shortcut": "Создать ярлык Steam", "create_shortcut_success": "Ярлык создан", + "you_might_need_to_restart_steam": "Возможно, вам потребуется перезапустить Steam, чтобы увидеть изменения", "create_shortcut_error": "Не удалось создать ярлык", - "allow_nsfw_content": "Продолжить", - "download": "Скачать", - "download_count": "Загрузки", - "download_error": "Этот вариант загрузки недоступен", - "executable_path_in_use": "Исполняемый файл уже используется \"{{game}}\"", - "nsfw_content_description": "{{title}} содержит контент, который может не подходить для всех возрастов. \nВы уверены, что хотите продолжить?", + "add_to_favorites": "Добавить в избранное", + "remove_from_favorites": "Удалить из избранного", + "failed_update_favorites": "Не удалось обновить избранное", + "game_removed_from_library": "Игра удалена из библиотеки", + "failed_remove_from_library": "Не удалось удалить из библиотеки", + "files_removed_success": "Файлы успешно удалены", + "failed_remove_files": "Не удалось удалить файлы", "nsfw_content_title": "Эта игра содержит неприемлемый контент", + "nsfw_content_description": "{{title}} содержит контент, который может не подходить для всех возрастов. \nВы уверены, что хотите продолжить?", + "allow_nsfw_content": "Продолжить", "refuse_nsfw_content": "Назад", "stats": "Статистика", + "download_count": "Загрузки", "player_count": "Активные игроки", + "download_error": "Этот вариант загрузки недоступен", + "download": "Скачать", + "executable_path_in_use": "Исполняемый файл уже используется \"{{game}}\"", "warning": "Внимание:", "hydra_needs_to_remain_open": "Для этой загрузки Hydra должна оставаться открытой до завершения. Если Hydra закроется до завершения, вы потеряете прогресс.", "achievements": "Достижения", @@ -244,26 +258,29 @@ "update_playtime_title": "Обновить время игры", "update_playtime_description": "Вручную обновите время игры для {{game}}", "update_playtime": "Обновить время игры", + "update_playtime_success": "Время игры успешно обновлено", + "update_playtime_error": "Не удалось обновить время игры", "update_game_playtime": "Обновить время игры", + "manual_playtime_warning": "Ваши часы будут отмечены как обновленные вручную. Это действие нельзя отменить.", + "manual_playtime_tooltip": "Это время игры было обновлено вручную", "download_error_not_cached_on_torbox": "Эта загрузка недоступна на TorBox, и получить статус загрузки с TorBox пока невозможно.", - "game_added_to_favorites": "Игра добавлена в избранное", + "download_error_not_cached_on_hydra": "Эта загрузка недоступна на Nimbus.", "game_removed_from_favorites": "Игра удалена из избранного", + "game_added_to_favorites": "Игра добавлена в избранное", + "game_removed_from_pinned": "Игра удалена из закрепленных", + "game_added_to_pinned": "Игра добавлена в закрепленные", "automatically_extract_downloaded_files": "Автоматическая распаковка загруженных файлов", - "create_steam_shortcut": "Создать ярлык Steam", - "you_might_need_to_restart_steam": "Возможно, вам потребуется перезапустить Steam, чтобы увидеть изменения", "create_start_menu_shortcut": "Создать ярлык в меню «Пуск»", "invalid_wine_prefix_path": "Недопустимый путь префикса Wine", "invalid_wine_prefix_path_description": "Путь к префиксу Wine недействителен. Пожалуйста, проверьте путь и попробуйте снова.", "missing_wine_prefix": "Префикс Wine необходим для создания резервной копии в Linux", - "download_error_not_cached_on_hydra": "Эта загрузка недоступна на Nimbus.", - "update_playtime_success": "Время игры успешно обновлено", - "update_playtime_error": "Не удалось обновить время игры", - "manual_playtime_warning": "Ваши часы будут отмечены как обновленные вручную. Это действие нельзя отменить.", "artifact_renamed": "Резервная копия успешно переименована", "rename_artifact": "Переименовать резервную копию", "rename_artifact_description": "Переименуйте резервную копию, присвоив ей более описательное имя.", "artifact_name_label": "Название резервной копии", "artifact_name_placeholder": "Введите название для резервной копии", + "save_changes": "Сохранить изменения", + "required_field": "Это поле обязательно к заполнению", "max_length_field": "Это поле должно содержать менее {{length}} символов", "freeze_backup": "Закрепить, чтобы она не была перезаписана автоматическими резервными копиями", "unfreeze_backup": "Открепить", @@ -271,7 +288,22 @@ "backup_unfrozen": "Резервная копия откреплена", "backup_freeze_failed": "Не удалось закрепить резервную копию", "backup_freeze_failed_description": "Вы должны оставить как минимум один свободный слот для автоматических резервных копий", - "manual_playtime_tooltip": "Это время игры было обновлено вручную" + "edit_game_modal_button": "Настроить ресурсы игры", + "game_details": "Детали игры", + "currency_symbol": "₽", + "currency_country": "ru", + "prices": "Цены", + "no_prices_found": "Цены не найдены", + "view_all_prices": "Нажмите, чтобы посмотреть все цены", + "retail_price": "Розничная цена", + "keyshop_price": "Цена в магазине ключей", + "historical_retail": "Исторические розничные цены", + "historical_keyshop": "Исторические цены в магазинах ключей", + "language": "Язык", + "caption": "Субтитры", + "audio": "Аудио", + "filter_by_source": "Фильтр по источнику", + "no_repacks_found": "Источники для этой игры не найдены" }, "activation": { "title": "Активировать Hydra", @@ -309,6 +341,7 @@ "stop_seeding": "Остановить раздачу", "resume_seeding": "Продолжить раздачу", "options": "Управлять", + "alldebrid_size_not_supported": "Информация о загрузке для AllDebrid пока не поддерживается", "extract": "Распаковать файлы", "extracting": "Распаковка файлов…" }, @@ -317,13 +350,10 @@ "change": "Изменить", "notifications": "Уведомления", "enable_download_notifications": "По завершении загрузки", - "enable_achievement_notifications": "Когда достижение разблокировано", "enable_repack_list_notifications": "При добавлении нового репака", "real_debrid_api_token_label": "Real-Debrid API-токен", "quit_app_instead_hiding": "Закрывать приложение вместо сворачивания в трей", "launch_with_system": "Запускать Hydra вместе с системой", - "launch_minimized": "Запустить Hydra в свернутом виде", - "disable_nsfw_alert": "Отключить предупреждение о непристойном контенте", "general": "Основные", "behavior": "Поведение", "download_sources": "Источники загрузки", @@ -350,11 +380,11 @@ "download_source_errored": "Ошибка", "sync_download_sources": "Обновить источники", "removed_download_source": "Источник удален", + "removed_download_sources": "Источники удалены", "cancel_button_confirmation_delete_all_sources": "Нет", "confirm_button_confirmation_delete_all_sources": "Да, удалить все", - "description_confirmation_delete_all_sources": "Вы удалите все источники", "title_confirmation_delete_all_sources": "Удалить все источники", - "removed_download_sources": "Источники удалены", + "description_confirmation_delete_all_sources": "Вы удалите все источники", "button_delete_all_sources": "Удалить все источники", "added_download_source": "Источник добавлен", "download_sources_synced": "Все источники обновлены", @@ -363,17 +393,20 @@ "found_download_option_one": "Найден {{countFormatted}} вариант загрузки", "found_download_option_other": "Найдено {{countFormatted}} вариантов загрузки", "import": "Импортировать", - "blocked_users": "Заблокированные пользователи", - "friends_only": "Только для друзей", - "must_be_valid_url": "У источника должен быть правильный URL", - "privacy": "Конфиденциальность", + "public": "Публичный", "private": "Частный", + "friends_only": "Только для друзей", + "privacy": "Конфиденциальность", "profile_visibility": "Видимость профиля", "profile_visibility_description": "Выберите, кто может видеть ваш профиль и библиотеку", - "public": "Публичный", "required_field": "Это поле обязательно к заполнению", "source_already_exists": "Этот источник уже добавлен", + "must_be_valid_url": "У источника должен быть правильный URL", + "blocked_users": "Заблокированные пользователи", "user_unblocked": "Пользователь разблокирован", + "enable_achievement_notifications": "Когда достижение разблокировано", + "launch_minimized": "Запустить Hydra в свернутом виде", + "disable_nsfw_alert": "Отключить предупреждение о непристойном контенте", "seed_after_download_complete": "Раздавать после завершения загрузки", "show_hidden_achievement_description": "Показывать описание скрытых достижений перед их получением", "account": "Аккаунт", @@ -415,9 +448,20 @@ "enable_torbox": "Включить TorBox", "torbox_description": "TorBox - это ваш премиум-сервис, конкурирующий даже с лучшими серверами на рынке.", "torbox_account_linked": "Аккаунт TorBox привязан", - "real_debrid_account_linked": "Аккаунт Real-Debrid привязан", "create_real_debrid_account": "Нажмите здесь, если у вас еще нет аккаунта Real-Debrid", "create_torbox_account": "Нажмите здесь, если у вас еще нет учетной записи TorBox", + "real_debrid_account_linked": "Аккаунт Real-Debrid привязан", + "enable_all_debrid": "Включить All-Debrid", + "all_debrid_description": "All-Debrid - это неограниченный загрузчик, который позволяет быстро скачивать файлы из различных источников.", + "all_debrid_free_account_error": "Аккаунт \"{{username}}\" является бесплатным. Пожалуйста, оформите подписку на All-Debrid", + "all_debrid_account_linked": "Аккаунт All-Debrid успешно привязан", + "alldebrid_missing_key": "Пожалуйста, предоставьте API ключ", + "alldebrid_invalid_key": "Неверный API ключ", + "alldebrid_blocked": "Ваш API ключ заблокирован по геолокации или IP", + "alldebrid_banned": "Этот аккаунт был заблокирован", + "alldebrid_unknown_error": "Произошла неизвестная ошибка", + "alldebrid_invalid_response": "Неверный ответ от All-Debrid", + "alldebrid_network_error": "Ошибка сети. Пожалуйста, проверьте соединение", "name_min_length": "Название темы должно содержать не менее 3 символов", "import_theme": "Импортировать тему", "import_theme_description": "Вы импортируете {{theme}} из магазина тем", @@ -431,6 +475,7 @@ "installing_common_redist": "Установка…", "show_download_speed_in_megabytes": "Показать скорость загрузки в мегабайтах в секунду", "extract_files_by_default": "Извлекать файлы по умолчанию после загрузки", + "enable_steam_achievements": "Включить поиск достижений Steam", "achievement_custom_notification_position": "Позиция уведомлений достижений", "top-left": "Верхний левый угол", "top-center": "Верхний центр", @@ -447,8 +492,7 @@ "hidden": "Скрытый", "test_notification": "Тестовое уведомление", "notification_preview": "Предварительный просмотр уведомления о достижении", - "enable_friend_start_game_notifications": "Когда друг начинает играть в игру", - "enable_steam_achievements": "Включить поиск достижений Steam" + "enable_friend_start_game_notifications": "Когда друг начинает играть в игру" }, "notifications": { "download_complete": "Загрузка завершена", @@ -460,13 +504,13 @@ "restart_to_install_update": "Перезапустите Hydra для установки обновления", "notification_achievement_unlocked_title": "Достижение разблокировано для {{game}}", "notification_achievement_unlocked_body": "были разблокированы {{achievement}} и другие {{count}}", + "new_friend_request_description": "{{displayName}} отправил вам запрос в друзья", "new_friend_request_title": "Новый запрос на добавление в друзья", "extraction_complete": "Распаковка завершена", "game_extracted": "{{title}} успешно распакован", "friend_started_playing_game": "{{displayName}} начал играть в игру", "test_achievement_notification_title": "Это тестовое уведомление", - "test_achievement_notification_description": "Довольно круто, да?", - "new_friend_request_description": "{{displayName}} отправил вам запрос в друзья" + "test_achievement_notification_description": "Довольно круто, да?" }, "system_tray": { "open": "Открыть Hydra", @@ -496,6 +540,10 @@ "last_time_played": "Последняя игра {{period}}", "activity": "Недавняя активность", "library": "Библиотека", + "pinned": "Закрепленные", + "achievements_earned": "Заработанные достижения", + "played_recently": "Недавно сыгранные", + "playtime": "Время игры", "total_play_time": "Всего сыграно", "manual_playtime_tooltip": "Время игры было обновлено вручную", "no_recent_activity_title": "Хммм... Тут ничего нет", @@ -539,24 +587,24 @@ "no_pending_invites": "У вас нет запросов ожидающих ответа", "no_blocked_users": "Вы не заблокировали ни одного пользователя", "friend_code_copied": "Код друга скопирован", - "displayname_max_length": "Отображаемое имя должно содержать не более 50 символов.", - "displayname_min_length": "Отображаемое имя должно содержать не менее 3 символов.", - "image_process_failure": "Сбой при обработке изображения", - "locked_profile": "Этот профиль является частным", + "undo_friendship_modal_text": "Это отменит вашу дружбу с {{displayName}}.", "privacy_hint": "Чтобы указать, кто может это видеть, перейдите в <0>Настройки.", - "profile_reported": "Профиль сообщил", - "report": "Отчет", - "report_description": "Дополнительная информация", - "report_description_placeholder": "Дополнительная информация", + "locked_profile": "Этот профиль является частным", + "image_process_failure": "Сбой при обработке изображения", + "required_field": "Это поле обязательно к заполнению", + "displayname_min_length": "Отображаемое имя должно содержать не менее 3 символов.", + "displayname_max_length": "Отображаемое имя должно содержать не более 50 символов.", "report_profile": "Пожаловаться на этот профиль", "report_reason": "Почему вы жалуетесь на этот профиль?", + "report_description": "Дополнительная информация", + "report_description_placeholder": "Дополнительная информация", + "report": "Отчет", "report_reason_hate": "Разжигание ненависти", - "report_reason_other": "Другой", "report_reason_sexual_content": "Сексуальный контент", - "report_reason_spam": "Спам", "report_reason_violence": "Насилие", - "required_field": "Это поле обязательно к заполнению", - "undo_friendship_modal_text": "Это отменит вашу дружбу с {{displayName}}.", + "report_reason_spam": "Спам", + "report_reason_other": "Другой", + "profile_reported": "Профиль сообщил", "your_friend_code": "Код вашего друга:", "upload_banner": "Загрузить баннер", "uploading_banner": "Загрузка баннера...", @@ -572,7 +620,9 @@ "show_achievements_on_profile": "Покажите свои достижения в профиле", "show_points_on_profile": "Показывать заработанные очки в своем профиле", "error_adding_friend": "Не удалось отправить запрос в друзья. Пожалуйста, проверьте код друга", - "friend_code_length_error": "Код друга должен содержать 8 символов" + "friend_code_length_error": "Код друга должен содержать 8 символов", + "game_removed_from_pinned": "Игра удалена из закрепленных", + "game_added_to_pinned": "Игра добавлена в закрепленные" }, "achievement": { "achievement_unlocked": "Достижение разблокировано", From b0d9d18c6cc04c94274b01ca8840ba8912257261 Mon Sep 17 00:00:00 2001 From: Wkeynhk <86107421+Wkeynhk@users.noreply.github.com> Date: Mon, 6 Oct 2025 00:35:24 +0300 Subject: [PATCH 025/197] Update translation.json --- src/locales/ru/translation.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/locales/ru/translation.json b/src/locales/ru/translation.json index c316977a..bf9bf751 100644 --- a/src/locales/ru/translation.json +++ b/src/locales/ru/translation.json @@ -288,7 +288,7 @@ "backup_unfrozen": "Резервная копия откреплена", "backup_freeze_failed": "Не удалось закрепить резервную копию", "backup_freeze_failed_description": "Вы должны оставить как минимум один свободный слот для автоматических резервных копий", - "edit_game_modal_button": "Настроить ресурсы игры", + "edit_game_modal_button": "Изменить детали игры", "game_details": "Детали игры", "currency_symbol": "₽", "currency_country": "ru", @@ -405,7 +405,7 @@ "blocked_users": "Заблокированные пользователи", "user_unblocked": "Пользователь разблокирован", "enable_achievement_notifications": "Когда достижение разблокировано", - "launch_minimized": "Запустить Hydra в свернутом виде", + "launch_minimized": "Запускать Hydra в свернутом виде", "disable_nsfw_alert": "Отключить предупреждение о непристойном контенте", "seed_after_download_complete": "Раздавать после завершения загрузки", "show_hidden_achievement_description": "Показывать описание скрытых достижений перед их получением", @@ -598,13 +598,13 @@ "report_reason": "Почему вы жалуетесь на этот профиль?", "report_description": "Дополнительная информация", "report_description_placeholder": "Дополнительная информация", - "report": "Отчет", + "report": "Пожаловаться", "report_reason_hate": "Разжигание ненависти", "report_reason_sexual_content": "Сексуальный контент", "report_reason_violence": "Насилие", "report_reason_spam": "Спам", - "report_reason_other": "Другой", - "profile_reported": "Профиль сообщил", + "report_reason_other": "Другое", + "profile_reported": "Жалоба на профиль отправлена", "your_friend_code": "Код вашего друга:", "upload_banner": "Загрузить баннер", "uploading_banner": "Загрузка баннера...", From 055be6b10a2c149bd3316cdf049192eb925c2ee9 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Mon, 6 Oct 2025 15:41:12 +0300 Subject: [PATCH 026/197] Fix: review prompt banner appearing in all games --- .../src/components/game-card/game-card.scss | 3 +-- .../src/pages/game-details/game-details-content.tsx | 13 +++++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/renderer/src/components/game-card/game-card.scss b/src/renderer/src/components/game-card/game-card.scss index 1830762f..46c6bec9 100644 --- a/src/renderer/src/components/game-card/game-card.scss +++ b/src/renderer/src/components/game-card/game-card.scss @@ -73,8 +73,7 @@ color: globals.$muted-color; font-size: 12px; align-items: center; - - // Ensure star rating is properly aligned + .star-rating { align-items: center; } diff --git a/src/renderer/src/pages/game-details/game-details-content.tsx b/src/renderer/src/pages/game-details/game-details-content.tsx index 9060dc39..16ee7386 100644 --- a/src/renderer/src/pages/game-details/game-details-content.tsx +++ b/src/renderer/src/pages/game-details/game-details-content.tsx @@ -106,7 +106,7 @@ export function GameDetailsContent() { const { showHydraCloudModal } = useSubscription(); const { userDetails, hasActiveSubscription } = useUserDetails(); - const { updateLibrary } = useLibrary(); + const { updateLibrary, library } = useLibrary(); const { formatDistance } = useDate(); const { showSuccessToast, showErrorToast } = useToast(); @@ -159,6 +159,14 @@ export function GameDetailsContent() { const [hasUserReviewed, setHasUserReviewed] = useState(false); const [reviewCheckLoading, setReviewCheckLoading] = useState(false); + // Check if the current game is in the user's library + const isGameInLibrary = useMemo(() => { + if (!library || !shop || !objectId) return false; + return library.some( + (libItem) => libItem.shop === shop && libItem.objectId === objectId + ); + }, [library, shop, objectId]); + const editor = useEditor({ extensions: [ StarterKit.configure({ @@ -561,7 +569,8 @@ export function GameDetailsContent() { showReviewPrompt && userDetails && !hasUserReviewed && - !reviewCheckLoading && ( + !reviewCheckLoading && + isGameInLibrary && ( Date: Mon, 6 Oct 2025 15:48:19 +0300 Subject: [PATCH 027/197] fix: formatting --- src/renderer/src/components/game-card/game-card.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/src/components/game-card/game-card.scss b/src/renderer/src/components/game-card/game-card.scss index 46c6bec9..9d1eaf93 100644 --- a/src/renderer/src/components/game-card/game-card.scss +++ b/src/renderer/src/components/game-card/game-card.scss @@ -73,7 +73,7 @@ color: globals.$muted-color; font-size: 12px; align-items: center; - + .star-rating { align-items: center; } From 47ac8e63acdcbe150bacc564dd35851b7b2efa50 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Mon, 6 Oct 2025 18:30:56 +0300 Subject: [PATCH 028/197] fix: fixed button layout in prompt message and fixed rating display in stats in game page --- src/renderer/src/components/star-rating/star-rating.tsx | 6 ++++-- .../src/pages/game-details/review-prompt-banner.tsx | 6 +++--- src/renderer/src/pages/game-details/sidebar/sidebar.tsx | 5 +++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/renderer/src/components/star-rating/star-rating.tsx b/src/renderer/src/components/star-rating/star-rating.tsx index cdd04e03..50d08afd 100644 --- a/src/renderer/src/components/star-rating/star-rating.tsx +++ b/src/renderer/src/components/star-rating/star-rating.tsx @@ -7,6 +7,7 @@ export interface StarRatingProps { size?: number; showCalculating?: boolean; calculatingText?: string; + hideIcon?: boolean; } export function StarRating({ @@ -15,11 +16,12 @@ export function StarRating({ size = 12, showCalculating = false, calculatingText = "Calculating", + hideIcon = false, }: Readonly) { if (rating === null && showCalculating) { return (
- + {!hideIcon && } {calculatingText}
); @@ -28,7 +30,7 @@ export function StarRating({ if (rating === null || rating === undefined) { return (
- + {!hideIcon && }
); diff --git a/src/renderer/src/pages/game-details/review-prompt-banner.tsx b/src/renderer/src/pages/game-details/review-prompt-banner.tsx index 01fdd075..053c97e8 100644 --- a/src/renderer/src/pages/game-details/review-prompt-banner.tsx +++ b/src/renderer/src/pages/game-details/review-prompt-banner.tsx @@ -25,12 +25,12 @@ export function ReviewPromptBanner({
- +
diff --git a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx index febb6a8b..33009508 100755 --- a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx +++ b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx @@ -233,10 +233,11 @@ export function Sidebar() { {t("rating_count")}

From a9b67ad1e6062d43b76848c8369d1d90fff06218 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Fri, 10 Oct 2025 14:25:09 -0300 Subject: [PATCH 029/197] feat: handle and log game backup errors --- src/locales/en/translation.json | 1 + src/locales/es/translation.json | 1 + src/locales/pt-PT/translation.json | 1 + src/main/services/steam.ts | 6 +++- .../confirm-modal/confirm-modal.tsx | 13 ++++++-- .../context/cloud-sync/cloud-sync.context.tsx | 12 +++++-- .../game-details/game-details.context.tsx | 3 +- .../game-details/hero/hero-panel-actions.tsx | 32 +++++++++++++++---- .../game-details/modals/repacks-modal.tsx | 2 +- 9 files changed, 56 insertions(+), 15 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 0c1ed914..d5a0708c 100755 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -219,6 +219,7 @@ "uploading_backup": "Uploading backup…", "no_backups": "You haven't created any backups for this game yet", "backup_uploaded": "Backup uploaded", + "backup_failed": "Backup failed", "backup_deleted": "Backup deleted", "backup_restored": "Backup restored", "see_all_achievements": "See all achievements", diff --git a/src/locales/es/translation.json b/src/locales/es/translation.json index 6f0fc9f1..226f77af 100644 --- a/src/locales/es/translation.json +++ b/src/locales/es/translation.json @@ -204,6 +204,7 @@ "uploading_backup": "Subiendo copia de seguridad…", "no_backups": "No has creado ninguna copia de seguridad para este juego todavía", "backup_uploaded": "Copia de seguridad subida", + "backup_failed": "Copia de seguridad fallida", "backup_deleted": "Copia de seguridad eliminada", "backup_restored": "Copia de seguridad restaurada", "see_all_achievements": "Ver todos los logros", diff --git a/src/locales/pt-PT/translation.json b/src/locales/pt-PT/translation.json index 654e94ec..d36e3083 100644 --- a/src/locales/pt-PT/translation.json +++ b/src/locales/pt-PT/translation.json @@ -142,6 +142,7 @@ "uploading_backup": "A criar backup…", "no_backups": "Ainda não fizeste nenhum backup deste jogo", "backup_uploaded": "Backup criado", + "backup_failed": "Falha ao criar backup", "backup_deleted": "Backup apagado", "backup_restored": "Backup restaurado", "see_all_achievements": "Ver todas as conquistas", diff --git a/src/main/services/steam.ts b/src/main/services/steam.ts index bc867111..e3aad8d9 100644 --- a/src/main/services/steam.ts +++ b/src/main/services/steam.ts @@ -76,7 +76,11 @@ export const getSteamAppDetails = async ( return null; }) .catch((err) => { - logger.error(err, { method: "getSteamAppDetails" }); + logger.error("Error on getSteamAppDetails", { + message: err?.message, + code: err?.code, + name: err?.name, + }); return null; }); }; diff --git a/src/renderer/src/components/confirm-modal/confirm-modal.tsx b/src/renderer/src/components/confirm-modal/confirm-modal.tsx index d210c035..75a8f5c9 100644 --- a/src/renderer/src/components/confirm-modal/confirm-modal.tsx +++ b/src/renderer/src/components/confirm-modal/confirm-modal.tsx @@ -33,9 +33,18 @@ export function ConfirmModal({ }; return ( - +
- diff --git a/src/renderer/src/context/cloud-sync/cloud-sync.context.tsx b/src/renderer/src/context/cloud-sync/cloud-sync.context.tsx index f9287a11..e1ea9e2f 100644 --- a/src/renderer/src/context/cloud-sync/cloud-sync.context.tsx +++ b/src/renderer/src/context/cloud-sync/cloud-sync.context.tsx @@ -87,7 +87,7 @@ export function CloudSyncContextProvider({ const [loadingPreview, setLoadingPreview] = useState(false); const [freezingArtifact, setFreezingArtifact] = useState(false); - const { showSuccessToast } = useToast(); + const { showSuccessToast, showErrorToast } = useToast(); const downloadGameArtifact = useCallback( async (gameArtifactId: string) => { @@ -122,9 +122,15 @@ export function CloudSyncContextProvider({ const uploadSaveGame = useCallback( async (downloadOptionTitle: string | null) => { setUploadingBackup(true); - window.electron.uploadSaveGame(objectId, shop, downloadOptionTitle); + window.electron + .uploadSaveGame(objectId, shop, downloadOptionTitle) + .catch((err) => { + setUploadingBackup(false); + logger.error("Failed to upload save game", { objectId, shop, err }); + showErrorToast(t("backup_failed")); + }); }, - [objectId, shop] + [objectId, shop, t, showErrorToast] ); const toggleArtifactFreeze = useCallback( diff --git a/src/renderer/src/context/game-details/game-details.context.tsx b/src/renderer/src/context/game-details/game-details.context.tsx index 23ea3845..5be5cf98 100644 --- a/src/renderer/src/context/game-details/game-details.context.tsx +++ b/src/renderer/src/context/game-details/game-details.context.tsx @@ -201,7 +201,8 @@ export function GameDetailsContextProvider({ }, [objectId, gameTitle, dispatch]); useEffect(() => { - const state = (location && (location.state as Record)) || {}; + const state = + (location && (location.state as Record)) || {}; if (state.openRepacks) { setShowRepacksModal(true); try { diff --git a/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx b/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx index ac8a1615..e23120a8 100644 --- a/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx +++ b/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx @@ -69,14 +69,32 @@ export function HeroPanelActions() { updateGame(); }; - window.addEventListener("hydra:game-favorite-toggled", onFavoriteToggled as EventListener); - window.addEventListener("hydra:game-removed-from-library", onGameRemoved as EventListener); - window.addEventListener("hydra:game-files-removed", onFilesRemoved as EventListener); + window.addEventListener( + "hydra:game-favorite-toggled", + onFavoriteToggled as EventListener + ); + window.addEventListener( + "hydra:game-removed-from-library", + onGameRemoved as EventListener + ); + window.addEventListener( + "hydra:game-files-removed", + onFilesRemoved as EventListener + ); return () => { - window.removeEventListener("hydra:game-favorite-toggled", onFavoriteToggled as EventListener); - window.removeEventListener("hydra:game-removed-from-library", onGameRemoved as EventListener); - window.removeEventListener("hydra:game-files-removed", onFilesRemoved as EventListener); + window.removeEventListener( + "hydra:game-favorite-toggled", + onFavoriteToggled as EventListener + ); + window.removeEventListener( + "hydra:game-removed-from-library", + onGameRemoved as EventListener + ); + window.removeEventListener( + "hydra:game-files-removed", + onFilesRemoved as EventListener + ); }; }, [updateLibrary, updateGame]); @@ -226,7 +244,7 @@ export function HeroPanelActions() { onClick={() => setShowRepacksModal(true)} theme="outline" disabled={isGameDownloading} - className={`hero-panel-actions__action ${!repacks.length ? 'hero-panel-actions__action--disabled' : ''}`} + className={`hero-panel-actions__action ${!repacks.length ? "hero-panel-actions__action--disabled" : ""}`} > {t("download")} diff --git a/src/renderer/src/pages/game-details/modals/repacks-modal.tsx b/src/renderer/src/pages/game-details/modals/repacks-modal.tsx index ec7dc3f8..97b8b1b5 100644 --- a/src/renderer/src/pages/game-details/modals/repacks-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/repacks-modal.tsx @@ -277,4 +277,4 @@ export function RepacksModal({ ); -} \ No newline at end of file +} From 91fd5932dabce08ed2c4e6fa9cdef8b14b6b0754 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Fri, 10 Oct 2025 14:40:57 -0300 Subject: [PATCH 030/197] fix: lastTimePlayed not updating correctly when merging games --- src/main/services/cloud-sync.ts | 4 ++-- src/main/services/library-sync/merge-with-remote-games.ts | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/services/cloud-sync.ts b/src/main/services/cloud-sync.ts index 6da24ce1..200a5ee3 100644 --- a/src/main/services/cloud-sync.ts +++ b/src/main/services/cloud-sync.ts @@ -80,7 +80,7 @@ export class CloudSync { try { await fs.promises.rm(backupPath, { recursive: true }); } catch (error) { - logger.error("Failed to remove backup path", error); + logger.error("Failed to remove backup path", { backupPath, error }); } } @@ -163,7 +163,7 @@ export class CloudSync { try { await fs.promises.unlink(bundleLocation); } catch (error) { - logger.error("Failed to remove tar file", error); + logger.error("Failed to remove tar file", { bundleLocation, error }); } } } diff --git a/src/main/services/library-sync/merge-with-remote-games.ts b/src/main/services/library-sync/merge-with-remote-games.ts index b5b2d551..68b4b835 100644 --- a/src/main/services/library-sync/merge-with-remote-games.ts +++ b/src/main/services/library-sync/merge-with-remote-games.ts @@ -22,7 +22,8 @@ export const mergeWithRemoteGames = async () => { const updatedLastTimePlayed = localGame.lastTimePlayed == null || (game.lastTimePlayed && - new Date(game.lastTimePlayed) > localGame.lastTimePlayed) + new Date(game.lastTimePlayed) > + new Date(localGame.lastTimePlayed)) ? game.lastTimePlayed : localGame.lastTimePlayed; From 7b8f7fc0703f0feec513610cbcfd9931a247b2f5 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Fri, 10 Oct 2025 15:17:27 -0300 Subject: [PATCH 031/197] feat: add alternative steam path on linux --- src/main/services/steam.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/services/steam.ts b/src/main/services/steam.ts index e3aad8d9..886772b5 100644 --- a/src/main/services/steam.ts +++ b/src/main/services/steam.ts @@ -19,7 +19,12 @@ export interface SteamAppDetailsResponse { export const getSteamLocation = async () => { if (process.platform === "linux") { - return path.join(SystemPath.getPath("home"), ".local", "share", "Steam"); + const possiblePaths = [ + path.join(SystemPath.getPath("home"), ".steam", "steam"), + path.join(SystemPath.getPath("home"), ".local", "share", "Steam"), + ]; + + return possiblePaths.find((p) => fs.existsSync(p)) || possiblePaths[0]; } if (process.platform === "darwin") { From 9bada771df3a0027946de57f1d59c869485c00bc Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sat, 11 Oct 2025 11:26:05 -0300 Subject: [PATCH 032/197] feat: separate game assets from game stats --- src/main/events/catalogue/get-game-assets.ts | 51 +++++++++++++++++++ .../events/catalogue/save-game-shop-assets.ts | 25 --------- .../library/add-custom-game-to-library.ts | 1 + .../events/library/create-steam-shortcut.ts | 10 ++-- src/main/level/sublevels/game-shop-assets.ts | 12 ++--- .../library-sync/merge-with-remote-games.ts | 4 ++ src/main/services/notifications/index.ts | 7 ++- .../services/ws/events/friend-game-session.ts | 11 ++-- src/preload/index.ts | 5 +- .../game-details/game-details.context.tsx | 40 +++++++-------- src/renderer/src/declaration.d.ts | 10 ++-- .../pages/game-details/sidebar/sidebar.tsx | 13 ++++- .../user-library-game-card.tsx | 2 +- src/types/index.ts | 4 +- 14 files changed, 111 insertions(+), 84 deletions(-) create mode 100644 src/main/events/catalogue/get-game-assets.ts delete mode 100644 src/main/events/catalogue/save-game-shop-assets.ts diff --git a/src/main/events/catalogue/get-game-assets.ts b/src/main/events/catalogue/get-game-assets.ts new file mode 100644 index 00000000..04a03808 --- /dev/null +++ b/src/main/events/catalogue/get-game-assets.ts @@ -0,0 +1,51 @@ +import type { GameShop, ShopAssets } from "@types"; +import { registerEvent } from "../register-event"; +import { HydraApi } from "@main/services"; +import { gamesShopAssetsSublevel, levelKeys } from "@main/level"; + +const LOCAL_CACHE_EXPIRATION = 1000 * 60 * 30; // 30 minutes + +export const getGameAssets = async (objectId: string, shop: GameShop) => { + const cachedAssets = await gamesShopAssetsSublevel.get( + levelKeys.game(shop, objectId) + ); + + if ( + cachedAssets && + cachedAssets.updatedAt + LOCAL_CACHE_EXPIRATION > Date.now() + ) { + return cachedAssets; + } + + return HydraApi.get( + `/games/${shop}/${objectId}/assets`, + null, + { + needsAuth: false, + } + ).then(async (assets) => { + if (!assets) return null; + + // Preserve existing title if it differs from the incoming title (indicating it was customized) + const shouldPreserveTitle = + cachedAssets?.title && cachedAssets.title !== assets.title; + + await gamesShopAssetsSublevel.put(levelKeys.game(shop, objectId), { + ...assets, + title: shouldPreserveTitle ? cachedAssets.title : assets.title, + updatedAt: Date.now(), + }); + + return assets; + }); +}; + +const getGameAssetsEvent = async ( + _event: Electron.IpcMainInvokeEvent, + objectId: string, + shop: GameShop +) => { + return getGameAssets(objectId, shop); +}; + +registerEvent("getGameAssets", getGameAssetsEvent); diff --git a/src/main/events/catalogue/save-game-shop-assets.ts b/src/main/events/catalogue/save-game-shop-assets.ts deleted file mode 100644 index bf5f8b81..00000000 --- a/src/main/events/catalogue/save-game-shop-assets.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { GameShop, ShopAssets } from "@types"; -import { gamesShopAssetsSublevel, levelKeys } from "@main/level"; -import { registerEvent } from "../register-event"; - -const saveGameShopAssets = async ( - _event: Electron.IpcMainInvokeEvent, - objectId: string, - shop: GameShop, - assets: ShopAssets -): Promise => { - const key = levelKeys.game(shop, objectId); - const existingAssets = await gamesShopAssetsSublevel.get(key); - - // Preserve existing title if it differs from the incoming title (indicating it was customized) - const shouldPreserveTitle = - existingAssets?.title && existingAssets.title !== assets.title; - - return gamesShopAssetsSublevel.put(key, { - ...existingAssets, - ...assets, - title: shouldPreserveTitle ? existingAssets.title : assets.title, - }); -}; - -registerEvent("saveGameShopAssets", saveGameShopAssets); diff --git a/src/main/events/library/add-custom-game-to-library.ts b/src/main/events/library/add-custom-game-to-library.ts index 47fd3436..f85c008b 100644 --- a/src/main/events/library/add-custom-game-to-library.ts +++ b/src/main/events/library/add-custom-game-to-library.ts @@ -27,6 +27,7 @@ const addCustomGameToLibrary = async ( } const assets = { + updatedAt: Date.now(), objectId, shop, title, diff --git a/src/main/events/library/create-steam-shortcut.ts b/src/main/events/library/create-steam-shortcut.ts index f83dd675..d5434d7f 100644 --- a/src/main/events/library/create-steam-shortcut.ts +++ b/src/main/events/library/create-steam-shortcut.ts @@ -1,12 +1,11 @@ import { registerEvent } from "../register-event"; -import type { GameShop, GameStats } from "@types"; +import type { GameShop, ShopAssets } from "@types"; import { gamesSublevel, levelKeys } from "@main/level"; import { composeSteamShortcut, getSteamLocation, getSteamShortcuts, getSteamUsersIds, - HydraApi, logger, SystemPath, writeSteamShortcuts, @@ -15,6 +14,7 @@ import fs from "node:fs"; import axios from "axios"; import path from "node:path"; import { ASSETS_PATH } from "@main/constants"; +import { getGameAssets } from "../catalogue/get-game-assets"; const downloadAsset = async (downloadPath: string, url?: string | null) => { try { @@ -41,7 +41,7 @@ const downloadAsset = async (downloadPath: string, url?: string | null) => { const downloadAssetsFromSteam = async ( shop: GameShop, objectId: string, - assets: GameStats["assets"] + assets: ShopAssets | null ) => { const gameAssetsPath = path.join(ASSETS_PATH, `${shop}-${objectId}`); @@ -86,9 +86,7 @@ const createSteamShortcut = async ( throw new Error("No executable path found for game"); } - const { assets } = await HydraApi.get( - `/games/${shop}/${objectId}/stats` - ); + const assets = await getGameAssets(objectId, shop); const steamUserIds = await getSteamUsersIds(); diff --git a/src/main/level/sublevels/game-shop-assets.ts b/src/main/level/sublevels/game-shop-assets.ts index 561d85df..806e041f 100644 --- a/src/main/level/sublevels/game-shop-assets.ts +++ b/src/main/level/sublevels/game-shop-assets.ts @@ -3,9 +3,9 @@ import type { ShopAssets } from "@types"; import { db } from "../level"; import { levelKeys } from "./keys"; -export const gamesShopAssetsSublevel = db.sublevel( - levelKeys.gameShopAssets, - { - valueEncoding: "json", - } -); +export const gamesShopAssetsSublevel = db.sublevel< + string, + ShopAssets & { updatedAt: number } +>(levelKeys.gameShopAssets, { + valueEncoding: "json", +}); diff --git a/src/main/services/library-sync/merge-with-remote-games.ts b/src/main/services/library-sync/merge-with-remote-games.ts index 68b4b835..f7ea2744 100644 --- a/src/main/services/library-sync/merge-with-remote-games.ts +++ b/src/main/services/library-sync/merge-with-remote-games.ts @@ -58,7 +58,11 @@ export const mergeWithRemoteGames = async () => { }); } + const localGameShopAsset = await gamesShopAssetsSublevel.get(gameKey); + await gamesShopAssetsSublevel.put(gameKey, { + updatedAt: Date.now(), + ...localGameShopAsset, shop: game.shop, objectId: game.objectId, title: localGame?.title || game.title, // Preserve local title if it exists diff --git a/src/main/services/notifications/index.ts b/src/main/services/notifications/index.ts index fa9ac593..d28c3cd7 100644 --- a/src/main/services/notifications/index.ts +++ b/src/main/services/notifications/index.ts @@ -10,7 +10,7 @@ import icon from "@resources/icon.png?asset"; import { NotificationOptions, toXmlString } from "./xml"; import { logger } from "../logger"; import { WindowManager } from "../window-manager"; -import type { Game, GameStats, UserPreferences, UserProfile } from "@types"; +import type { Game, UserPreferences, UserProfile } from "@types"; import { db, levelKeys } from "@main/level"; import { restartAndInstallUpdate } from "@main/events/autoupdater/restart-and-install-update"; import { SystemPath } from "../system-path"; @@ -108,15 +108,14 @@ export const publishNewFriendRequestNotification = async ( }; export const publishFriendStartedPlayingGameNotification = async ( - friend: UserProfile, - game: GameStats + friend: UserProfile ) => { new Notification({ title: t("friend_started_playing_game", { ns: "notifications", displayName: friend.displayName, }), - body: game.assets?.title, + body: friend?.currentGame?.title, icon: friend?.profileImageUrl ? await downloadImage(friend.profileImageUrl) : trayIcon, diff --git a/src/main/services/ws/events/friend-game-session.ts b/src/main/services/ws/events/friend-game-session.ts index 67967b3c..b089c421 100644 --- a/src/main/services/ws/events/friend-game-session.ts +++ b/src/main/services/ws/events/friend-game-session.ts @@ -2,7 +2,7 @@ import type { FriendGameSession } from "@main/generated/envelope"; import { db, levelKeys } from "@main/level"; import { HydraApi } from "@main/services/hydra-api"; import { publishFriendStartedPlayingGameNotification } from "@main/services/notifications"; -import type { GameStats, UserPreferences, UserProfile } from "@types"; +import type { UserPreferences, UserProfile } from "@types"; export const friendGameSessionEvent = async (payload: FriendGameSession) => { const userPreferences = await db.get( @@ -14,12 +14,9 @@ export const friendGameSessionEvent = async (payload: FriendGameSession) => { if (userPreferences?.friendStartGameNotificationsEnabled === false) return; - const [friend, gameStats] = await Promise.all([ - HydraApi.get(`/users/${payload.friendId}`), - HydraApi.get(`/games/steam/${payload.objectId}/stats`), - ]).catch(() => [null, null]); + const friend = await HydraApi.get(`/users/${payload.friendId}`); - if (friend && gameStats) { - publishFriendStartedPlayingGameNotification(friend, gameStats); + if (friend) { + publishFriendStartedPlayingGameNotification(friend); } }; diff --git a/src/preload/index.ts b/src/preload/index.ts index 813758f0..e26909d4 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -17,7 +17,6 @@ import type { Theme, FriendRequestSync, ShortcutLocation, - ShopAssets, AchievementCustomNotificationPosition, AchievementNotificationInfo, } from "@types"; @@ -67,8 +66,6 @@ contextBridge.exposeInMainWorld("electron", { ipcRenderer.invoke("searchGames", payload, take, skip), getCatalogue: (category: CatalogueCategory) => ipcRenderer.invoke("getCatalogue", category), - saveGameShopAssets: (objectId: string, shop: GameShop, assets: ShopAssets) => - ipcRenderer.invoke("saveGameShopAssets", objectId, shop, assets), getGameShopDetails: (objectId: string, shop: GameShop, language: string) => ipcRenderer.invoke("getGameShopDetails", objectId, shop, language), getRandomGame: () => ipcRenderer.invoke("getRandomGame"), @@ -76,6 +73,8 @@ contextBridge.exposeInMainWorld("electron", { ipcRenderer.invoke("getHowLongToBeat", objectId, shop), getGameStats: (objectId: string, shop: GameShop) => ipcRenderer.invoke("getGameStats", objectId, shop), + getGameAssets: (objectId: string, shop: GameShop) => + ipcRenderer.invoke("getGameAssets", objectId, shop), getTrendingGames: () => ipcRenderer.invoke("getTrendingGames"), createGameReview: ( shop: GameShop, diff --git a/src/renderer/src/context/game-details/game-details.context.tsx b/src/renderer/src/context/game-details/game-details.context.tsx index 5be5cf98..778fa3fe 100644 --- a/src/renderer/src/context/game-details/game-details.context.tsx +++ b/src/renderer/src/context/game-details/game-details.context.tsx @@ -142,29 +142,23 @@ export function GameDetailsContextProvider({ } }); - const statsPromise = window.electron - .getGameStats(objectId, shop) - .then((result) => { - if (abortController.signal.aborted) return null; - setStats(result); - return result; - }); + window.electron.getGameStats(objectId, shop).then((result) => { + if (abortController.signal.aborted) return; + setStats(result); + }); - Promise.all([shopDetailsPromise, statsPromise]) - .then(([_, stats]) => { - if (stats) { - const assets = stats.assets; - if (assets) { - window.electron.saveGameShopAssets(objectId, shop, assets); + const assetsPromise = window.electron.getGameAssets(objectId, shop); - setShopDetails((prev) => { - if (!prev) return null; - return { - ...prev, - assets, - }; - }); - } + Promise.all([shopDetailsPromise, assetsPromise]) + .then(([_, assets]) => { + if (assets) { + setShopDetails((prev) => { + if (!prev) return null; + return { + ...prev, + assets, + }; + }); } }) .finally(() => { @@ -207,8 +201,8 @@ export function GameDetailsContextProvider({ setShowRepacksModal(true); try { window.history.replaceState({}, document.title, location.pathname); - } catch (_e) { - void _e; + } catch (e) { + console.error(e); } } }, [location]); diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 82bbeb28..9f9e4177 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -39,6 +39,7 @@ import type { AchievementCustomNotificationPosition, AchievementNotificationInfo, UserLibraryResponse, + Game, } from "@types"; import type { AxiosProgressEvent } from "axios"; import type disk from "diskusage"; @@ -77,11 +78,6 @@ declare global { skip: number ) => Promise<{ edges: CatalogueSearchResult[]; count: number }>; getCatalogue: (category: CatalogueCategory) => Promise; - saveGameShopAssets: ( - objectId: string, - shop: GameShop, - assets: ShopAssets - ) => Promise; getGameShopDetails: ( objectId: string, shop: GameShop, @@ -93,6 +89,10 @@ declare global { shop: GameShop ) => Promise; getGameStats: (objectId: string, shop: GameShop) => Promise; + getGameAssets: ( + objectId: string, + shop: GameShop + ) => Promise; getTrendingGames: () => Promise; createGameReview: ( shop: GameShop, diff --git a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx index 33009508..df1429ec 100755 --- a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx +++ b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx @@ -233,9 +233,18 @@ export function Sidebar() { {t("rating_count")}

diff --git a/src/renderer/src/pages/profile/profile-content/user-library-game-card.tsx b/src/renderer/src/pages/profile/profile-content/user-library-game-card.tsx index a3d24958..72b48a8c 100644 --- a/src/renderer/src/pages/profile/profile-content/user-library-game-card.tsx +++ b/src/renderer/src/pages/profile/profile-content/user-library-game-card.tsx @@ -234,7 +234,7 @@ export function UserLibraryGameCard({
{game.title} diff --git a/src/types/index.ts b/src/types/index.ts index 0c6af89b..6a864f3a 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -45,7 +45,7 @@ export interface ShopAssets { libraryImageUrl: string; logoImageUrl: string; logoPosition: string | null; - coverImageUrl: string; + coverImageUrl: string | null; } export type ShopDetails = SteamAppDetails & { @@ -235,8 +235,8 @@ export interface DownloadSourceValidationResult { export interface GameStats { downloadCount: number; playerCount: number; - assets: ShopAssets | null; averageScore: number | null; + reviewCount: number; } export interface GameReview { From b21c97ea66d6e68bb6b87cdc2ba0ae32f0364972 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sat, 11 Oct 2025 11:59:54 -0300 Subject: [PATCH 033/197] feat: remove import --- src/main/events/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/events/index.ts b/src/main/events/index.ts index 1d537db3..ecea6463 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -3,7 +3,6 @@ import { ipcMain } from "electron"; import "./catalogue/get-catalogue"; import "./catalogue/get-game-shop-details"; -import "./catalogue/save-game-shop-assets"; import "./catalogue/get-how-long-to-beat"; import "./catalogue/get-random-game"; import "./catalogue/search-games"; From df6d9df31d453c08e8b83d96c74a9d5d7fe8a26d Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sun, 12 Oct 2025 12:18:43 -0300 Subject: [PATCH 034/197] feat: update cache time --- src/main/events/catalogue/get-game-assets.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/events/catalogue/get-game-assets.ts b/src/main/events/catalogue/get-game-assets.ts index 04a03808..de1d2b1f 100644 --- a/src/main/events/catalogue/get-game-assets.ts +++ b/src/main/events/catalogue/get-game-assets.ts @@ -3,7 +3,7 @@ import { registerEvent } from "../register-event"; import { HydraApi } from "@main/services"; import { gamesShopAssetsSublevel, levelKeys } from "@main/level"; -const LOCAL_CACHE_EXPIRATION = 1000 * 60 * 30; // 30 minutes +const LOCAL_CACHE_EXPIRATION = 1000 * 60 * 60 * 8; // 8 hours export const getGameAssets = async (objectId: string, shop: GameShop) => { const cachedAssets = await gamesShopAssetsSublevel.get( From 741f9de85c5d320054803ee59fef43b35f7b1b55 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Sun, 12 Oct 2025 19:51:26 +0300 Subject: [PATCH 035/197] Fix: formatting --- .../src/pages/game-details/sidebar/sidebar.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx index 33009508..df1429ec 100755 --- a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx +++ b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx @@ -233,9 +233,18 @@ export function Sidebar() { {t("rating_count")}

From 2240a8c9fb5be0261c0dd64b2fcbe862311b1fc8 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Sun, 12 Oct 2025 19:54:45 +0300 Subject: [PATCH 036/197] Fix: TipTap formatting not displaying on the review message --- src/shared/html-sanitizer.ts | 48 ++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/src/shared/html-sanitizer.ts b/src/shared/html-sanitizer.ts index 4f8042e3..9cd50fc6 100644 --- a/src/shared/html-sanitizer.ts +++ b/src/shared/html-sanitizer.ts @@ -43,19 +43,51 @@ export function sanitizeHtml(html: string): string { return ""; } - let cleanText = removeHtmlTags(html); + // Use DOM-based sanitization to preserve safe formatting while removing dangerous content. + const tempDiv = document.createElement("div"); + tempDiv.innerHTML = html; - cleanText = decodeHtmlEntities(cleanText); + // Remove clearly unsafe elements entirely. + const disallowedSelectors = [ + "script", + "style", + "iframe", + "object", + "embed", + "link", + "meta", + ]; + disallowedSelectors.forEach((sel) => { + tempDiv.querySelectorAll(sel).forEach((el) => el.remove()); + }); - cleanText = removeZalgoText(cleanText); + // Strip potentially dangerous attributes from remaining elements. + tempDiv.querySelectorAll("*").forEach((el) => { + Array.from(el.attributes).forEach((attr) => { + const name = attr.name.toLowerCase(); + if ( + name.startsWith("on") || // Event handlers + name === "style" || + name === "src" || + name === "href" // Links disabled in editor; avoid javascript: URLs + ) { + el.removeAttribute(attr.name); + } + }); + }); - cleanText = cleanText.replaceAll(/\s+/g, " ").trim(); - - if (!cleanText || cleanText.length === 0) { - return ""; + // Clean Zalgo text characters within text nodes. + const walker = document.createTreeWalker(tempDiv, NodeFilter.SHOW_TEXT); + let node: Node | null; + // eslint-disable-next-line no-cond-assign + while ((node = walker.nextNode())) { + const textNode = node as Text; + const value = textNode.nodeValue || ""; + textNode.nodeValue = removeZalgoText(value); } - return cleanText; + const cleanHtml = tempDiv.innerHTML.trim(); + return cleanHtml; } export function stripHtml(html: string): string { From 602b2fef91e108275de43a8d279643cc1b9f8a17 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Sun, 12 Oct 2025 19:57:12 +0300 Subject: [PATCH 037/197] Fix: TipTap formatting not displaying on the review message --- src/shared/html-sanitizer.ts | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/src/shared/html-sanitizer.ts b/src/shared/html-sanitizer.ts index 9cd50fc6..ea3d475b 100644 --- a/src/shared/html-sanitizer.ts +++ b/src/shared/html-sanitizer.ts @@ -6,37 +6,7 @@ function removeZalgoText(text: string): string { return text.replaceAll(zalgoRegex, ""); } -function decodeHtmlEntities(text: string): string { - const entityMap: { [key: string]: string } = { - "&": "&", - "<": "<", - ">": ">", - """: '"', - "'": "'", - " ": " ", - }; - return text.replaceAll(/&[#\w]+;/g, (entity) => { - return entityMap[entity] || entity; - }); -} - -function removeHtmlTags(html: string): string { - let result = ""; - let inTag = false; - - for (const char of html) { - if (char === "<") { - inTag = true; - } else if (char === ">") { - inTag = false; - } else if (!inTag) { - result += char; - } - } - - return result; -} export function sanitizeHtml(html: string): string { if (!html || typeof html !== "string") { From 14204f1fbebbec7a0cd5b76de318b410243e04c4 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sun, 12 Oct 2025 18:39:41 +0100 Subject: [PATCH 038/197] feat: adding review styling --- src/locales/en/translation.json | 17 +- src/locales/pt-BR/translation.json | 117 ++++++- src/main/constants.ts | 11 + src/main/events/index.ts | 3 + .../misc/check-homebrew-folder-exists.ts | 13 + .../misc/get-hydra-decky-plugin-info.ts | 63 ++++ .../events/misc/install-hydra-decky-plugin.ts | 50 +++ src/main/main.ts | 5 + src/main/services/decky-plugin.ts | 313 ++++++++++++++++++ src/main/services/index.ts | 1 + src/preload/index.ts | 4 + src/renderer/src/assets/icons/decky.png | Bin 0 -> 215327 bytes .../confirm-modal/confirm-modal.scss | 11 - .../confirm-modal/confirm-modal.tsx | 57 ---- .../confirmation-modal.scss | 2 +- .../confirmation-modal/confirmation-modal.tsx | 2 +- .../game-context-menu/game-context-menu.tsx | 32 +- .../src/components/sidebar/sidebar.scss | 22 ++ .../src/components/sidebar/sidebar.tsx | 131 +++++++- src/renderer/src/declaration.d.ts | 13 + src/renderer/src/helpers.ts | 8 + .../description-header.scss | 18 +- .../game-details/game-details-content.tsx | 132 ++++++-- .../src/pages/game-details/game-details.scss | 84 +++-- .../src/pages/settings/settings-debrid.scss | 71 ++++ .../src/pages/settings/settings-debrid.tsx | 228 +++++++++++++ src/renderer/src/pages/settings/settings.tsx | 33 +- 27 files changed, 1226 insertions(+), 215 deletions(-) create mode 100644 src/main/events/misc/check-homebrew-folder-exists.ts create mode 100644 src/main/events/misc/get-hydra-decky-plugin-info.ts create mode 100644 src/main/events/misc/install-hydra-decky-plugin.ts create mode 100644 src/main/services/decky-plugin.ts create mode 100644 src/renderer/src/assets/icons/decky.png delete mode 100644 src/renderer/src/components/confirm-modal/confirm-modal.scss delete mode 100644 src/renderer/src/components/confirm-modal/confirm-modal.tsx create mode 100644 src/renderer/src/pages/settings/settings-debrid.scss create mode 100644 src/renderer/src/pages/settings/settings-debrid.tsx diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index c3b3e452..3dc93d90 100755 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -76,7 +76,18 @@ "edit_game_modal_drop_hero_image_here": "Drop hero image here", "edit_game_modal_drop_to_replace_icon": "Drop to replace icon", "edit_game_modal_drop_to_replace_logo": "Drop to replace logo", - "edit_game_modal_drop_to_replace_hero": "Drop to replace hero" + "edit_game_modal_drop_to_replace_hero": "Drop to replace hero", + "install_decky_plugin": "Install Decky Plugin", + "decky_plugin_installed_version": "Decky Plugin (v{{version}})", + "install_decky_plugin_title": "Install Hydra Decky Plugin", + "install_decky_plugin_message": "This will download and install the Hydra plugin for Decky Loader. This may require elevated permissions. Continue?", + "update_decky_plugin_title": "Update Hydra Decky Plugin", + "update_decky_plugin_message": "A new version of the Hydra Decky plugin is available. Would you like to update it now?", + "decky_plugin_installed": "Decky plugin v{{version}} installed successfully", + "decky_plugin_installation_failed": "Failed to install Decky plugin: {{error}}", + "decky_plugin_installation_error": "Error installing Decky plugin: {{error}}", + "confirm": "Confirm", + "cancel": "Cancel" }, "header": { "search": "Search games", @@ -227,7 +238,7 @@ "rating_neutral": "Neutral", "rating_positive": "Positive", "rating_very_positive": "Very Positive", - "submit_review": "Submit Review", + "submit_review": "Submit", "submitting": "Submitting...", "review_submitted_successfully": "Review submitted successfully!", "review_submission_failed": "Failed to submit review. Please try again.", @@ -486,6 +497,8 @@ "delete_theme_description": "This will delete the theme {{theme}}", "cancel": "Cancel", "appearance": "Appearance", + "debrid": "Debrid", + "debrid_description": "Debrid services are premium unrestricted downloaders that allow you to quickly download files hosted on various file hosting services, only limited by your internet speed.", "enable_torbox": "Enable TorBox", "torbox_description": "TorBox is your premium seedbox service rivaling even the best servers on the market.", "torbox_account_linked": "TorBox account linked", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 37569701..e9a84c89 100755 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -27,21 +27,67 @@ "friends": "Amigos", "need_help": "Precisa de ajuda?", "favorites": "Favoritos", + "playable_button_title": "Mostrar apenas jogos que você pode jogar agora", "add_custom_game_tooltip": "Adicionar jogo personalizado", + "show_playable_only_tooltip": "Mostrar Apenas Jogáveis", "custom_game_modal": "Adicionar jogo personalizado", + "custom_game_modal_description": "Adicione um jogo personalizado à sua biblioteca selecionando um arquivo executável", + "custom_game_modal_executable_path": "Caminho do Executável", + "custom_game_modal_select_executable": "Selecionar arquivo executável", + "custom_game_modal_title": "Título", + "custom_game_modal_enter_title": "Insira o título", "edit_game_modal_title": "Título", - "playable_button_title": "", - "custom_game_modal_add": "Adicionar Jogo", - "custom_game_modal_adding": "Adicionando...", "custom_game_modal_browse": "Buscar", "custom_game_modal_cancel": "Cancelar", - "edit_game_modal_assets": "Imagens", - "edit_game_modal_icon": "Ícone", - "edit_game_modal_browse": "Buscar", - "edit_game_modal_cancel": "Cancelar", + "custom_game_modal_add": "Adicionar Jogo", + "custom_game_modal_adding": "Adicionando...", + "custom_game_modal_success": "Jogo personalizado adicionado com sucesso", + "custom_game_modal_failed": "Falha ao adicionar jogo personalizado", + "custom_game_modal_executable": "Executável", + "edit_game_modal": "Personalizar detalhes", + "edit_game_modal_description": "Personalize os recursos e detalhes do jogo", "edit_game_modal_enter_title": "Insira o título", + "edit_game_modal_image": "Imagem", + "edit_game_modal_select_image": "Selecionar imagem", + "edit_game_modal_browse": "Buscar", + "edit_game_modal_image_preview": "Visualização da imagem", + "edit_game_modal_icon": "Ícone", + "edit_game_modal_select_icon": "Selecionar ícone", + "edit_game_modal_icon_preview": "Visualização do ícone", "edit_game_modal_logo": "Logo", - "edit_game_modal": "Personalizar detalhes" + "edit_game_modal_select_logo": "Selecionar logo", + "edit_game_modal_logo_preview": "Visualização do logo", + "edit_game_modal_hero": "Hero da Biblioteca", + "edit_game_modal_select_hero": "Selecionar imagem hero da biblioteca", + "edit_game_modal_hero_preview": "Visualização da imagem hero da biblioteca", + "edit_game_modal_cancel": "Cancelar", + "edit_game_modal_update": "Atualizar", + "edit_game_modal_updating": "Atualizando...", + "edit_game_modal_fill_required": "Por favor, preencha todos os campos obrigatórios", + "edit_game_modal_success": "Recursos atualizados com sucesso", + "edit_game_modal_failed": "Falha ao atualizar recursos", + "edit_game_modal_image_filter": "Imagem", + "edit_game_modal_icon_resolution": "Resolução recomendada: 256x256px", + "edit_game_modal_logo_resolution": "Resolução recomendada: 640x360px", + "edit_game_modal_hero_resolution": "Resolução recomendada: 1920x620px", + "edit_game_modal_assets": "Imagens", + "edit_game_modal_drop_icon_image_here": "Solte a imagem do ícone aqui", + "edit_game_modal_drop_logo_image_here": "Solte a imagem do logo aqui", + "edit_game_modal_drop_hero_image_here": "Solte a imagem hero aqui", + "edit_game_modal_drop_to_replace_icon": "Solte para substituir o ícone", + "edit_game_modal_drop_to_replace_logo": "Solte para substituir o logo", + "edit_game_modal_drop_to_replace_hero": "Solte para substituir o hero", + "install_decky_plugin": "Instalar Plugin Decky", + "decky_plugin_installed_version": "Plugin Decky (v{{version}})", + "install_decky_plugin_title": "Instalar Plugin Hydra Decky", + "install_decky_plugin_message": "Isso irá baixar e instalar o plugin Hydra para Decky Loader. Pode ser necessário permissões elevadas. Continuar?", + "update_decky_plugin_title": "Atualizar Plugin Hydra Decky", + "update_decky_plugin_message": "Uma nova versão do plugin Hydra Decky está disponível. Gostaria de atualizar agora?", + "decky_plugin_installed": "Plugin Decky v{{version}} instalado com sucesso", + "decky_plugin_installation_failed": "Falha ao instalar plugin Decky: {{error}}", + "decky_plugin_installation_error": "Erro ao instalar plugin Decky: {{error}}", + "confirm": "Confirmar", + "cancel": "Cancelar" }, "header": { "search": "Buscar jogos", @@ -256,7 +302,48 @@ "update_playtime": "Modificar tempo de jogo", "update_playtime_description": "Atualizar manualmente o tempo de jogo de {{game}}", "update_playtime_error": "Falha ao atualizar tempo de jogo", - "update_playtime_title": "Atualizar tempo de jogo" + "update_playtime_title": "Atualizar tempo de jogo", + "update_playtime_success": "Tempo de jogo atualizado com sucesso", + "show_more": "Mostrar mais", + "show_less": "Mostrar menos", + "reviews": "Avaliações", + "leave_a_review": "Deixar uma Avaliação", + "write_review_placeholder": "Compartilhe seus pensamentos sobre este jogo...", + "sort_newest": "Mais Recentes", + "sort_oldest": "Mais Antigas", + "sort_highest_score": "Maior Nota", + "sort_lowest_score": "Menor Nota", + "sort_most_voted": "Mais Votadas", + "no_reviews_yet": "Ainda não há avaliações", + "be_first_to_review": "Seja o primeiro a compartilhar seus pensamentos sobre este jogo!", + "rating": "Avaliação", + "rating_stats": "Avaliação", + "rating_very_negative": "Muito Negativo", + "rating_negative": "Negativo", + "rating_neutral": "Neutro", + "rating_positive": "Positivo", + "rating_very_positive": "Muito Positivo", + "submit_review": "Enviar", + "submitting": "Enviando...", + "review_submitted_successfully": "Avaliação enviada com sucesso!", + "review_submission_failed": "Falha ao enviar avaliação. Por favor, tente novamente.", + "review_cannot_be_empty": "O campo de texto da avaliação não pode estar vazio.", + "review_deleted_successfully": "Avaliação excluída com sucesso.", + "review_deletion_failed": "Falha ao excluir avaliação. Por favor, tente novamente.", + "loading_reviews": "Carregando avaliações...", + "loading_more_reviews": "Carregando mais avaliações...", + "load_more_reviews": "Carregar Mais Avaliações", + "you_seemed_to_enjoy_this_game": "Parece que você gostou deste jogo", + "would_you_recommend_this_game": "Gostaria de deixar uma avaliação para este jogo?", + "yes": "Sim", + "maybe_later": "Talvez Mais Tarde", + "delete_review": "Excluir avaliação", + "remove_review": "Remover Avaliação", + "delete_review_modal_title": "Tem certeza de que deseja excluir sua avaliação?", + "delete_review_modal_description": "Esta ação não pode ser desfeita.", + "delete_review_modal_delete_button": "Excluir", + "delete_review_modal_cancel_button": "Cancelar", + "rating_count": "Avaliação" }, "activation": { "title": "Ativação", @@ -395,6 +482,8 @@ "delete_theme_description": "Isso irá deletar o tema {{theme}}", "cancel": "Cancelar", "appearance": "Aparência", + "debrid": "Debrid", + "debrid_description": "Serviços Debrid são downloaders premium sem restrições que permitem baixar rapidamente arquivos hospedados em vários serviços de hospedagem de arquivos, limitados apenas pela sua velocidade de internet.", "enable_torbox": "Habilitar TorBox", "torbox_description": "TorBox é o seu serviço de seedbox premium que rivaliza até com os melhores servidores do mercado.", "torbox_account_linked": "Conta do TorBox vinculada", @@ -457,7 +546,8 @@ "game_card": { "available_one": "Disponível", "available_other": "Disponíveis", - "no_downloads": "Sem downloads disponíveis" + "no_downloads": "Sem downloads disponíveis", + "calculating": "Calculando" }, "binary_not_found_modal": { "title": "Programas não instalados", @@ -569,7 +659,12 @@ "amount_minutes_short": "{{amount}}m", "amount_hours_short": "{{amount}}h", "game_added_to_pinned": "Jogo adicionado aos fixados", - "achievements_earned": "Conquistas recebidas" + "game_removed_from_pinned": "Jogo removido dos fixados", + "achievements_earned": "Conquistas recebidas", + "karma": "Karma", + "karma_count": "karma", + "karma_description": "Ganho a partir de curtidas positivas em avaliações", + "manual_playtime_tooltip": "Este tempo de jogo foi atualizado manualmente" }, "achievement": { "achievement_unlocked": "Conquista desbloqueada", diff --git a/src/main/constants.ts b/src/main/constants.ts index b067be80..82b99b2a 100644 --- a/src/main/constants.ts +++ b/src/main/constants.ts @@ -42,3 +42,14 @@ export const appVersion = app.getVersion() + (isStaging ? "-staging" : ""); export const ASSETS_PATH = path.join(SystemPath.getPath("userData"), "Assets"); export const MAIN_LOOP_INTERVAL = 2000; + +export const DECKY_PLUGINS_LOCATION = path.join( + SystemPath.getPath("home"), + "homebrew", + "plugins" +); + +export const HYDRA_DECKY_PLUGIN_LOCATION = path.join( + DECKY_PLUGINS_LOCATION, + "Hydra" +); diff --git a/src/main/events/index.ts b/src/main/events/index.ts index 1d537db3..6146da22 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -58,6 +58,9 @@ import "./misc/install-common-redist"; import "./misc/can-install-common-redist"; import "./misc/save-temp-file"; import "./misc/delete-temp-file"; +import "./misc/install-hydra-decky-plugin"; +import "./misc/get-hydra-decky-plugin-info"; +import "./misc/check-homebrew-folder-exists"; import "./torrenting/cancel-game-download"; import "./torrenting/pause-game-download"; import "./torrenting/resume-game-download"; diff --git a/src/main/events/misc/check-homebrew-folder-exists.ts b/src/main/events/misc/check-homebrew-folder-exists.ts new file mode 100644 index 00000000..32e09754 --- /dev/null +++ b/src/main/events/misc/check-homebrew-folder-exists.ts @@ -0,0 +1,13 @@ +import { registerEvent } from "../register-event"; +import { DECKY_PLUGINS_LOCATION } from "@main/constants"; +import fs from "node:fs"; +import path from "node:path"; + +const checkHomebrewFolderExists = async ( + _event: Electron.IpcMainInvokeEvent +): Promise => { + const homebrewPath = path.dirname(DECKY_PLUGINS_LOCATION); + return fs.existsSync(homebrewPath); +}; + +registerEvent("checkHomebrewFolderExists", checkHomebrewFolderExists); diff --git a/src/main/events/misc/get-hydra-decky-plugin-info.ts b/src/main/events/misc/get-hydra-decky-plugin-info.ts new file mode 100644 index 00000000..da72033e --- /dev/null +++ b/src/main/events/misc/get-hydra-decky-plugin-info.ts @@ -0,0 +1,63 @@ +import { registerEvent } from "../register-event"; +import { logger } from "@main/services"; +import { HYDRA_DECKY_PLUGIN_LOCATION } from "@main/constants"; +import fs from "node:fs"; +import path from "node:path"; + +const getHydraDeckyPluginInfo = async ( + _event: Electron.IpcMainInvokeEvent +): Promise<{ + installed: boolean; + version: string | null; + path: string; +}> => { + try { + // Check if plugin folder exists + if (!fs.existsSync(HYDRA_DECKY_PLUGIN_LOCATION)) { + logger.log("Hydra Decky plugin not installed"); + return { + installed: false, + version: null, + path: HYDRA_DECKY_PLUGIN_LOCATION, + }; + } + + // Check if package.json exists + const packageJsonPath = path.join( + HYDRA_DECKY_PLUGIN_LOCATION, + "package.json" + ); + + if (!fs.existsSync(packageJsonPath)) { + logger.log("Hydra Decky plugin package.json not found"); + return { + installed: false, + version: null, + path: HYDRA_DECKY_PLUGIN_LOCATION, + }; + } + + // Read and parse package.json + const packageJsonContent = fs.readFileSync(packageJsonPath, "utf-8"); + const packageJson = JSON.parse(packageJsonContent); + const version = packageJson.version; + + logger.log(`Hydra Decky plugin installed, version: ${version}`); + + return { + installed: true, + version, + path: HYDRA_DECKY_PLUGIN_LOCATION, + }; + } catch (error) { + logger.error("Failed to get plugin info:", error); + return { + installed: false, + version: null, + path: HYDRA_DECKY_PLUGIN_LOCATION, + }; + } +}; + +registerEvent("getHydraDeckyPluginInfo", getHydraDeckyPluginInfo); + diff --git a/src/main/events/misc/install-hydra-decky-plugin.ts b/src/main/events/misc/install-hydra-decky-plugin.ts new file mode 100644 index 00000000..3ddbbd64 --- /dev/null +++ b/src/main/events/misc/install-hydra-decky-plugin.ts @@ -0,0 +1,50 @@ +import { registerEvent } from "../register-event"; +import { logger, DeckyPlugin } from "@main/services"; +import { HYDRA_DECKY_PLUGIN_LOCATION } from "@main/constants"; + +const installHydraDeckyPlugin = async ( + _event: Electron.IpcMainInvokeEvent +): Promise<{ + success: boolean; + path: string; + currentVersion: string | null; + expectedVersion: string; + error?: string; +}> => { + try { + logger.log("Installing/updating Hydra Decky plugin..."); + + const result = await DeckyPlugin.checkPluginVersion(); + + if (result.exists && !result.outdated) { + logger.log("Plugin installed successfully"); + return { + success: true, + path: HYDRA_DECKY_PLUGIN_LOCATION, + currentVersion: result.currentVersion, + expectedVersion: result.expectedVersion, + }; + } else { + logger.error("Failed to install plugin"); + return { + success: false, + path: HYDRA_DECKY_PLUGIN_LOCATION, + currentVersion: result.currentVersion, + expectedVersion: result.expectedVersion, + error: "Plugin installation failed", + }; + } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + logger.error("Failed to install plugin:", error); + return { + success: false, + path: HYDRA_DECKY_PLUGIN_LOCATION, + currentVersion: null, + expectedVersion: "0.0.3", + error: errorMessage, + }; + } +}; + +registerEvent("installHydraDeckyPlugin", installHydraDeckyPlugin); diff --git a/src/main/main.ts b/src/main/main.ts index 67391057..9b8ecc2b 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -16,6 +16,7 @@ import { startMainLoop, Ludusavi, Lock, + DeckyPlugin, } from "@main/services"; export const loadState = async () => { @@ -49,6 +50,10 @@ export const loadState = async () => { Ludusavi.copyConfigFileToUserData(); Ludusavi.copyBinaryToUserData(); + if (process.platform === "linux") { + DeckyPlugin.checkAndUpdateIfOutdated(); + } + await HydraApi.setupApi().then(() => { uploadGamesBatch(); // WSClient.connect(); diff --git a/src/main/services/decky-plugin.ts b/src/main/services/decky-plugin.ts new file mode 100644 index 00000000..7e178189 --- /dev/null +++ b/src/main/services/decky-plugin.ts @@ -0,0 +1,313 @@ +import fs from "node:fs"; +import path from "node:path"; +import os from "node:os"; +import axios from "axios"; +import sudo from "sudo-prompt"; +import { app } from "electron"; +import { + HYDRA_DECKY_PLUGIN_LOCATION, + DECKY_PLUGINS_LOCATION, +} from "@main/constants"; +import { logger } from "./logger"; +import { SevenZip } from "./7zip"; +import { SystemPath } from "./system-path"; + +export class DeckyPlugin { + private static readonly EXPECTED_VERSION = "0.0.3"; + private static readonly DOWNLOAD_URL = + "https://github.com/hydralauncher/decky-hydra-launcher/releases/download/0.0.3/Hydra.zip"; + + private static getPackageJsonPath(): string { + return path.join(HYDRA_DECKY_PLUGIN_LOCATION, "package.json"); + } + + private static async downloadPlugin(): Promise { + logger.log("Downloading Hydra Decky plugin..."); + + const tempDir = SystemPath.getPath("temp"); + const zipPath = path.join(tempDir, "Hydra.zip"); + + const response = await axios.get(this.DOWNLOAD_URL, { + responseType: "arraybuffer", + }); + + await fs.promises.writeFile(zipPath, response.data); + logger.log(`Plugin downloaded to: ${zipPath}`); + + return zipPath; + } + + private static async extractPlugin(zipPath: string): Promise { + logger.log("Extracting Hydra Decky plugin..."); + + const tempDir = SystemPath.getPath("temp"); + const extractPath = path.join(tempDir, "hydra-decky-plugin"); + + if (fs.existsSync(extractPath)) { + await fs.promises.rm(extractPath, { recursive: true, force: true }); + } + + await fs.promises.mkdir(extractPath, { recursive: true }); + + return new Promise((resolve, reject) => { + SevenZip.extractFile( + { + filePath: zipPath, + outputPath: extractPath, + }, + () => { + logger.log(`Plugin extracted to: ${extractPath}`); + resolve(extractPath); + }, + () => { + reject(new Error("Failed to extract plugin")); + } + ); + }); + } + + private static needsSudo(): boolean { + try { + if (fs.existsSync(DECKY_PLUGINS_LOCATION)) { + fs.accessSync(DECKY_PLUGINS_LOCATION, fs.constants.W_OK); + return false; + } + + const parentDir = path.dirname(DECKY_PLUGINS_LOCATION); + if (fs.existsSync(parentDir)) { + fs.accessSync(parentDir, fs.constants.W_OK); + return false; + } + + return true; + } catch (error) { + if ( + error && + typeof error === "object" && + "code" in error && + (error.code === "EACCES" || error.code === "EPERM") + ) { + return true; + } + throw error; + } + } + + private static async installPluginWithSudo( + extractPath: string + ): Promise { + logger.log("Installing plugin with sudo..."); + + const username = os.userInfo().username; + const sourcePath = path.join(extractPath, "Hydra"); + + return new Promise((resolve, reject) => { + const command = `mkdir -p "${DECKY_PLUGINS_LOCATION}" && rm -rf "${HYDRA_DECKY_PLUGIN_LOCATION}" && cp -r "${sourcePath}" "${HYDRA_DECKY_PLUGIN_LOCATION}" && chown -R ${username}: "${DECKY_PLUGINS_LOCATION}"`; + + sudo.exec( + command, + { name: app.getName() }, + (sudoError, _stdout, stderr) => { + if (sudoError) { + logger.error("Failed to install plugin with sudo:", sudoError); + reject(sudoError); + } else { + logger.log("Plugin installed successfully with sudo"); + if (stderr) { + logger.log("Sudo stderr:", stderr); + } + resolve(); + } + } + ); + }); + } + + private static async installPluginWithoutSudo( + extractPath: string + ): Promise { + logger.log("Installing plugin without sudo..."); + + const sourcePath = path.join(extractPath, "Hydra"); + + if (!fs.existsSync(DECKY_PLUGINS_LOCATION)) { + await fs.promises.mkdir(DECKY_PLUGINS_LOCATION, { recursive: true }); + } + + if (fs.existsSync(HYDRA_DECKY_PLUGIN_LOCATION)) { + await fs.promises.rm(HYDRA_DECKY_PLUGIN_LOCATION, { + recursive: true, + force: true, + }); + } + + await fs.promises.cp(sourcePath, HYDRA_DECKY_PLUGIN_LOCATION, { + recursive: true, + }); + + logger.log("Plugin installed successfully"); + } + + private static async installPlugin(extractPath: string): Promise { + if (this.needsSudo()) { + await this.installPluginWithSudo(extractPath); + } else { + await this.installPluginWithoutSudo(extractPath); + } + } + + private static async updatePlugin(): Promise { + let zipPath: string | null = null; + let extractPath: string | null = null; + + try { + zipPath = await this.downloadPlugin(); + extractPath = await this.extractPlugin(zipPath); + await this.installPlugin(extractPath); + + logger.log("Plugin update completed successfully"); + } catch (error) { + logger.error("Failed to update plugin:", error); + throw error; + } finally { + if (zipPath) { + try { + await fs.promises.rm(zipPath, { force: true }); + logger.log("Cleaned up downloaded zip file"); + } catch (cleanupError) { + logger.error("Failed to clean up zip file:", cleanupError); + } + } + + if (extractPath) { + try { + await fs.promises.rm(extractPath, { recursive: true, force: true }); + logger.log("Cleaned up extraction directory"); + } catch (cleanupError) { + logger.error( + "Failed to clean up extraction directory:", + cleanupError + ); + } + } + } + } + + public static async checkAndUpdateIfOutdated(): Promise { + if (!fs.existsSync(HYDRA_DECKY_PLUGIN_LOCATION)) { + logger.log("Hydra Decky plugin not installed, skipping update check"); + return; + } + + const packageJsonPath = this.getPackageJsonPath(); + + try { + if (!fs.existsSync(packageJsonPath)) { + logger.log( + "Hydra Decky plugin package.json not found, skipping update" + ); + return; + } + + const packageJsonContent = fs.readFileSync(packageJsonPath, "utf-8"); + const packageJson = JSON.parse(packageJsonContent); + const currentVersion = packageJson.version; + const isOutdated = currentVersion !== this.EXPECTED_VERSION; + + if (isOutdated) { + logger.log( + `Hydra Decky plugin is outdated. Current: ${currentVersion}, Expected: ${this.EXPECTED_VERSION}. Updating...` + ); + + await this.updatePlugin(); + logger.log("Hydra Decky plugin updated successfully"); + } else { + logger.log(`Hydra Decky plugin is up to date (${currentVersion})`); + } + } catch (error) { + logger.error(`Error checking/updating Hydra Decky plugin: ${error}`); + } + } + + public static async checkPluginVersion(): Promise<{ + exists: boolean; + outdated: boolean; + currentVersion: string | null; + expectedVersion: string; + }> { + if (!fs.existsSync(HYDRA_DECKY_PLUGIN_LOCATION)) { + logger.log("Hydra Decky plugin folder not found, installing..."); + + try { + await this.updatePlugin(); + return { + exists: true, + outdated: false, + currentVersion: this.EXPECTED_VERSION, + expectedVersion: this.EXPECTED_VERSION, + }; + } catch (error) { + logger.error("Failed to install plugin:", error); + return { + exists: false, + outdated: true, + currentVersion: null, + expectedVersion: this.EXPECTED_VERSION, + }; + } + } + + const packageJsonPath = this.getPackageJsonPath(); + + try { + if (!fs.existsSync(packageJsonPath)) { + logger.log("Hydra Decky plugin package.json not found, installing..."); + + await this.updatePlugin(); + return { + exists: true, + outdated: false, + currentVersion: this.EXPECTED_VERSION, + expectedVersion: this.EXPECTED_VERSION, + }; + } + + const packageJsonContent = fs.readFileSync(packageJsonPath, "utf-8"); + const packageJson = JSON.parse(packageJsonContent); + const currentVersion = packageJson.version; + const isOutdated = currentVersion !== this.EXPECTED_VERSION; + + if (isOutdated) { + logger.log( + `Hydra Decky plugin is outdated. Current: ${currentVersion}, Expected: ${this.EXPECTED_VERSION}` + ); + + await this.updatePlugin(); + + return { + exists: true, + outdated: false, + currentVersion: this.EXPECTED_VERSION, + expectedVersion: this.EXPECTED_VERSION, + }; + } else { + logger.log(`Hydra Decky plugin is up to date (${currentVersion})`); + } + + return { + exists: true, + outdated: isOutdated, + currentVersion, + expectedVersion: this.EXPECTED_VERSION, + }; + } catch (error) { + logger.error(`Error checking Hydra Decky plugin version: ${error}`); + return { + exists: false, + outdated: true, + currentVersion: null, + expectedVersion: this.EXPECTED_VERSION, + }; + } + } +} diff --git a/src/main/services/index.ts b/src/main/services/index.ts index 727805c7..88b39d1b 100644 --- a/src/main/services/index.ts +++ b/src/main/services/index.ts @@ -17,3 +17,4 @@ export * from "./system-path"; export * from "./library-sync"; export * from "./wine"; export * from "./lock"; +export * from "./decky-plugin"; diff --git a/src/preload/index.ts b/src/preload/index.ts index 813758f0..700561ac 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -386,6 +386,10 @@ contextBridge.exposeInMainWorld("electron", { getBadges: () => ipcRenderer.invoke("getBadges"), canInstallCommonRedist: () => ipcRenderer.invoke("canInstallCommonRedist"), installCommonRedist: () => ipcRenderer.invoke("installCommonRedist"), + installHydraDeckyPlugin: () => ipcRenderer.invoke("installHydraDeckyPlugin"), + getHydraDeckyPluginInfo: () => ipcRenderer.invoke("getHydraDeckyPluginInfo"), + checkHomebrewFolderExists: () => + ipcRenderer.invoke("checkHomebrewFolderExists"), platform: process.platform, /* Auto update */ diff --git a/src/renderer/src/assets/icons/decky.png b/src/renderer/src/assets/icons/decky.png new file mode 100644 index 0000000000000000000000000000000000000000..205552dd3d7558e885ad4808e4345ac2d00738b6 GIT binary patch literal 215327 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 |_%gzbZ+qO%ANCsR6RiU; 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 literal 0 HcmV?d00001 diff --git a/src/renderer/src/components/confirm-modal/confirm-modal.scss b/src/renderer/src/components/confirm-modal/confirm-modal.scss deleted file mode 100644 index e5bda187..00000000 --- a/src/renderer/src/components/confirm-modal/confirm-modal.scss +++ /dev/null @@ -1,11 +0,0 @@ -@use "../../scss/globals.scss"; - -.confirm-modal { - &__actions { - display: flex; - width: 100%; - justify-content: flex-end; - align-items: center; - gap: globals.$spacing-unit; - } -} diff --git a/src/renderer/src/components/confirm-modal/confirm-modal.tsx b/src/renderer/src/components/confirm-modal/confirm-modal.tsx deleted file mode 100644 index 75a8f5c9..00000000 --- a/src/renderer/src/components/confirm-modal/confirm-modal.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { useTranslation } from "react-i18next"; -import { Button, Modal } from "@renderer/components"; -import "./confirm-modal.scss"; - -export interface ConfirmModalProps { - visible: boolean; - title: string; - description?: string; - onClose: () => void; - onConfirm: () => Promise | void; - confirmLabel?: string; - cancelLabel?: string; - confirmTheme?: "primary" | "outline" | "danger"; - confirmDisabled?: boolean; -} - -export function ConfirmModal({ - visible, - title, - description, - onClose, - onConfirm, - confirmLabel, - cancelLabel, - confirmTheme = "outline", - confirmDisabled = false, -}: ConfirmModalProps) { - const { t } = useTranslation(); - - const handleConfirm = async () => { - await onConfirm(); - onClose(); - }; - - return ( - -
- - - -
-
- ); -} diff --git a/src/renderer/src/components/confirmation-modal/confirmation-modal.scss b/src/renderer/src/components/confirmation-modal/confirmation-modal.scss index 428818c4..7689ebcd 100644 --- a/src/renderer/src/components/confirmation-modal/confirmation-modal.scss +++ b/src/renderer/src/components/confirmation-modal/confirmation-modal.scss @@ -8,7 +8,7 @@ &__actions { display: flex; align-self: flex-end; - gap: calc(globals.$spacing-unit * 2); + gap: globals.$spacing-unit; } &__description { font-size: 16px; diff --git a/src/renderer/src/components/confirmation-modal/confirmation-modal.tsx b/src/renderer/src/components/confirmation-modal/confirmation-modal.tsx index 63256935..f81453fa 100644 --- a/src/renderer/src/components/confirmation-modal/confirmation-modal.tsx +++ b/src/renderer/src/components/confirmation-modal/confirmation-modal.tsx @@ -42,7 +42,7 @@ export function ConfirmationModal({ {cancelButtonLabel} ))} + {window.electron.platform === "linux" && homebrewFolderExists && ( +
  • + +
  • + )} @@ -321,18 +404,20 @@ export function Sidebar() { - {hasActiveSubscription && ( - - )} +
    + {hasActiveSubscription && ( + + )} +
    @@ -772,11 +791,20 @@ export function GameDetailsContent() {
    {review.user?.profileImageUrl && ( - {review.user.displayName + )}
    {userDetails?.id === review.user?.id && ( diff --git a/src/renderer/src/pages/game-details/game-details.scss b/src/renderer/src/pages/game-details/game-details.scss index 09c0f05f..aee2e639 100644 --- a/src/renderer/src/pages/game-details/game-details.scss +++ b/src/renderer/src/pages/game-details/game-details.scss @@ -128,7 +128,7 @@ $hero-height: 300px; &__star-rating { display: flex; align-items: center; - gap: 4px; + gap: 2px; } &__star { @@ -136,7 +136,7 @@ $hero-height: 300px; border: none; color: #666666; cursor: pointer; - padding: 4px; + padding: 2px; border-radius: 4px; display: flex; align-items: center; @@ -220,30 +220,6 @@ $hero-height: 300px; } } - &__review-submit-button { - background-color: rgba(255, 255, 255, 0.05); - border: 1px solid rgba(255, 255, 255, 0.1); - border-radius: 6px; - color: #ffffff; - padding: 10px 20px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: all 0.2s ease; - white-space: nowrap; - - &:hover:not(:disabled) { - background-color: rgba(255, 255, 255, 0.08); - border-color: rgba(255, 255, 255, 0.15); - } - - &:disabled { - background-color: rgba(255, 255, 255, 0.1); - cursor: not-allowed; - color: rgba(255, 255, 255, 0.5); - } - } - &__reviews-list { margin-top: calc(globals.$spacing-unit * 3); } @@ -288,7 +264,12 @@ $hero-height: 300px; } &__review-item { - background: rgba(255, 255, 255, 0.03); + background: linear-gradient( + to right, + globals.$dark-background-color 0%, + globals.$dark-background-color 30%, + globals.$background-color 100% + ); border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 6px; padding: calc(globals.$spacing-unit * 2); @@ -310,12 +291,29 @@ $hero-height: 300px; gap: calc(globals.$spacing-unit * 1); } + &__review-avatar-button { + background: none; + border: none; + padding: 0; + cursor: pointer; + transition: opacity 0.2s ease; + + &:hover { + opacity: 0.8; + } + + &:active { + opacity: 0.6; + } + } + &__review-avatar { width: 32px; height: 32px; border-radius: 4px; object-fit: cover; border: 2px solid rgba(255, 255, 255, 0.1); + display: block; } &__review-user-info { @@ -370,16 +368,7 @@ $hero-height: 300px; &:hover { background: rgba(255, 255, 255, 0.1); border-color: rgba(255, 255, 255, 0.2); - } - - &--upvote:hover { - color: #4caf50; - border-color: #4caf50; - } - - &--downvote:hover { - color: #f44336; - border-color: #f44336; + color: #ffffff; } &--active { @@ -398,6 +387,9 @@ $hero-height: 300px; span { font-weight: 500; + display: inline-block; + min-width: 1ch; + overflow: hidden; } } @@ -1015,9 +1007,9 @@ $hero-height: 300px; &__review-input-container { display: flex; flex-direction: column; - border: 1px solid #3a3a3a; + border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 8px; - background-color: #1e1e1e; + background-color: globals.$dark-background-color; overflow: hidden; } @@ -1026,8 +1018,8 @@ $hero-height: 300px; justify-content: space-between; align-items: center; padding: 8px 12px; - background-color: #2a2a2a; - border-bottom: 1px solid #3a3a3a; + background-color: globals.$background-color; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); } &__review-editor-toolbar { @@ -1037,7 +1029,7 @@ $hero-height: 300px; &__editor-button { background: none; - border: 1px solid #4a4a4a; + border: 1px solid rgba(255, 255, 255, 0.15); border-radius: 4px; color: #ffffff; padding: 4px 8px; @@ -1046,13 +1038,13 @@ $hero-height: 300px; transition: all 0.2s ease; &:hover { - background-color: #3a3a3a; - border-color: #5a5a5a; + background-color: rgba(255, 255, 255, 0.08); + border-color: rgba(255, 255, 255, 0.2); } &.is-active { - background-color: #0078d4; - border-color: #0078d4; + background-color: globals.$brand-blue; + border-color: globals.$brand-blue; } &:disabled { diff --git a/src/renderer/src/pages/settings/settings-debrid.scss b/src/renderer/src/pages/settings/settings-debrid.scss new file mode 100644 index 00000000..749ddbbc --- /dev/null +++ b/src/renderer/src/pages/settings/settings-debrid.scss @@ -0,0 +1,71 @@ +@use "../../scss/globals.scss"; + +.settings-debrid { + display: flex; + flex-direction: column; + gap: calc(globals.$spacing-unit * 2); + + &__description { + margin: 0 0 calc(globals.$spacing-unit * 2) 0; + color: var(--text-secondary); + line-height: 1.6; + } + + &__section { + display: flex; + flex-direction: column; + + &:not(:last-child) { + margin-bottom: calc(globals.$spacing-unit * 2); + } + } + + &__section-header { + display: flex; + align-items: center; + gap: calc(globals.$spacing-unit); + margin-bottom: calc(globals.$spacing-unit * 2); + } + + &__section-title { + margin: 0; + font-size: 1.125rem; + font-weight: 600; + color: var(--text-primary); + } + + &__collapse-button { + background: none; + border: none; + color: rgba(255, 255, 255, 0.7); + cursor: pointer; + padding: 4px; + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; + transition: all ease 0.2s; + flex-shrink: 0; + + &:hover { + color: rgba(255, 255, 255, 0.9); + background-color: rgba(255, 255, 255, 0.1); + } + } + + &__check-icon { + color: white; + flex-shrink: 0; + } + + &__beta-badge { + background: linear-gradient(135deg, #c9aa71, #d4af37); + color: #1a1a1a; + font-size: 0.625rem; + font-weight: 700; + padding: 2px 6px; + border-radius: 4px; + letter-spacing: 0.5px; + flex-shrink: 0; + } +} diff --git a/src/renderer/src/pages/settings/settings-debrid.tsx b/src/renderer/src/pages/settings/settings-debrid.tsx new file mode 100644 index 00000000..4bb7d276 --- /dev/null +++ b/src/renderer/src/pages/settings/settings-debrid.tsx @@ -0,0 +1,228 @@ +import { useState, useCallback, useMemo } from "react"; +import { useFeature, useAppSelector } from "@renderer/hooks"; +import { SettingsTorBox } from "./settings-torbox"; +import { SettingsRealDebrid } from "./settings-real-debrid"; +import { SettingsAllDebrid } from "./settings-all-debrid"; +import { motion, AnimatePresence } from "framer-motion"; +import { ChevronRightIcon, CheckCircleFillIcon } from "@primer/octicons-react"; +import { useTranslation } from "react-i18next"; +import "./settings-debrid.scss"; + +interface CollapseState { + torbox: boolean; + realDebrid: boolean; + allDebrid: boolean; +} + +const sectionVariants = { + collapsed: { + opacity: 0, + y: -20, + height: 0, + transition: { + duration: 0.3, + ease: [0.25, 0.1, 0.25, 1], + opacity: { duration: 0.1 }, + y: { duration: 0.1 }, + height: { duration: 0.2 }, + }, + }, + expanded: { + opacity: 1, + y: 0, + height: "auto", + transition: { + duration: 0.3, + ease: [0.25, 0.1, 0.25, 1], + opacity: { duration: 0.2, delay: 0.1 }, + y: { duration: 0.3 }, + height: { duration: 0.3 }, + }, + }, +}; + +const chevronVariants = { + collapsed: { + rotate: 0, + transition: { + duration: 0.2, + ease: "easeInOut", + }, + }, + expanded: { + rotate: 90, + transition: { + duration: 0.2, + ease: "easeInOut", + }, + }, +}; + +export function SettingsDebrid() { + const { t } = useTranslation("settings"); + const { isFeatureEnabled, Feature } = useFeature(); + const isTorBoxEnabled = isFeatureEnabled(Feature.TorBox); + + const userPreferences = useAppSelector( + (state) => state.userPreferences.value + ); + + const initialCollapseState = useMemo(() => { + return { + torbox: !userPreferences?.torBoxApiToken, + realDebrid: !userPreferences?.realDebridApiToken, + allDebrid: !userPreferences?.allDebridApiKey, + }; + }, [userPreferences]); + + const [collapseState, setCollapseState] = + useState(initialCollapseState); + + const toggleSection = useCallback((section: keyof CollapseState) => { + setCollapseState((prevState) => ({ + ...prevState, + [section]: !prevState[section], + })); + }, []); + + return ( +
    +

    {t("debrid_description")}

    + +
    +
    + +

    Real-Debrid

    + {userPreferences?.realDebridApiToken && ( + + )} +
    + + + {!collapseState.realDebrid && ( + + + + )} + +
    + + {isTorBoxEnabled && ( +
    +
    + +

    TorBox

    + {userPreferences?.torBoxApiToken && ( + + )} +
    + + + {!collapseState.torbox && ( + + + + )} + +
    + )} + +
    +
    + +

    All-Debrid

    + BETA + {userPreferences?.allDebridApiKey && ( + + )} +
    + + + {!collapseState.allDebrid && ( + + + + )} + +
    +
    + ); +} diff --git a/src/renderer/src/pages/settings/settings.tsx b/src/renderer/src/pages/settings/settings.tsx index d609d218..eb19af31 100644 --- a/src/renderer/src/pages/settings/settings.tsx +++ b/src/renderer/src/pages/settings/settings.tsx @@ -1,7 +1,5 @@ import { Button } from "@renderer/components"; import { useTranslation } from "react-i18next"; -import { SettingsRealDebrid } from "./settings-real-debrid"; -import { SettingsAllDebrid } from "./settings-all-debrid"; import { SettingsGeneral } from "./settings-general"; import { SettingsBehavior } from "./settings-behavior"; import { SettingsDownloadSources } from "./settings-download-sources"; @@ -10,21 +8,17 @@ import { SettingsContextProvider, } from "@renderer/context"; import { SettingsAccount } from "./settings-account"; -import { useFeature, useUserDetails } from "@renderer/hooks"; +import { useUserDetails } from "@renderer/hooks"; import { useMemo } from "react"; import "./settings.scss"; import { SettingsAppearance } from "./aparence/settings-appearance"; -import { SettingsTorBox } from "./settings-torbox"; +import { SettingsDebrid } from "./settings-debrid"; export default function Settings() { const { t } = useTranslation("settings"); const { userDetails } = useUserDetails(); - const { isFeatureEnabled, Feature } = useFeature(); - - const isTorBoxEnabled = isFeatureEnabled(Feature.TorBox); - const categories = useMemo(() => { const categories = [ { tabLabel: t("general"), contentTitle: t("general") }, @@ -34,16 +28,7 @@ export default function Settings() { tabLabel: t("appearance"), contentTitle: t("appearance"), }, - ...(isTorBoxEnabled - ? [ - { - tabLabel: "TorBox", - contentTitle: "TorBox", - }, - ] - : []), - { tabLabel: "Real-Debrid", contentTitle: "Real-Debrid" }, - { tabLabel: "All-Debrid", contentTitle: "All-Debrid" }, + { tabLabel: t("debrid"), contentTitle: t("debrid") }, ]; if (userDetails) @@ -52,7 +37,7 @@ export default function Settings() { { tabLabel: t("account"), contentTitle: t("account") }, ]; return categories; - }, [userDetails, t, isTorBoxEnabled]); + }, [userDetails, t]); return ( @@ -76,15 +61,7 @@ export default function Settings() { } if (currentCategoryIndex === 4) { - return ; - } - - if (currentCategoryIndex === 5) { - return ; - } - - if (currentCategoryIndex === 6) { - return ; + return ; } return ; From 5877c8c79807a1c2495a9267d649f88cf0c99812 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sun, 12 Oct 2025 18:40:05 +0100 Subject: [PATCH 039/197] feat: adding review styling --- src/main/events/misc/get-hydra-decky-plugin-info.ts | 1 - .../src/pages/game-details/sidebar/sidebar.tsx | 13 +++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/events/misc/get-hydra-decky-plugin-info.ts b/src/main/events/misc/get-hydra-decky-plugin-info.ts index da72033e..6ff7b050 100644 --- a/src/main/events/misc/get-hydra-decky-plugin-info.ts +++ b/src/main/events/misc/get-hydra-decky-plugin-info.ts @@ -60,4 +60,3 @@ const getHydraDeckyPluginInfo = async ( }; registerEvent("getHydraDeckyPluginInfo", getHydraDeckyPluginInfo); - diff --git a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx index 33009508..df1429ec 100755 --- a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx +++ b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx @@ -233,9 +233,18 @@ export function Sidebar() { {t("rating_count")}

    From 34aea2b0c421286b285a58b35e9df86be28bdac6 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sun, 12 Oct 2025 18:51:47 +0100 Subject: [PATCH 040/197] feat: adding api call for decky plugin --- src/locales/en/translation.json | 1 + src/locales/pt-BR/translation.json | 1 + .../misc/get-hydra-decky-plugin-info.ts | 36 ++- .../events/misc/install-hydra-decky-plugin.ts | 2 +- src/main/services/decky-plugin.ts | 209 +++++++++++++----- .../src/components/sidebar/sidebar.tsx | 17 +- src/renderer/src/declaration.d.ts | 2 + src/shared/html-sanitizer.ts | 2 - 8 files changed, 199 insertions(+), 71 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 3dc93d90..0ca77d87 100755 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -78,6 +78,7 @@ "edit_game_modal_drop_to_replace_logo": "Drop to replace logo", "edit_game_modal_drop_to_replace_hero": "Drop to replace hero", "install_decky_plugin": "Install Decky Plugin", + "update_decky_plugin": "Update Decky Plugin", "decky_plugin_installed_version": "Decky Plugin (v{{version}})", "install_decky_plugin_title": "Install Hydra Decky Plugin", "install_decky_plugin_message": "This will download and install the Hydra plugin for Decky Loader. This may require elevated permissions. Continue?", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index e9a84c89..b9cee539 100755 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -78,6 +78,7 @@ "edit_game_modal_drop_to_replace_logo": "Solte para substituir o logo", "edit_game_modal_drop_to_replace_hero": "Solte para substituir o hero", "install_decky_plugin": "Instalar Plugin Decky", + "update_decky_plugin": "Atualizar Plugin Decky", "decky_plugin_installed_version": "Plugin Decky (v{{version}})", "install_decky_plugin_title": "Instalar Plugin Hydra Decky", "install_decky_plugin_message": "Isso irá baixar e instalar o plugin Hydra para Decky Loader. Pode ser necessário permissões elevadas. Continuar?", diff --git a/src/main/events/misc/get-hydra-decky-plugin-info.ts b/src/main/events/misc/get-hydra-decky-plugin-info.ts index 6ff7b050..430bd691 100644 --- a/src/main/events/misc/get-hydra-decky-plugin-info.ts +++ b/src/main/events/misc/get-hydra-decky-plugin-info.ts @@ -1,17 +1,37 @@ import { registerEvent } from "../register-event"; -import { logger } from "@main/services"; +import { logger, HydraApi } from "@main/services"; import { HYDRA_DECKY_PLUGIN_LOCATION } from "@main/constants"; import fs from "node:fs"; import path from "node:path"; +interface DeckyReleaseInfo { + version: string; + downloadUrl: string; +} + const getHydraDeckyPluginInfo = async ( _event: Electron.IpcMainInvokeEvent ): Promise<{ installed: boolean; version: string | null; path: string; + outdated: boolean; + expectedVersion: string | null; }> => { try { + // Fetch the expected version from API + let expectedVersion: string | null = null; + try { + const releaseInfo = await HydraApi.get( + "/decky/release", + {}, + { needsAuth: false } + ); + expectedVersion = releaseInfo.version; + } catch (error) { + logger.error("Failed to fetch Decky release info:", error); + } + // Check if plugin folder exists if (!fs.existsSync(HYDRA_DECKY_PLUGIN_LOCATION)) { logger.log("Hydra Decky plugin not installed"); @@ -19,6 +39,8 @@ const getHydraDeckyPluginInfo = async ( installed: false, version: null, path: HYDRA_DECKY_PLUGIN_LOCATION, + outdated: true, + expectedVersion, }; } @@ -34,6 +56,8 @@ const getHydraDeckyPluginInfo = async ( installed: false, version: null, path: HYDRA_DECKY_PLUGIN_LOCATION, + outdated: true, + expectedVersion, }; } @@ -42,12 +66,18 @@ const getHydraDeckyPluginInfo = async ( const packageJson = JSON.parse(packageJsonContent); const version = packageJson.version; - logger.log(`Hydra Decky plugin installed, version: ${version}`); + const outdated = expectedVersion ? version !== expectedVersion : false; + + logger.log( + `Hydra Decky plugin installed, version: ${version}, expected: ${expectedVersion}, outdated: ${outdated}` + ); return { installed: true, version, path: HYDRA_DECKY_PLUGIN_LOCATION, + outdated, + expectedVersion, }; } catch (error) { logger.error("Failed to get plugin info:", error); @@ -55,6 +85,8 @@ const getHydraDeckyPluginInfo = async ( installed: false, version: null, path: HYDRA_DECKY_PLUGIN_LOCATION, + outdated: true, + expectedVersion: null, }; } }; diff --git a/src/main/events/misc/install-hydra-decky-plugin.ts b/src/main/events/misc/install-hydra-decky-plugin.ts index 3ddbbd64..e14ea2ed 100644 --- a/src/main/events/misc/install-hydra-decky-plugin.ts +++ b/src/main/events/misc/install-hydra-decky-plugin.ts @@ -41,7 +41,7 @@ const installHydraDeckyPlugin = async ( success: false, path: HYDRA_DECKY_PLUGIN_LOCATION, currentVersion: null, - expectedVersion: "0.0.3", + expectedVersion: "unknown", error: errorMessage, }; } diff --git a/src/main/services/decky-plugin.ts b/src/main/services/decky-plugin.ts index 7e178189..4dc1fdad 100644 --- a/src/main/services/decky-plugin.ts +++ b/src/main/services/decky-plugin.ts @@ -11,11 +11,35 @@ import { import { logger } from "./logger"; import { SevenZip } from "./7zip"; import { SystemPath } from "./system-path"; +import { HydraApi } from "./hydra-api"; + +interface DeckyReleaseInfo { + version: string; + downloadUrl: string; +} export class DeckyPlugin { - private static readonly EXPECTED_VERSION = "0.0.3"; - private static readonly DOWNLOAD_URL = - "https://github.com/hydralauncher/decky-hydra-launcher/releases/download/0.0.3/Hydra.zip"; + private static releaseInfo: DeckyReleaseInfo | null = null; + + private static async getDeckyReleaseInfo(): Promise { + if (this.releaseInfo) { + return this.releaseInfo; + } + + try { + const response = await HydraApi.get( + "/decky/release", + {}, + { needsAuth: false } + ); + + this.releaseInfo = response; + return response; + } catch (error) { + logger.error("Failed to fetch Decky release info:", error); + throw error; + } + } private static getPackageJsonPath(): string { return path.join(HYDRA_DECKY_PLUGIN_LOCATION, "package.json"); @@ -24,10 +48,11 @@ export class DeckyPlugin { private static async downloadPlugin(): Promise { logger.log("Downloading Hydra Decky plugin..."); + const releaseInfo = await this.getDeckyReleaseInfo(); const tempDir = SystemPath.getPath("temp"); const zipPath = path.join(tempDir, "Hydra.zip"); - const response = await axios.get(this.DOWNLOAD_URL, { + const response = await axios.get(releaseInfo.downloadUrl, { responseType: "arraybuffer", }); @@ -209,14 +234,15 @@ export class DeckyPlugin { return; } + const releaseInfo = await this.getDeckyReleaseInfo(); const packageJsonContent = fs.readFileSync(packageJsonPath, "utf-8"); const packageJson = JSON.parse(packageJsonContent); const currentVersion = packageJson.version; - const isOutdated = currentVersion !== this.EXPECTED_VERSION; + const isOutdated = currentVersion !== releaseInfo.version; if (isOutdated) { logger.log( - `Hydra Decky plugin is outdated. Current: ${currentVersion}, Expected: ${this.EXPECTED_VERSION}. Updating...` + `Hydra Decky plugin is outdated. Current: ${currentVersion}, Expected: ${releaseInfo.version}. Updating...` ); await this.updatePlugin(); @@ -235,78 +261,139 @@ export class DeckyPlugin { currentVersion: string | null; expectedVersion: string; }> { - if (!fs.existsSync(HYDRA_DECKY_PLUGIN_LOCATION)) { - logger.log("Hydra Decky plugin folder not found, installing..."); + try { + const releaseInfo = await this.getDeckyReleaseInfo(); + + if (!fs.existsSync(HYDRA_DECKY_PLUGIN_LOCATION)) { + logger.log("Hydra Decky plugin folder not found, installing..."); + + try { + await this.updatePlugin(); + + // Read the actual installed version from package.json + const packageJsonPath = this.getPackageJsonPath(); + if (fs.existsSync(packageJsonPath)) { + const packageJsonContent = fs.readFileSync( + packageJsonPath, + "utf-8" + ); + const packageJson = JSON.parse(packageJsonContent); + return { + exists: true, + outdated: false, + currentVersion: packageJson.version, + expectedVersion: releaseInfo.version, + }; + } + + return { + exists: true, + outdated: false, + currentVersion: releaseInfo.version, + expectedVersion: releaseInfo.version, + }; + } catch (error) { + logger.error("Failed to install plugin:", error); + return { + exists: false, + outdated: true, + currentVersion: null, + expectedVersion: releaseInfo.version, + }; + } + } + + const packageJsonPath = this.getPackageJsonPath(); try { - await this.updatePlugin(); + if (!fs.existsSync(packageJsonPath)) { + logger.log( + "Hydra Decky plugin package.json not found, installing..." + ); + + await this.updatePlugin(); + + // Read the actual installed version from package.json + if (fs.existsSync(packageJsonPath)) { + const packageJsonContent = fs.readFileSync( + packageJsonPath, + "utf-8" + ); + const packageJson = JSON.parse(packageJsonContent); + return { + exists: true, + outdated: false, + currentVersion: packageJson.version, + expectedVersion: releaseInfo.version, + }; + } + + return { + exists: true, + outdated: false, + currentVersion: releaseInfo.version, + expectedVersion: releaseInfo.version, + }; + } + + const packageJsonContent = fs.readFileSync(packageJsonPath, "utf-8"); + const packageJson = JSON.parse(packageJsonContent); + const currentVersion = packageJson.version; + const isOutdated = currentVersion !== releaseInfo.version; + + if (isOutdated) { + logger.log( + `Hydra Decky plugin is outdated. Current: ${currentVersion}, Expected: ${releaseInfo.version}` + ); + + await this.updatePlugin(); + + if (fs.existsSync(packageJsonPath)) { + const updatedPackageJsonContent = fs.readFileSync( + packageJsonPath, + "utf-8" + ); + const updatedPackageJson = JSON.parse(updatedPackageJsonContent); + return { + exists: true, + outdated: false, + currentVersion: updatedPackageJson.version, + expectedVersion: releaseInfo.version, + }; + } + + return { + exists: true, + outdated: false, + currentVersion: releaseInfo.version, + expectedVersion: releaseInfo.version, + }; + } else { + logger.log(`Hydra Decky plugin is up to date (${currentVersion})`); + } + return { exists: true, - outdated: false, - currentVersion: this.EXPECTED_VERSION, - expectedVersion: this.EXPECTED_VERSION, + outdated: isOutdated, + currentVersion, + expectedVersion: releaseInfo.version, }; } catch (error) { - logger.error("Failed to install plugin:", error); + logger.error(`Error checking Hydra Decky plugin version: ${error}`); return { exists: false, outdated: true, currentVersion: null, - expectedVersion: this.EXPECTED_VERSION, + expectedVersion: releaseInfo.version, }; } - } - - const packageJsonPath = this.getPackageJsonPath(); - - try { - if (!fs.existsSync(packageJsonPath)) { - logger.log("Hydra Decky plugin package.json not found, installing..."); - - await this.updatePlugin(); - return { - exists: true, - outdated: false, - currentVersion: this.EXPECTED_VERSION, - expectedVersion: this.EXPECTED_VERSION, - }; - } - - const packageJsonContent = fs.readFileSync(packageJsonPath, "utf-8"); - const packageJson = JSON.parse(packageJsonContent); - const currentVersion = packageJson.version; - const isOutdated = currentVersion !== this.EXPECTED_VERSION; - - if (isOutdated) { - logger.log( - `Hydra Decky plugin is outdated. Current: ${currentVersion}, Expected: ${this.EXPECTED_VERSION}` - ); - - await this.updatePlugin(); - - return { - exists: true, - outdated: false, - currentVersion: this.EXPECTED_VERSION, - expectedVersion: this.EXPECTED_VERSION, - }; - } else { - logger.log(`Hydra Decky plugin is up to date (${currentVersion})`); - } - - return { - exists: true, - outdated: isOutdated, - currentVersion, - expectedVersion: this.EXPECTED_VERSION, - }; } catch (error) { - logger.error(`Error checking Hydra Decky plugin version: ${error}`); + logger.error(`Error fetching release info: ${error}`); return { exists: false, outdated: true, currentVersion: null, - expectedVersion: this.EXPECTED_VERSION, + expectedVersion: "unknown", }; } } diff --git a/src/renderer/src/components/sidebar/sidebar.tsx b/src/renderer/src/components/sidebar/sidebar.tsx index 13647556..5dd6ae94 100644 --- a/src/renderer/src/components/sidebar/sidebar.tsx +++ b/src/renderer/src/components/sidebar/sidebar.tsx @@ -51,7 +51,8 @@ export function Sidebar() { const [deckyPluginInfo, setDeckyPluginInfo] = useState<{ installed: boolean; version: string | null; - }>({ installed: false, version: null }); + outdated: boolean; + }>({ installed: false, version: null, outdated: false }); const [homebrewFolderExists, setHomebrewFolderExists] = useState(false); const [showDeckyConfirmModal, setShowDeckyConfirmModal] = useState(false); const navigate = useNavigate(); @@ -102,6 +103,7 @@ export function Sidebar() { setDeckyPluginInfo({ installed: info.installed, version: info.version, + outdated: info.outdated, }); setHomebrewFolderExists(folderExists); } catch (error) { @@ -110,6 +112,9 @@ export function Sidebar() { }; const handleInstallHydraDeckyPlugin = () => { + if (deckyPluginInfo.installed && !deckyPluginInfo.outdated) { + return; + } setShowDeckyConfirmModal(true); }; @@ -318,11 +323,13 @@ export function Sidebar() { style={{ width: 16, height: 16 }} /> - {deckyPluginInfo.installed + {deckyPluginInfo.installed && !deckyPluginInfo.outdated ? t("decky_plugin_installed_version", { version: deckyPluginInfo.version, }) - : t("install_decky_plugin")} + : deckyPluginInfo.installed && deckyPluginInfo.outdated + ? t("update_decky_plugin") + : t("install_decky_plugin")} @@ -433,12 +440,12 @@ export function Sidebar() { ; checkHomebrewFolderExists: () => Promise; onCommonRedistProgress: ( diff --git a/src/shared/html-sanitizer.ts b/src/shared/html-sanitizer.ts index ea3d475b..c78d7dd8 100644 --- a/src/shared/html-sanitizer.ts +++ b/src/shared/html-sanitizer.ts @@ -6,8 +6,6 @@ function removeZalgoText(text: string): string { return text.replaceAll(zalgoRegex, ""); } - - export function sanitizeHtml(html: string): string { if (!html || typeof html !== "string") { return ""; From 38b04ee991d5317350a18c9077782d6af1237b20 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sun, 12 Oct 2025 19:01:07 +0100 Subject: [PATCH 041/197] fix: fixing translations --- src/shared/html-sanitizer.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/shared/html-sanitizer.ts b/src/shared/html-sanitizer.ts index 01027b2f..4a243e44 100644 --- a/src/shared/html-sanitizer.ts +++ b/src/shared/html-sanitizer.ts @@ -1,4 +1,5 @@ function removeZalgoText(text: string): string { + // eslint-disable-next-line no-misleading-character-class const zalgoRegex = /[\u0300-\u036F\u1AB0-\u1AFF\u1DC0-\u1DFF\u20D0-\u20FF\uFE20-\uFE2F]/g; From 030b3b8f7c9c5995c936691e0e128948006a8a1b Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sun, 12 Oct 2025 19:03:30 +0100 Subject: [PATCH 042/197] fix: fixing translations --- src/shared/html-sanitizer.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/shared/html-sanitizer.ts b/src/shared/html-sanitizer.ts index 4a243e44..778a5ee4 100644 --- a/src/shared/html-sanitizer.ts +++ b/src/shared/html-sanitizer.ts @@ -1,7 +1,8 @@ function removeZalgoText(text: string): string { - // eslint-disable-next-line no-misleading-character-class + // Match combining characters that are commonly used in Zalgo text + // Using alternation instead of character class to avoid misleading-character-class warning const zalgoRegex = - /[\u0300-\u036F\u1AB0-\u1AFF\u1DC0-\u1DFF\u20D0-\u20FF\uFE20-\uFE2F]/g; + /(\u0300-\u036F|\u1AB0-\u1AFF|\u1DC0-\u1DFF|\u20D0-\u20FF|\uFE20-\uFE2F)/g; return text.replaceAll(zalgoRegex, ""); } From c2e5bc0e91d14a692d7d60f06aa31ce97ceb03fe Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sun, 12 Oct 2025 19:04:10 +0100 Subject: [PATCH 043/197] fix: fixing translations --- src/shared/html-sanitizer.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/shared/html-sanitizer.ts b/src/shared/html-sanitizer.ts index 778a5ee4..8f3ae932 100644 --- a/src/shared/html-sanitizer.ts +++ b/src/shared/html-sanitizer.ts @@ -1,10 +1,19 @@ function removeZalgoText(text: string): string { // Match combining characters that are commonly used in Zalgo text - // Using alternation instead of character class to avoid misleading-character-class warning - const zalgoRegex = - /(\u0300-\u036F|\u1AB0-\u1AFF|\u1DC0-\u1DFF|\u20D0-\u20FF|\uFE20-\uFE2F)/g; + // Using a more explicit approach to avoid misleading-character-class warning + const combiningMarks = [ + /\u0300-\u036F/g, // Combining Diacritical Marks + /\u1AB0-\u1AFF/g, // Combining Diacritical Marks Extended + /\u1DC0-\u1DFF/g, // Combining Diacritical Marks Supplement + /\u20D0-\u20FF/g, // Combining Diacritical Marks for Symbols + /\uFE20-\uFE2F/g, // Combining Half Marks + ]; - return text.replaceAll(zalgoRegex, ""); + let result = text; + for (const regex of combiningMarks) { + result = result.replace(regex, ""); + } + return result; } export function sanitizeHtml(html: string): string { From 1cba3f350c438e281a270d070ea25c8e1bee6959 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Sun, 12 Oct 2025 22:12:15 +0300 Subject: [PATCH 044/197] formatting --- src/shared/html-sanitizer.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/shared/html-sanitizer.ts b/src/shared/html-sanitizer.ts index ea3d475b..c78d7dd8 100644 --- a/src/shared/html-sanitizer.ts +++ b/src/shared/html-sanitizer.ts @@ -6,8 +6,6 @@ function removeZalgoText(text: string): string { return text.replaceAll(zalgoRegex, ""); } - - export function sanitizeHtml(html: string): string { if (!html || typeof html !== "string") { return ""; From 7c33c43d9c3475d00b7a1a472a035ff7dec06319 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sun, 12 Oct 2025 20:16:37 +0100 Subject: [PATCH 045/197] fix: fixing search on sources modal --- .../components/context-menu/context-menu.scss | 3 +- .../components/context-menu/context-menu.tsx | 4 +-- .../game-context-menu/game-context-menu.tsx | 18 ++++++++++-- .../game-context-menu/use-game-actions.ts | 28 ++++++++++++++++++- .../game-details/modals/repacks-modal.tsx | 14 +++++++++- 5 files changed, 58 insertions(+), 9 deletions(-) diff --git a/src/renderer/src/components/context-menu/context-menu.scss b/src/renderer/src/components/context-menu/context-menu.scss index 673fa2da..e292b22d 100644 --- a/src/renderer/src/components/context-menu/context-menu.scss +++ b/src/renderer/src/components/context-menu/context-menu.scss @@ -19,7 +19,6 @@ &__item-container { position: relative; - padding-right: 8px; } &__item { @@ -97,7 +96,7 @@ &__submenu { position: absolute; - left: calc(100% - 2px); + left: 100%; top: 0; background-color: globals.$background-color; border: 1px solid globals.$border-color; diff --git a/src/renderer/src/components/context-menu/context-menu.tsx b/src/renderer/src/components/context-menu/context-menu.tsx index cb9e0347..34431728 100644 --- a/src/renderer/src/components/context-menu/context-menu.tsx +++ b/src/renderer/src/components/context-menu/context-menu.tsx @@ -144,9 +144,9 @@ export function ContextMenu({ if (parentRect.right + submenuWidth > viewportWidth - 8) { styles.left = "auto"; - styles.right = "calc(100% - 2px)"; + styles.right = "100%"; } else { - styles.left = "calc(100% - 2px)"; + styles.left = "100%"; styles.right = undefined; } diff --git a/src/renderer/src/components/game-context-menu/game-context-menu.tsx b/src/renderer/src/components/game-context-menu/game-context-menu.tsx index e424dac7..694012b7 100644 --- a/src/renderer/src/components/game-context-menu/game-context-menu.tsx +++ b/src/renderer/src/components/game-context-menu/game-context-menu.tsx @@ -40,9 +40,11 @@ export function GameContextMenu({ canPlay, isDeleting, isGameDownloading, + isGameRunning, hasRepacks, shouldShowCreateStartMenuShortcut, handlePlayGame, + handleCloseGame, handleToggleFavorite, handleCreateShortcut, handleCreateSteamShortcut, @@ -57,10 +59,20 @@ export function GameContextMenu({ const items: ContextMenuItemData[] = [ { id: "play", - label: canPlay ? t("play") : t("download"), - icon: canPlay ? : , + label: isGameRunning ? t("close") : canPlay ? t("play") : t("download"), + icon: isGameRunning ? ( + + ) : canPlay ? ( + + ) : ( + + ), onClick: () => { - void handlePlayGame(); + if (isGameRunning) { + void handleCloseGame(); + } else { + void handlePlayGame(); + } }, disabled: isDeleting, }, diff --git a/src/renderer/src/components/game-context-menu/use-game-actions.ts b/src/renderer/src/components/game-context-menu/use-game-actions.ts index 042b796b..c7224225 100644 --- a/src/renderer/src/components/game-context-menu/use-game-actions.ts +++ b/src/renderer/src/components/game-context-menu/use-game-actions.ts @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import { useTranslation } from "react-i18next"; import { LibraryGame, ShortcutLocation } from "@types"; import { useDownload, useLibrary, useToast } from "@renderer/hooks"; @@ -21,6 +21,7 @@ export function useGameActions(game: LibraryGame) { } = useDownload(); const [creatingSteamShortcut, setCreatingSteamShortcut] = useState(false); + const [isGameRunning, setIsGameRunning] = useState(false); const canPlay = Boolean(game.executablePath); const isDeleting = isGameDeleting(game.id); @@ -30,6 +31,20 @@ export function useGameActions(game: LibraryGame) { const shouldShowCreateStartMenuShortcut = window.electron.platform === "win32"; + useEffect(() => { + const unsubscribe = window.electron.onGamesRunning((gamesIds) => { + const updatedIsGameRunning = + !!game?.id && + !!gamesIds.find((gameRunning) => gameRunning.id == game.id); + + setIsGameRunning(updatedIsGameRunning); + }); + + return () => { + unsubscribe(); + }; + }, [game?.id]); + const handlePlayGame = async () => { if (!canPlay) { const path = buildGameDetailsPath({ @@ -75,6 +90,15 @@ export function useGameActions(game: LibraryGame) { } }; + const handleCloseGame = async () => { + try { + await window.electron.closeGame(game.shop, game.objectId); + } catch (error) { + showErrorToast("Failed to close game"); + logger.error("Failed to close game", error); + } + }; + const handleToggleFavorite = async () => { try { if (game.favorite) { @@ -239,10 +263,12 @@ export function useGameActions(game: LibraryGame) { canPlay, isDeleting, isGameDownloading, + isGameRunning, hasRepacks, shouldShowCreateStartMenuShortcut, creatingSteamShortcut, handlePlayGame, + handleCloseGame, handleToggleFavorite, handleCreateShortcut, handleCreateSteamShortcut, diff --git a/src/renderer/src/pages/game-details/modals/repacks-modal.tsx b/src/renderer/src/pages/game-details/modals/repacks-modal.tsx index 97b8b1b5..f2d1f974 100644 --- a/src/renderer/src/pages/game-details/modals/repacks-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/repacks-modal.tsx @@ -161,6 +161,14 @@ export function RepacksModal({ const [isFilterDrawerOpen, setIsFilterDrawerOpen] = useState(false); + useEffect(() => { + if (!visible) { + setFilterTerm(""); + setSelectedFingerprints([]); + setIsFilterDrawerOpen(false); + } + }, [visible]); + return ( <>
    - + {downloadSources.length > 0 && (
    )} - {reviews.map((review) => ( -
    - {review.isBlocked && - !visibleBlockedReviews.has(review.id) ? ( -
    - Review from blocked user —{" "} - -
    - ) : ( - <> -
    -
    - {review.user?.profileImageUrl && ( - - )} -
    - -
    - - {formatDistance( - new Date(review.createdAt), - new Date(), - { addSuffix: true } - )} +
    0 ? 0.5 : 1, + transition: "opacity 0.2s ease", + }} + > + {reviews.map((review) => ( +
    + {review.isBlocked && + !visibleBlockedReviews.has(review.id) ? ( +
    + Review from blocked user —{" "} + +
    + ) : ( + <> +
    +
    + {review.user?.profileImageUrl && ( + + )} +
    + +
    + + {formatDistance( + new Date(review.createdAt), + new Date(), + { addSuffix: true } + )} +
    +
    + {[1, 2, 3, 4, 5].map((starValue) => ( + + ))} +
    - {[1, 2, 3, 4, 5].map((starValue) => ( - +
    +
    + + handleVoteReview(review.id, "upvote") + } + animate={ + review.hasUpvoted + ? { + scale: [1, 1.2, 1], + transition: { duration: 0.3 }, + } + : {} } - className={`game-details__review-star ${ - starValue <= review.score - ? "game-details__review-star--filled" - : "game-details__review-star--empty" - } ${ - starValue <= review.score - ? getScoreColorClass(review.score) - : "" - }`} - /> - ))} -
    -
    -
    -
    -
    - - handleVoteReview(review.id, "upvote") - } - animate={ - review.hasUpvoted - ? { - scale: [1, 1.2, 1], - transition: { duration: 0.3 }, - } - : {} - } - > - - - - (previousVotesRef.current.get(review.id) - ?.upvotes || 0) - } - variants={{ - enter: (isIncreasing: boolean) => ({ - y: isIncreasing ? 10 : -10, - opacity: 0, - }), - center: { y: 0, opacity: 1 }, - exit: (isIncreasing: boolean) => ({ - y: isIncreasing ? -10 : 10, - opacity: 0, - }), - }} - initial="enter" - animate="center" - exit="exit" - transition={{ duration: 0.2 }} - onAnimationComplete={() => { - previousVotesRef.current.set(review.id, { - upvotes: review.upvotes || 0, - downvotes: review.downvotes || 0, - }); - }} - > - {formatNumber(review.upvotes || 0)} - - - - - handleVoteReview(review.id, "downvote") - } - animate={ - review.hasDownvoted - ? { - scale: [1, 1.2, 1], - transition: { duration: 0.3 }, - } - : {} - } - > - - - - (previousVotesRef.current.get(review.id) - ?.downvotes || 0) - } - variants={{ - enter: (isIncreasing: boolean) => ({ - y: isIncreasing ? 10 : -10, - opacity: 0, - }), - center: { y: 0, opacity: 1 }, - exit: (isIncreasing: boolean) => ({ - y: isIncreasing ? -10 : 10, - opacity: 0, - }), - }} - initial="enter" - animate="center" - exit="exit" - transition={{ duration: 0.2 }} - onAnimationComplete={() => { - previousVotesRef.current.set(review.id, { - upvotes: review.upvotes || 0, - downvotes: review.downvotes || 0, - }); - }} - > - {formatNumber(review.downvotes || 0)} - - - -
    - {userDetails?.id === review.user?.id && ( - - )} - {review.isBlocked && - visibleBlockedReviews.has(review.id) && ( -
    + {userDetails?.id === review.user?.id && ( + )} -
    - - )} -
    - ))} + {review.isBlocked && + visibleBlockedReviews.has(review.id) && ( + + )} +
    + + )} +
    + ))} +
    {hasMoreReviews && !reviewsLoading && ( )} - {game?.shop !== "custom" && ( -
    - {showReviewForm && ( - <> -
    -

    - {t("leave_a_review")} -

    -
    - -
    -
    -
    -
    - - - -
    -
    - MAX_REVIEW_CHARS - ? "over-limit" - : "" - } - > - {reviewCharCount}/{MAX_REVIEW_CHARS} - -
    -
    -
    - -
    -
    - -
    -
    -
    - {[1, 2, 3, 4, 5].map((starValue) => ( - - ))} -
    -
    - - -
    -
    - - )} - - {showReviewForm && ( -
    - )} - -
    -
    -
    -

    - {t("reviews")} -

    - - {totalReviewCount} - -
    -
    - - - {reviewsLoading && reviews.length === 0 && ( -
    - {t("loading_reviews")} -
    - )} - - {!reviewsLoading && reviews.length === 0 && ( -
    -
    - -
    -

    - {t("no_reviews_yet")} -

    -

    - {t("be_first_to_review")} -

    -
    - )} - -
    0 ? 0.5 : 1, - transition: "opacity 0.2s ease", - }} - > - {reviews.map((review) => ( -
    - {review.isBlocked && - !visibleBlockedReviews.has(review.id) ? ( -
    - Review from blocked user —{" "} - -
    - ) : ( - <> -
    -
    - {review.user?.profileImageUrl && ( - - )} -
    - -
    - - {formatDistance( - new Date(review.createdAt), - new Date(), - { addSuffix: true } - )} -
    -
    -
    -
    - {[1, 2, 3, 4, 5].map((starValue) => ( - - ))} -
    -
    -
    -
    -
    - - handleVoteReview(review.id, "upvote") - } - animate={ - review.hasUpvoted - ? { - scale: [1, 1.2, 1], - transition: { duration: 0.3 }, - } - : {} - } - > - - - - (previousVotesRef.current.get(review.id) - ?.upvotes || 0) - } - variants={{ - enter: (isIncreasing: boolean) => ({ - y: isIncreasing ? 10 : -10, - opacity: 0, - }), - center: { y: 0, opacity: 1 }, - exit: (isIncreasing: boolean) => ({ - y: isIncreasing ? -10 : 10, - opacity: 0, - }), - }} - initial="enter" - animate="center" - exit="exit" - transition={{ duration: 0.2 }} - onAnimationComplete={() => { - previousVotesRef.current.set( - review.id, - { - upvotes: review.upvotes || 0, - downvotes: review.downvotes || 0, - } - ); - }} - > - {formatNumber(review.upvotes || 0)} - - - - - handleVoteReview(review.id, "downvote") - } - animate={ - review.hasDownvoted - ? { - scale: [1, 1.2, 1], - transition: { duration: 0.3 }, - } - : {} - } - > - - - - (previousVotesRef.current.get(review.id) - ?.downvotes || 0) - } - variants={{ - enter: (isIncreasing: boolean) => ({ - y: isIncreasing ? 10 : -10, - opacity: 0, - }), - center: { y: 0, opacity: 1 }, - exit: (isIncreasing: boolean) => ({ - y: isIncreasing ? -10 : 10, - opacity: 0, - }), - }} - initial="enter" - animate="center" - exit="exit" - transition={{ duration: 0.2 }} - onAnimationComplete={() => { - previousVotesRef.current.set( - review.id, - { - upvotes: review.upvotes || 0, - downvotes: review.downvotes || 0, - } - ); - }} - > - {formatNumber(review.downvotes || 0)} - - - -
    - {userDetails?.id === review.user?.id && ( - - )} - {review.isBlocked && - visibleBlockedReviews.has(review.id) && ( - - )} -
    - - )} -
    - ))} -
    - - {hasMoreReviews && !reviewsLoading && ( - - )} - - {reviewsLoading && reviews.length > 0 && ( -
    - {t("loading_more_reviews")} -
    - )} -
    -
    + {game?.shop !== "custom" && shop && objectId && ( + )}
    @@ -1077,15 +259,6 @@ export function GameDetailsContent() { onGameUpdated={handleGameUpdated} /> )} - - { - setShowDeleteReviewModal(false); - setReviewToDelete(null); - }} - onConfirm={confirmDeleteReview} - />
    ); } diff --git a/src/renderer/src/pages/game-details/game-details.scss b/src/renderer/src/pages/game-details/game-details.scss index aee2e639..555d0797 100644 --- a/src/renderer/src/pages/game-details/game-details.scss +++ b/src/renderer/src/pages/game-details/game-details.scss @@ -291,31 +291,6 @@ $hero-height: 300px; gap: calc(globals.$spacing-unit * 1); } - &__review-avatar-button { - background: none; - border: none; - padding: 0; - cursor: pointer; - transition: opacity 0.2s ease; - - &:hover { - opacity: 0.8; - } - - &:active { - opacity: 0.6; - } - } - - &__review-avatar { - width: 32px; - height: 32px; - border-radius: 4px; - object-fit: cover; - border: 2px solid rgba(255, 255, 255, 0.1); - display: block; - } - &__review-user-info { display: flex; flex-direction: column; diff --git a/src/renderer/src/pages/game-details/game-logo.tsx b/src/renderer/src/pages/game-details/game-logo.tsx new file mode 100644 index 00000000..f0cb92ae --- /dev/null +++ b/src/renderer/src/pages/game-details/game-logo.tsx @@ -0,0 +1,49 @@ +import type { Game, ShopDetailsWithAssets } from "@types"; + +interface GameLogoProps { + game: Game | null; + shopDetails: ShopDetailsWithAssets | null; +} + +const getImageWithCustomPriority = ( + customUrl: string | null | undefined, + originalUrl: string | null | undefined, + fallbackUrl?: string | null | undefined +) => { + return customUrl || originalUrl || fallbackUrl || ""; +}; + +export function GameLogo({ game, shopDetails }: Readonly) { + const isCustomGame = game?.shop === "custom"; + + const logoImage = isCustomGame + ? game?.logoImageUrl || "" + : getImageWithCustomPriority( + game?.customLogoImageUrl, + shopDetails?.assets?.logoImageUrl + ); + + if (isCustomGame) { + // For custom games, show logo image if available, otherwise show game title as text + if (logoImage) { + return ( + {game?.title} + ); + } else { + return
    {game?.title}
    ; + } + } else { + // For non-custom games, show logo image if available + return logoImage ? ( + {game?.title} + ) : null; + } +} diff --git a/src/renderer/src/pages/game-details/game-reviews.tsx b/src/renderer/src/pages/game-details/game-reviews.tsx new file mode 100644 index 00000000..467b53b2 --- /dev/null +++ b/src/renderer/src/pages/game-details/game-reviews.tsx @@ -0,0 +1,548 @@ +import { useCallback, useEffect, useRef, useState } from "react"; +import { NoteIcon } from "@primer/octicons-react"; +import { useEditor } from "@tiptap/react"; +import StarterKit from "@tiptap/starter-kit"; +import { useTranslation } from "react-i18next"; +import type { GameReview, Game, GameShop } from "@types"; + +import { ReviewForm } from "./review-form"; +import { ReviewItem } from "./review-item"; +import { ReviewSortOptions } from "./review-sort-options"; +import { ReviewPromptBanner } from "./review-prompt-banner"; +import { useToast } from "@renderer/hooks"; + +type ReviewSortOption = + | "newest" + | "oldest" + | "score_high" + | "score_low" + | "most_voted"; + +interface GameReviewsProps { + shop: GameShop; + objectId: string; + game: Game | null; + userDetailsId?: string; + isGameInLibrary: boolean; + hasUserReviewed: boolean; + onUserReviewedChange: (hasReviewed: boolean) => void; +} + +const MAX_REVIEW_CHARS = 1000; + +export function GameReviews({ + shop, + objectId, + game, + userDetailsId, + isGameInLibrary, + hasUserReviewed, + onUserReviewedChange, +}: Readonly) { + const { t } = useTranslation("game_details"); + const { showSuccessToast, showErrorToast } = useToast(); + + const [reviews, setReviews] = useState([]); + const [reviewsLoading, setReviewsLoading] = useState(false); + const [reviewScore, setReviewScore] = useState(null); + const [submittingReview, setSubmittingReview] = useState(false); + const [reviewCharCount, setReviewCharCount] = useState(0); + const [reviewsSortBy, setReviewsSortBy] = + useState("newest"); + const [reviewsPage, setReviewsPage] = useState(0); + const [hasMoreReviews, setHasMoreReviews] = useState(true); + const [visibleBlockedReviews, setVisibleBlockedReviews] = useState< + Set + >(new Set()); + const [totalReviewCount, setTotalReviewCount] = useState(0); + const [showReviewForm, setShowReviewForm] = useState(false); + const [votingReviews, setVotingReviews] = useState>(new Set()); + const [showReviewPrompt, setShowReviewPrompt] = useState(false); + + const previousVotesRef = useRef< + Map + >(new Map()); + const abortControllerRef = useRef(null); + + const editor = useEditor({ + extensions: [ + StarterKit.configure({ + link: false, + }), + ], + content: "", + editorProps: { + attributes: { + class: "game-details__review-editor", + "data-placeholder": t("write_review_placeholder"), + }, + handlePaste: (view, event) => { + const htmlContent = event.clipboardData?.getData("text/html") || ""; + const plainText = event.clipboardData?.getData("text/plain") || ""; + + const currentText = view.state.doc.textContent; + const remainingChars = MAX_REVIEW_CHARS - currentText.length; + + if ((htmlContent || plainText) && remainingChars > 0) { + event.preventDefault(); + + if (htmlContent) { + const tempDiv = document.createElement("div"); + tempDiv.innerHTML = htmlContent; + const textLength = tempDiv.textContent?.length || 0; + + if (textLength <= remainingChars) { + return false; + } + } + + const truncatedText = plainText.slice(0, remainingChars); + view.dispatch(view.state.tr.insertText(truncatedText)); + return true; + } + return false; + }, + }, + onUpdate: ({ editor }) => { + const text = editor.getText(); + setReviewCharCount(text.length); + + if (text.length > MAX_REVIEW_CHARS) { + const truncatedContent = text.slice(0, MAX_REVIEW_CHARS); + editor.commands.setContent(truncatedContent); + setReviewCharCount(MAX_REVIEW_CHARS); + } + }, + }); + + const checkUserReview = useCallback(async () => { + if (!objectId || !userDetailsId) return; + + try { + const response = await window.electron.hydraApi.get<{ + hasReviewed: boolean; + }>(`/games/${shop}/${objectId}/reviews/check`, { + needsAuth: true, + }); + const hasReviewed = response?.hasReviewed || false; + onUserReviewedChange(hasReviewed); + + const twoHoursInMilliseconds = 2 * 60 * 60 * 1000; + const hasEnoughPlaytime = + game && + game.playTimeInMilliseconds >= twoHoursInMilliseconds && + !game.hasManuallyUpdatedPlaytime; + + if ( + !hasReviewed && + hasEnoughPlaytime && + !sessionStorage.getItem(`reviewPromptDismissed_${objectId}`) + ) { + setShowReviewPrompt(true); + setShowReviewForm(true); + } + } catch (error) { + console.error("Failed to check user review:", error); + } + }, [objectId, userDetailsId, shop, game, onUserReviewedChange]); + + const loadReviews = useCallback( + async (reset = false) => { + if (!objectId) return; + + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + } + + const abortController = new AbortController(); + abortControllerRef.current = abortController; + + setReviewsLoading(true); + try { + const skip = reset ? 0 : reviewsPage * 20; + const params = new URLSearchParams({ + take: "20", + skip: skip.toString(), + sortBy: reviewsSortBy, + }); + + const response = await window.electron.hydraApi.get( + `/games/${shop}/${objectId}/reviews?${params.toString()}`, + { needsAuth: false } + ); + + if (abortController.signal.aborted) { + return; + } + + const typedResponse = response as unknown as + | { reviews: GameReview[]; totalCount: number } + | undefined; + const reviewsData = typedResponse?.reviews || []; + const reviewCount = typedResponse?.totalCount || 0; + + if (reset) { + setReviews(reviewsData); + setReviewsPage(0); + setTotalReviewCount(reviewCount); + } else { + setReviews((prev) => [...prev, ...reviewsData]); + } + + setHasMoreReviews(reviewsData.length === 20); + } catch (error) { + if (!abortController.signal.aborted) { + console.error("Failed to load reviews:", error); + } + } finally { + if (!abortController.signal.aborted) { + setReviewsLoading(false); + } + } + }, + [objectId, shop, reviewsPage, reviewsSortBy] + ); + + const handleVoteReview = async ( + reviewId: string, + voteType: "upvote" | "downvote" + ) => { + if (!objectId || votingReviews.has(reviewId)) return; + + setVotingReviews((prev) => new Set(prev).add(reviewId)); + + const reviewIndex = reviews.findIndex((r) => r.id === reviewId); + if (reviewIndex === -1) { + setVotingReviews((prev) => { + const next = new Set(prev); + next.delete(reviewId); + return next; + }); + return; + } + + const review = reviews[reviewIndex]; + const originalReview = { ...review }; + + const updatedReviews = [...reviews]; + const updatedReview = { ...review }; + + if (voteType === "upvote") { + if (review.hasUpvoted) { + updatedReview.hasUpvoted = false; + updatedReview.upvotes = Math.max(0, (review.upvotes || 0) - 1); + } else { + updatedReview.hasUpvoted = true; + updatedReview.upvotes = (review.upvotes || 0) + 1; + + if (review.hasDownvoted) { + updatedReview.hasDownvoted = false; + updatedReview.downvotes = Math.max(0, (review.downvotes || 0) - 1); + } + } + } else { + if (review.hasDownvoted) { + updatedReview.hasDownvoted = false; + updatedReview.downvotes = Math.max(0, (review.downvotes || 0) - 1); + } else { + updatedReview.hasDownvoted = true; + updatedReview.downvotes = (review.downvotes || 0) + 1; + + if (review.hasUpvoted) { + updatedReview.hasUpvoted = false; + updatedReview.upvotes = Math.max(0, (review.upvotes || 0) - 1); + } + } + } + + updatedReviews[reviewIndex] = updatedReview; + setReviews(updatedReviews); + + try { + await window.electron.hydraApi.put( + `/games/${shop}/${objectId}/reviews/${reviewId}/${voteType}`, + { data: {} } + ); + } catch (error) { + console.error(`Failed to ${voteType} review:`, error); + + const rolledBackReviews = [...reviews]; + rolledBackReviews[reviewIndex] = originalReview; + setReviews(rolledBackReviews); + + showErrorToast(t("vote_failed")); + } finally { + setTimeout(() => { + setVotingReviews((prev) => { + const next = new Set(prev); + next.delete(reviewId); + return next; + }); + }, 500); + } + }; + + const handleDeleteReview = async (reviewId: string) => { + if (!objectId) return; + + try { + await window.electron.hydraApi.delete( + `/games/${shop}/${objectId}/reviews/${reviewId}` + ); + loadReviews(true); + onUserReviewedChange(false); + setShowReviewForm(true); + showSuccessToast(t("review_deleted_successfully")); + } catch (error) { + console.error("Failed to delete review:", error); + showErrorToast(t("review_deletion_failed")); + } + }; + + const handleSubmitReview = async () => { + const reviewHtml = editor?.getHTML() || ""; + const reviewText = editor?.getText() || ""; + + if (!objectId) return; + + if (!reviewText.trim()) { + showErrorToast(t("review_cannot_be_empty")); + return; + } + + if (submittingReview || reviewCharCount > MAX_REVIEW_CHARS) { + return; + } + + if (reviewScore === null) { + return; + } + + setSubmittingReview(true); + + try { + await window.electron.hydraApi.post( + `/games/${shop}/${objectId}/reviews`, + { + data: { + reviewHtml, + score: reviewScore, + }, + } + ); + + editor?.commands.clearContent(); + setReviewScore(null); + showSuccessToast(t("review_submitted_successfully")); + + await loadReviews(true); + setShowReviewForm(false); + setShowReviewPrompt(false); + onUserReviewedChange(true); + } catch (error) { + console.error("Failed to submit review:", error); + showErrorToast(t("review_submission_failed")); + } finally { + setSubmittingReview(false); + } + }; + + const handleReviewPromptYes = () => { + setShowReviewPrompt(false); + + setTimeout(() => { + const reviewFormElement = document.querySelector( + ".game-details__review-form" + ); + if (reviewFormElement) { + reviewFormElement.scrollIntoView({ + behavior: "smooth", + block: "start", + }); + } + }, 100); + }; + + const handleReviewPromptLater = () => { + setShowReviewPrompt(false); + setShowReviewForm(false); + if (objectId) { + sessionStorage.setItem(`reviewPromptDismissed_${objectId}`, "true"); + } + }; + + const handleSortChange = (newSortBy: ReviewSortOption) => { + if (newSortBy !== reviewsSortBy) { + setReviewsSortBy(newSortBy); + setReviewsPage(0); + setHasMoreReviews(true); + loadReviews(true); + } + }; + + const toggleBlockedReview = (reviewId: string) => { + setVisibleBlockedReviews((prev) => { + const newSet = new Set(prev); + if (newSet.has(reviewId)) { + newSet.delete(reviewId); + } else { + newSet.add(reviewId); + } + return newSet; + }); + }; + + const loadMoreReviews = () => { + if (!reviewsLoading && hasMoreReviews) { + setReviewsPage((prev) => prev + 1); + loadReviews(false); + } + }; + + const handleVoteAnimationComplete = ( + reviewId: string, + votes: { upvotes: number; downvotes: number } + ) => { + previousVotesRef.current.set(reviewId, votes); + }; + + useEffect(() => { + if (objectId) { + loadReviews(true); + if (userDetailsId) { + checkUserReview(); + } + } + + return () => { + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + abortControllerRef.current = null; + } + }; + }, [objectId, userDetailsId, checkUserReview, loadReviews]); + + useEffect(() => { + if (reviewsPage > 0) { + loadReviews(false); + } + }, [reviewsPage, loadReviews]); + + useEffect(() => { + reviews.forEach((review) => { + if (!previousVotesRef.current.has(review.id)) { + previousVotesRef.current.set(review.id, { + upvotes: review.upvotes || 0, + downvotes: review.downvotes || 0, + }); + } + }); + }, [reviews]); + + return ( +
    + {showReviewPrompt && + userDetailsId && + !hasUserReviewed && + isGameInLibrary && ( + + )} + + {showReviewForm && ( + <> + +
    + + )} + +
    +
    +
    +

    {t("reviews")}

    + + {totalReviewCount} + +
    +
    + + + {reviewsLoading && reviews.length === 0 && ( +
    + {t("loading_reviews")} +
    + )} + + {!reviewsLoading && reviews.length === 0 && ( +
    +
    + +
    +

    + {t("no_reviews_yet")} +

    +

    + {t("be_first_to_review")} +

    +
    + )} + +
    0 ? 0.5 : 1, + transition: "opacity 0.2s ease", + }} + > + {reviews.map((review) => ( + + ))} +
    + + {hasMoreReviews && !reviewsLoading && ( + + )} + + {reviewsLoading && reviews.length > 0 && ( +
    + {t("loading_more_reviews")} +
    + )} +
    +
    + ); +} diff --git a/src/renderer/src/pages/game-details/review-form.tsx b/src/renderer/src/pages/game-details/review-form.tsx new file mode 100644 index 00000000..2cc83a19 --- /dev/null +++ b/src/renderer/src/pages/game-details/review-form.tsx @@ -0,0 +1,149 @@ +import { Star } from "lucide-react"; +import { useTranslation } from "react-i18next"; +import { EditorContent, Editor } from "@tiptap/react"; +import { Button } from "@renderer/components"; + +interface ReviewFormProps { + editor: Editor | null; + reviewScore: number | null; + reviewCharCount: number; + maxReviewChars: number; + submittingReview: boolean; + onScoreChange: (score: number) => void; + onSubmit: () => void; +} + +const getSelectScoreColorClass = (score: number): string => { + if (score >= 1 && score <= 2) return "game-details__review-score-select--red"; + if (score >= 3 && score <= 3) + return "game-details__review-score-select--yellow"; + if (score >= 4 && score <= 5) + return "game-details__review-score-select--green"; + return ""; +}; + +const getRatingText = (score: number, t: (key: string) => string): string => { + switch (score) { + case 1: + return t("rating_very_negative"); + case 2: + return t("rating_negative"); + case 3: + return t("rating_neutral"); + case 4: + return t("rating_positive"); + case 5: + return t("rating_very_positive"); + default: + return ""; + } +}; + +export function ReviewForm({ + editor, + reviewScore, + reviewCharCount, + maxReviewChars, + submittingReview, + onScoreChange, + onSubmit, +}: Readonly) { + const { t } = useTranslation("game_details"); + + return ( + <> +
    +

    {t("leave_a_review")}

    +
    + +
    +
    +
    +
    + + + +
    +
    + maxReviewChars ? "over-limit" : ""} + > + {reviewCharCount}/{maxReviewChars} + +
    +
    +
    + +
    +
    + +
    +
    +
    + {[1, 2, 3, 4, 5].map((starValue) => ( + + ))} +
    +
    + + +
    +
    + + ); +} diff --git a/src/renderer/src/pages/game-details/review-item.tsx b/src/renderer/src/pages/game-details/review-item.tsx new file mode 100644 index 00000000..85c81e70 --- /dev/null +++ b/src/renderer/src/pages/game-details/review-item.tsx @@ -0,0 +1,264 @@ +import { TrashIcon, ClockIcon } from "@primer/octicons-react"; +import { ThumbsUp, ThumbsDown, Star } from "lucide-react"; +import { useNavigate } from "react-router-dom"; +import { motion, AnimatePresence } from "framer-motion"; +import { useTranslation } from "react-i18next"; +import type { GameReview } from "@types"; + +import { sanitizeHtml } from "@shared"; +import { useDate } from "@renderer/hooks"; +import { formatNumber } from "@renderer/helpers"; +import { Avatar } from "@renderer/components"; + +interface ReviewItemProps { + review: GameReview; + userDetailsId?: string; + isBlocked: boolean; + isVisible: boolean; + isVoting: boolean; + previousVotes: { upvotes: number; downvotes: number }; + onVote: (reviewId: string, voteType: "upvote" | "downvote") => void; + onDelete: (reviewId: string) => void; + onToggleVisibility: (reviewId: string) => void; + onAnimationComplete: ( + reviewId: string, + votes: { upvotes: number; downvotes: number } + ) => void; +} + +const getScoreColorClass = (score: number): string => { + if (score >= 1 && score <= 2) return "game-details__review-score--red"; + if (score >= 3 && score <= 3) return "game-details__review-score--yellow"; + if (score >= 4 && score <= 5) return "game-details__review-score--green"; + return ""; +}; + +const getRatingText = (score: number, t: (key: string) => string): string => { + switch (score) { + case 1: + return t("rating_very_negative"); + case 2: + return t("rating_negative"); + case 3: + return t("rating_neutral"); + case 4: + return t("rating_positive"); + case 5: + return t("rating_very_positive"); + default: + return ""; + } +}; + +export function ReviewItem({ + review, + userDetailsId, + isBlocked, + isVisible, + isVoting, + previousVotes, + onVote, + onDelete, + onToggleVisibility, + onAnimationComplete, +}: Readonly) { + const navigate = useNavigate(); + const { t } = useTranslation("game_details"); + const { formatDistance } = useDate(); + + if (isBlocked && !isVisible) { + return ( +
    +
    + Review from blocked user —{" "} + +
    +
    + ); + } + + return ( +
    +
    +
    + +
    + +
    + + {formatDistance(new Date(review.createdAt), new Date(), { + addSuffix: true, + })} +
    +
    +
    +
    + {[1, 2, 3, 4, 5].map((starValue) => ( + + ))} +
    +
    +
    +
    +
    + onVote(review.id, "upvote")} + disabled={isVoting} + style={{ + opacity: isVoting ? 0.5 : 1, + cursor: isVoting ? "not-allowed" : "pointer", + }} + animate={ + review.hasUpvoted + ? { + scale: [1, 1.2, 1], + transition: { duration: 0.3 }, + } + : {} + } + > + + + previousVotes.upvotes} + variants={{ + enter: (isIncreasing: boolean) => ({ + y: isIncreasing ? 10 : -10, + opacity: 0, + }), + center: { y: 0, opacity: 1 }, + exit: (isIncreasing: boolean) => ({ + y: isIncreasing ? -10 : 10, + opacity: 0, + }), + }} + initial="enter" + animate="center" + exit="exit" + transition={{ duration: 0.2 }} + onAnimationComplete={() => { + onAnimationComplete(review.id, { + upvotes: review.upvotes || 0, + downvotes: review.downvotes || 0, + }); + }} + > + {formatNumber(review.upvotes || 0)} + + + + onVote(review.id, "downvote")} + disabled={isVoting} + style={{ + opacity: isVoting ? 0.5 : 1, + cursor: isVoting ? "not-allowed" : "pointer", + }} + animate={ + review.hasDownvoted + ? { + scale: [1, 1.2, 1], + transition: { duration: 0.3 }, + } + : {} + } + > + + + previousVotes.downvotes} + variants={{ + enter: (isIncreasing: boolean) => ({ + y: isIncreasing ? 10 : -10, + opacity: 0, + }), + center: { y: 0, opacity: 1 }, + exit: (isIncreasing: boolean) => ({ + y: isIncreasing ? -10 : 10, + opacity: 0, + }), + }} + initial="enter" + animate="center" + exit="exit" + transition={{ duration: 0.2 }} + onAnimationComplete={() => { + onAnimationComplete(review.id, { + upvotes: review.upvotes || 0, + downvotes: review.downvotes || 0, + }); + }} + > + {formatNumber(review.downvotes || 0)} + + + +
    + {userDetailsId === review.user.id && ( + + )} + {isBlocked && isVisible && ( + + )} +
    +
    + ); +} diff --git a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx index df1429ec..d16f4d3f 100755 --- a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx +++ b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx @@ -91,10 +91,11 @@ export function Sidebar() { }); } else { try { - const howLongToBeat = await window.electron.getHowLongToBeat( - objectId, - shop - ); + const howLongToBeat = await window.electron.hydraApi.get< + HowLongToBeatCategory[] | null + >(`/games/${shop}/${objectId}/how-long-to-beat`, { + needsAuth: false, + }); if (howLongToBeat) { howLongToBeatEntriesTable.add({ diff --git a/src/renderer/src/pages/home/home.tsx b/src/renderer/src/pages/home/home.tsx index 0b762882..40bf181d 100644 --- a/src/renderer/src/pages/home/home.tsx +++ b/src/renderer/src/pages/home/home.tsx @@ -40,7 +40,15 @@ export default function Home() { setCurrentCatalogueCategory(category); setIsLoading(true); - const catalogue = await window.electron.getCatalogue(category); + const params = new URLSearchParams({ + take: "12", + skip: "0", + }); + + const catalogue = await window.electron.hydraApi.get( + `/catalogue/${category}?${params.toString()}`, + { needsAuth: false } + ); setCatalogue((prev) => ({ ...prev, [category]: catalogue })); } finally { diff --git a/src/renderer/src/pages/profile/report-profile/report-profile.tsx b/src/renderer/src/pages/profile/report-profile/report-profile.tsx index 40084aba..67b7a72a 100644 --- a/src/renderer/src/pages/profile/report-profile/report-profile.tsx +++ b/src/renderer/src/pages/profile/report-profile/report-profile.tsx @@ -54,8 +54,13 @@ export function ReportProfile() { const onSubmit = useCallback( async (values: FormValues) => { - return window.electron - .reportUser(userProfile!.id, values.reason, values.description) + return window.electron.hydraApi + .post(`/users/${userProfile!.id}/report`, { + data: { + reason: values.reason, + description: values.description, + }, + }) .then(() => { showSuccessToast(t("profile_reported")); setShowReportProfileModal(false); diff --git a/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal-list.tsx b/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal-list.tsx index 610816fa..6ae91cfd 100644 --- a/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal-list.tsx +++ b/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal-list.tsx @@ -34,8 +34,13 @@ export const UserFriendModalList = ({ const loadNextPage = () => { if (page > maxPage) return; setIsLoading(true); - window.electron - .getUserFriends(userId, pageSize, page * pageSize) + + const url = isMe ? "/profile/friends" : `/users/${userId}/friends`; + + window.electron.hydraApi + .get<{ totalFriends: number; friends: UserFriend[] }>(url, { + params: { take: pageSize, skip: page * pageSize }, + }) .then((newPage) => { if (page === 0) { setMaxPage(newPage.totalFriends / pageSize); diff --git a/src/types/index.ts b/src/types/index.ts index 6a864f3a..b4d8044a 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -254,7 +254,7 @@ export interface GameReview { id: string; displayName: string; profileImageUrl: string | null; - } | null; + }; } export interface TrendingGame extends ShopAssets { diff --git a/yarn.lock b/yarn.lock index 71551858..2a610688 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7301,9 +7301,9 @@ ms@^2.0.0, ms@^2.1.1, ms@^2.1.3: integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== nan@^2.18.0: - version "2.22.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.22.0.tgz#31bc433fc33213c97bad36404bb68063de604de3" - integrity sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw== + version "2.23.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.23.0.tgz#24aa4ddffcc37613a2d2935b97683c1ec96093c6" + integrity sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ== nanoid@^3.3.7: version "3.3.7" From 25758a540fffaea40a7816df6f7a7d7e82a7209f Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Mon, 13 Oct 2025 17:45:51 +0100 Subject: [PATCH 063/197] ci: adding dockerfile --- src/main/services/window-manager.ts | 6 ++---- src/renderer/src/app.tsx | 2 +- .../src/pages/settings/aparence/modals/add-theme-modal.tsx | 2 +- .../pages/settings/aparence/modals/import-theme-modal.tsx | 2 +- .../src/pages/settings/settings-download-sources.tsx | 4 ++-- 5 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index b794529f..bb7142ba 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -58,12 +58,10 @@ export class WindowManager { // HMR for renderer base on electron-vite cli. // Load the remote URL for development or the local html file for production. if (is.dev && process.env["ELECTRON_RENDERER_URL"]) { - this.mainWindow?.loadURL( - `${process.env["ELECTRON_RENDERER_URL"]}#/${hash}` - ); + this.mainWindow?.loadURL(`http://192.168.1.159:8001#/${hash}`); } else { this.mainWindow - ?.loadURL(`https://staging.hydralauncher.gg/#/${hash}`) + ?.loadURL(`http://192.168.1.159:8001/#/${hash}`) .catch(() => { this.mainWindow?.loadFile( path.join(__dirname, "../renderer/index.html"), diff --git a/src/renderer/src/app.tsx b/src/renderer/src/app.tsx index 7cd6eaa4..b829c239 100644 --- a/src/renderer/src/app.tsx +++ b/src/renderer/src/app.tsx @@ -212,7 +212,7 @@ export function App() { useEffect(() => { updateRepacks(); - const id = crypto.randomUUID(); + const id = `${Date.now()}-${Math.random()}`; const channel = new BroadcastChannel(`download_sources:sync:${id}`); channel.onmessage = async (event: MessageEvent) => { diff --git a/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx b/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx index 58bcf58d..9cc1c1ad 100644 --- a/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx +++ b/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx @@ -79,7 +79,7 @@ export function AddThemeModal({ const onSubmit = useCallback( async (values: FormValues) => { const theme: Theme = { - id: crypto.randomUUID(), + id: `${Date.now()}-${Math.random()}`, name: values.name, isActive: false, author: userDetails?.id, diff --git a/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx b/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx index 601e9568..9d6a832b 100644 --- a/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx +++ b/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx @@ -30,7 +30,7 @@ export const ImportThemeModal = ({ const handleImportTheme = async () => { const theme: Theme = { - id: crypto.randomUUID(), + id: `${Date.now()}-${Math.random()}`, name: themeName, isActive: false, author: authorId, diff --git a/src/renderer/src/pages/settings/settings-download-sources.tsx b/src/renderer/src/pages/settings/settings-download-sources.tsx index fa30dfa1..f338fc07 100644 --- a/src/renderer/src/pages/settings/settings-download-sources.tsx +++ b/src/renderer/src/pages/settings/settings-download-sources.tsx @@ -95,7 +95,7 @@ export function SettingsDownloadSources() { const handleRemoveAllDownloadSources = () => { setIsRemovingDownloadSource(true); - const id = crypto.randomUUID(); + const id = `${Date.now()}-${Math.random()}`; const channel = new BroadcastChannel(`download_sources:delete_all:${id}`); downloadSourcesWorker.postMessage(["DELETE_ALL_DOWNLOAD_SOURCES", id]); @@ -120,7 +120,7 @@ export function SettingsDownloadSources() { const syncDownloadSources = async () => { setIsSyncingDownloadSources(true); - const id = crypto.randomUUID(); + const id = `${Date.now()}-${Math.random()}`; const channel = new BroadcastChannel(`download_sources:sync:${id}`); downloadSourcesWorker.postMessage(["SYNC_DOWNLOAD_SOURCES", id]); From 53d81018e9be5ad0c08122b67d2432263344043e Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Mon, 13 Oct 2025 18:05:48 +0100 Subject: [PATCH 064/197] feat: adding uuid library --- Dockerfile | 9 --------- package.json | 1 + src/renderer/src/app.tsx | 3 ++- src/renderer/src/helpers.ts | 9 +++++++++ .../pages/settings/aparence/modals/add-theme-modal.tsx | 3 ++- .../settings/aparence/modals/import-theme-modal.tsx | 8 ++++++-- .../src/pages/settings/settings-download-sources.tsx | 5 +++-- yarn.lock | 5 +++++ 8 files changed, 28 insertions(+), 15 deletions(-) diff --git a/Dockerfile b/Dockerfile index 533be1a9..aefa3070 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,12 +13,3 @@ RUN yarn install --frozen-lockfile --ignore-scripts COPY . . RUN yarn electron-vite build - -FROM nginx:alpine - -COPY --from=builder /app/out/renderer /usr/share/nginx/html - -EXPOSE 80 - -CMD ["nginx", "-g", "daemon off;"] - diff --git a/package.json b/package.json index b81e6d15..2e3dd7a5 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,7 @@ "tar": "^7.4.3", "tough-cookie": "^5.1.1", "user-agents": "^1.1.387", + "uuid": "^13.0.0", "winreg": "^1.2.5", "ws": "^8.18.1", "yaml": "^2.6.1", diff --git a/src/renderer/src/app.tsx b/src/renderer/src/app.tsx index b829c239..0a8ee1e2 100644 --- a/src/renderer/src/app.tsx +++ b/src/renderer/src/app.tsx @@ -27,6 +27,7 @@ import { downloadSourcesWorker } from "./workers"; import { downloadSourcesTable } from "./dexie"; import { useSubscription } from "./hooks/use-subscription"; import { HydraCloudModal } from "./pages/shared-modals/hydra-cloud/hydra-cloud-modal"; +import { generateUUID } from "./helpers"; import { injectCustomCss, removeCustomCss } from "./helpers"; import "./app.scss"; @@ -212,7 +213,7 @@ export function App() { useEffect(() => { updateRepacks(); - const id = `${Date.now()}-${Math.random()}`; + const id = generateUUID(); const channel = new BroadcastChannel(`download_sources:sync:${id}`); channel.onmessage = async (event: MessageEvent) => { diff --git a/src/renderer/src/helpers.ts b/src/renderer/src/helpers.ts index 41b783bc..f09cec84 100644 --- a/src/renderer/src/helpers.ts +++ b/src/renderer/src/helpers.ts @@ -1,6 +1,7 @@ import type { GameShop } from "@types"; import Color from "color"; +import { v4 as uuidv4 } from "uuid"; import { THEME_WEB_STORE_URL } from "./constants"; export const formatDownloadProgress = ( @@ -112,3 +113,11 @@ export const formatNumber = (num: number): string => { maximumFractionDigits: 1, }).format(num); }; + +/** + * Generates a UUID v4 + * @returns A random UUID string + */ +export const generateUUID = (): string => { + return uuidv4(); +}; diff --git a/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx b/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx index 9cc1c1ad..522d8546 100644 --- a/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx +++ b/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx @@ -9,6 +9,7 @@ import { useForm } from "react-hook-form"; import * as yup from "yup"; import { yupResolver } from "@hookform/resolvers/yup"; import { useCallback } from "react"; +import { generateUUID } from "@renderer/helpers"; import "./modals.scss"; @@ -79,7 +80,7 @@ export function AddThemeModal({ const onSubmit = useCallback( async (values: FormValues) => { const theme: Theme = { - id: `${Date.now()}-${Math.random()}`, + id: generateUUID(), name: values.name, isActive: false, author: userDetails?.id, diff --git a/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx b/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx index 9d6a832b..516f320f 100644 --- a/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx +++ b/src/renderer/src/pages/settings/aparence/modals/import-theme-modal.tsx @@ -3,7 +3,11 @@ import { Modal } from "@renderer/components/modal/modal"; import { useTranslation } from "react-i18next"; import "./modals.scss"; import { Theme } from "@types"; -import { injectCustomCss, removeCustomCss } from "@renderer/helpers"; +import { + injectCustomCss, + removeCustomCss, + generateUUID, +} from "@renderer/helpers"; import { useToast } from "@renderer/hooks"; import { THEME_WEB_STORE_URL } from "@renderer/constants"; import { logger } from "@renderer/logger"; @@ -30,7 +34,7 @@ export const ImportThemeModal = ({ const handleImportTheme = async () => { const theme: Theme = { - id: `${Date.now()}-${Math.random()}`, + id: generateUUID(), name: themeName, isActive: false, author: authorId, diff --git a/src/renderer/src/pages/settings/settings-download-sources.tsx b/src/renderer/src/pages/settings/settings-download-sources.tsx index f338fc07..720a313b 100644 --- a/src/renderer/src/pages/settings/settings-download-sources.tsx +++ b/src/renderer/src/pages/settings/settings-download-sources.tsx @@ -23,6 +23,7 @@ import { downloadSourcesTable } from "@renderer/dexie"; import { downloadSourcesWorker } from "@renderer/workers"; import { useNavigate } from "react-router-dom"; import { setFilters, clearFilters } from "@renderer/features"; +import { generateUUID } from "@renderer/helpers"; import "./settings-download-sources.scss"; export function SettingsDownloadSources() { @@ -95,7 +96,7 @@ export function SettingsDownloadSources() { const handleRemoveAllDownloadSources = () => { setIsRemovingDownloadSource(true); - const id = `${Date.now()}-${Math.random()}`; + const id = generateUUID(); const channel = new BroadcastChannel(`download_sources:delete_all:${id}`); downloadSourcesWorker.postMessage(["DELETE_ALL_DOWNLOAD_SOURCES", id]); @@ -120,7 +121,7 @@ export function SettingsDownloadSources() { const syncDownloadSources = async () => { setIsSyncingDownloadSources(true); - const id = `${Date.now()}-${Math.random()}`; + const id = generateUUID(); const channel = new BroadcastChannel(`download_sources:sync:${id}`); downloadSourcesWorker.postMessage(["SYNC_DOWNLOAD_SOURCES", id]); diff --git a/yarn.lock b/yarn.lock index 2a610688..06cf613d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9479,6 +9479,11 @@ util-deprecate@^1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== +uuid@^13.0.0: + version "13.0.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-13.0.0.tgz#263dc341b19b4d755eb8fe36b78d95a6b65707e8" + integrity sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w== + uuid@^9.0.1: version "9.0.1" resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" From fc541daaed7514cc5ccb9e8017dd0ec067cda0dc Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Mon, 13 Oct 2025 18:08:41 +0100 Subject: [PATCH 065/197] feat: adding uuid library --- Dockerfile | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Dockerfile b/Dockerfile index aefa3070..4ed800b3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,3 +13,11 @@ RUN yarn install --frozen-lockfile --ignore-scripts COPY . . RUN yarn electron-vite build + +FROM nginx:alpine + +COPY --from=builder /app/out/renderer /usr/share/nginx/html + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] From 5e59e1a7d11772fc38ce11f361d98fc701e23f6b Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Mon, 13 Oct 2025 18:12:07 +0100 Subject: [PATCH 066/197] feat: adding uuid library --- src/main/services/window-manager.ts | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index bb7142ba..70ff130e 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -58,18 +58,16 @@ export class WindowManager { // HMR for renderer base on electron-vite cli. // Load the remote URL for development or the local html file for production. if (is.dev && process.env["ELECTRON_RENDERER_URL"]) { - this.mainWindow?.loadURL(`http://192.168.1.159:8001#/${hash}`); + this.mainWindow?.loadURL( + `${process.env["ELECTRON_RENDERER_URL"]}#/${hash}` + ); } else { - this.mainWindow - ?.loadURL(`http://192.168.1.159:8001/#/${hash}`) - .catch(() => { - this.mainWindow?.loadFile( - path.join(__dirname, "../renderer/index.html"), - { - hash, - } - ); - }); + this.mainWindow?.loadFile( + path.join(__dirname, "../renderer/index.html"), + { + hash, + } + ); } } From 621adbb1ab67f666ca1379a80baa1e309f2615f7 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Mon, 13 Oct 2025 22:51:53 +0100 Subject: [PATCH 067/197] ci: testing build --- .github/workflows/build-renderer.yml | 36 ++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 .github/workflows/build-renderer.yml diff --git a/.github/workflows/build-renderer.yml b/.github/workflows/build-renderer.yml new file mode 100644 index 00000000..b5fb3164 --- /dev/null +++ b/.github/workflows/build-renderer.yml @@ -0,0 +1,36 @@ +name: Build + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: push + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Check out Git repository + uses: actions/checkout@v4 + + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version: 20.18.0 + + - name: Install dependencies + run: yarn --frozen-lockfile --ignore-scripts + + - name: Build Renderer + run: electron-vite build + env: + RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.EXTERNAL_RESOURCES_URL }} + + - name: Deploy with wrangler + uses: cloudflare/wrangler-action@v3 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + command: pages deploy out/renderer --project-name=hydra-staging + gitHubToken: ${{ secrets.GITHUB_TOKEN }} From 59d4545476342b22375be0d37320e210902fbfd7 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Mon, 13 Oct 2025 22:54:15 +0100 Subject: [PATCH 068/197] ci: testing build --- .github/workflows/build-renderer.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-renderer.yml b/.github/workflows/build-renderer.yml index b5fb3164..9537f3cd 100644 --- a/.github/workflows/build-renderer.yml +++ b/.github/workflows/build-renderer.yml @@ -1,4 +1,4 @@ -name: Build +name: Build Renderer concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -23,7 +23,7 @@ jobs: run: yarn --frozen-lockfile --ignore-scripts - name: Build Renderer - run: electron-vite build + run: yarn build env: RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.EXTERNAL_RESOURCES_URL }} From a8bbb7619095bedb8a0bccca9672f38eb52da329 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Mon, 13 Oct 2025 22:59:09 +0100 Subject: [PATCH 069/197] ci: testing build --- .github/workflows/build-renderer.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-renderer.yml b/.github/workflows/build-renderer.yml index 9537f3cd..8e1d0bc6 100644 --- a/.github/workflows/build-renderer.yml +++ b/.github/workflows/build-renderer.yml @@ -27,10 +27,11 @@ jobs: env: RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.EXTERNAL_RESOURCES_URL }} - - name: Deploy with wrangler - uses: cloudflare/wrangler-action@v3 - with: - apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} - accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - command: pages deploy out/renderer --project-name=hydra-staging - gitHubToken: ${{ secrets.GITHUB_TOKEN }} + - name: Deploy to Cloudflare Pages + run: | + npx --yes wrangler@3 pages deploy out/renderer \ + --project-name=hydra-staging \ + --commit-dirty=true + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} From e1ee3a47d67e0f6ae23d1a562712143b5c0b55ed Mon Sep 17 00:00:00 2001 From: whintersnow0 Date: Mon, 13 Oct 2025 23:41:08 +0200 Subject: [PATCH 070/197] fix: add missing uk translations --- src/locales/uk/translation.json | 234 ++++++++++++++++++++++++++++++-- 1 file changed, 221 insertions(+), 13 deletions(-) diff --git a/src/locales/uk/translation.json b/src/locales/uk/translation.json index 26aa8aae..cd8395da 100644 --- a/src/locales/uk/translation.json +++ b/src/locales/uk/translation.json @@ -27,7 +27,68 @@ "favorites": "Улюблені", "friends": "Друзі", "need_help": "Потрібна допомога?", - "playable_button_title": "Показати лише ігри, які можна грати зараз" + "playable_button_title": "Показати лише ігри, які можна грати зараз", + "add_custom_game_tooltip": "Додати власну гру", + "show_playable_only_tooltip": "Показати лише доступні для гри", + "custom_game_modal": "Додати власну гру", + "custom_game_modal_description": "Додайте власну гру до бібліотеки, вибравши виконуваний файл", + "custom_game_modal_executable_path": "Шлях до виконуваного файлу", + "custom_game_modal_select_executable": "Виберіть виконуваний файл", + "custom_game_modal_title": "Назва гри", + "custom_game_modal_enter_title": "Введіть назву гри", + "custom_game_modal_browse": "Огляд", + "custom_game_modal_cancel": "Скасувати", + "custom_game_modal_add": "Додати гру", + "custom_game_modal_adding": "Додавання гри...", + "custom_game_modal_success": "Власну гру успішно додано", + "custom_game_modal_failed": "Не вдалося додати власну гру", + "custom_game_modal_executable": "Виконуваний файл", + "edit_game_modal": "Налаштувати ресурси", + "edit_game_modal_description": "Налаштуйте ресурси та деталі гри", + "edit_game_modal_title": "Назва", + "edit_game_modal_enter_title": "Введіть назву", + "edit_game_modal_image": "Зображення", + "edit_game_modal_select_image": "Виберіть зображення", + "edit_game_modal_browse": "Огляд", + "edit_game_modal_image_preview": "Попередній перегляд зображення", + "edit_game_modal_icon": "Іконка", + "edit_game_modal_select_icon": "Виберіть іконку", + "edit_game_modal_icon_preview": "Попередній перегляд іконки", + "edit_game_modal_logo": "Логотип", + "edit_game_modal_select_logo": "Виберіть логотип", + "edit_game_modal_logo_preview": "Попередній перегляд логотипу", + "edit_game_modal_hero": "Зображення обкладинки гри", + "edit_game_modal_select_hero": "Виберіть обкладинку гри", + "edit_game_modal_hero_preview": "Попередній перегляд обкладинки гри", + "edit_game_modal_cancel": "Скасувати", + "edit_game_modal_update": "Оновити", + "edit_game_modal_updating": "Оновлення...", + "edit_game_modal_fill_required": "Будь ласка, заповніть всі обов'язкові поля", + "edit_game_modal_success": "Ресурси успішно оновлено", + "edit_game_modal_failed": "Не вдалося оновити ресурси", + "edit_game_modal_image_filter": "Зображення", + "edit_game_modal_icon_resolution": "Рекомендована роздільна здатність: 256x256px", + "edit_game_modal_logo_resolution": "Рекомендована роздільна здатність: 640x360px", + "edit_game_modal_hero_resolution": "Рекомендована роздільна здатність: 1920x620px", + "edit_game_modal_assets": "Ресурси", + "edit_game_modal_drop_icon_image_here": "Перетягніть зображення іконки сюди", + "edit_game_modal_drop_logo_image_here": "Перетягніть зображення логотипу сюди", + "edit_game_modal_drop_hero_image_here": "Перетягніть зображення обкладинки сюди", + "edit_game_modal_drop_to_replace_icon": "Перетягніть для заміни іконки", + "edit_game_modal_drop_to_replace_logo": "Перетягніть для заміни логотипу", + "edit_game_modal_drop_to_replace_hero": "Перетягніть для заміни обкладинки", + "install_decky_plugin": "Встановити плагін Decky", + "update_decky_plugin": "Оновити плагін Decky", + "decky_plugin_installed_version": "Плагін Decky (v{{version}})", + "install_decky_plugin_title": "Встановити плагін Hydra Decky", + "install_decky_plugin_message": "Це завантажить і встановить плагін Hydra для Decky Loader. Можуть знадобитися підвищені дозволи. Продовжити?", + "update_decky_plugin_title": "Оновити плагін Hydra Decky", + "update_decky_plugin_message": "Доступна нова версія плагіна Hydra Decky. Бажаєте оновити його зараз?", + "decky_plugin_installed": "Плагін Decky v{{version}} успішно встановлено", + "decky_plugin_installation_failed": "Не вдалося встановити плагін Decky: {{error}}", + "decky_plugin_installation_error": "Помилка встановлення плагіна Decky: {{error}}", + "confirm": "Підтвердити", + "cancel": "Скасувати" }, "header": { "search": "Пошук", @@ -86,6 +147,7 @@ "amount_minutes": "{{amount}} хвилин", "accuracy": "{{accuracy}}% точність", "add_to_library": "Додати до бібліотеки", + "already_in_library": "Вже в бібліотеці", "remove_from_library": "Видалити з бібліотеки", "no_downloads": "Немає доступних завантажень", "play_time": "Час гри: {{amount}}", @@ -102,6 +164,7 @@ "download_now": "Завантажити зараз", "calculating_eta": "Обчислення залишкового часу…", "create_shortcut": "Створити ярлик на робочому столі", + "create_shortcut_simple": "Створити ярлик", "create_shortcut_success": "Ярлик успішно створено", "create_shortcut_error": "Виникла помилка під час створення ярлику", "nsfw_content_title": "Ця гра містить неприйнятний контент", @@ -135,6 +198,7 @@ "open_folder": "Відкрити папку", "open_screenshot": "Відкрити скріншот", "options": "Налаштування", + "properties": "Властивості", "paused": "Призупинено", "previous_screenshot": "Попередній скріншот", "remove_files": "Видалити файли", @@ -171,7 +235,7 @@ "loading_save_preview": "Виконується пошук збережень гри...", "wine_prefix": "Префікс Wine", "wine_prefix_description": "Префікс Wine використовувався для запуску цієї гри", - "launch_options": "Параметри загрузки", + "launch_options": "Параметри завантаження", "launch_options_description": "Досвідчені користувачі можуть ввести власні модифікації до параметрів запуску (експериментальна функція).", "launch_options_placeholder": "Параметри не вказано", "no_download_option_info": "Немає інформації", @@ -198,11 +262,105 @@ "download_error_not_cached_on_hydra": "Це завантаження недоступне через Nimbus.", "game_removed_from_favorites": "Гра видалена з улюбленних", "game_added_to_favorites": "Гра була добавлена у улюблені", - "automatically_extract_downloaded_files": "Автоматично розархівувати завантаженні файли" + "automatically_extract_downloaded_files": "Автоматично розархівувати завантаженні файли", + "create_steam_shortcut": "Створити ярлик Steam", + "you_might_need_to_restart_steam": "Можливо, вам знадобиться перезапустити Steam, щоб побачити зміни", + "add_to_favorites": "Додати до улюбленого", + "remove_from_favorites": "Видалити з улюбленого", + "failed_update_favorites": "Не вдалося оновити улюблене", + "game_removed_from_library": "Гру видалено з бібліотеки", + "failed_remove_from_library": "Не вдалося видалити з бібліотеки", + "files_removed_success": "Файли успішно видалено", + "failed_remove_files": "Не вдалося видалити файли", + "show_more": "Показати більше", + "show_less": "Показати менше", + "reviews": "Відгуки", + "leave_a_review": "Залишити відгук", + "write_review_placeholder": "Поділіться своїми думками про цю гру...", + "sort_newest": "Спочатку нові", + "no_reviews_yet": "Поки що немає відгуків", + "be_first_to_review": "Станьте першим, хто поділиться своїми думками про цю гру!", + "sort_oldest": "Спочатку старі", + "sort_highest_score": "Найвища оцінка", + "sort_lowest_score": "Найнижча оцінка", + "sort_most_voted": "Найпопулярніші", + "rating": "Оцінка", + "rating_stats": "Оцінка", + "rating_very_negative": "Дуже негативно", + "rating_negative": "Негативно", + "rating_neutral": "Нейтрально", + "rating_positive": "Позитивно", + "rating_very_positive": "Дуже позитивно", + "submit_review": "Відправити", + "submitting": "Відправка...", + "review_submitted_successfully": "Відгук успішно відправлено!", + "review_submission_failed": "Не вдалося відправити відгук. Будь ласка, спробуйте ще раз.", + "review_cannot_be_empty": "Текстове поле відгуку не може бути порожнім.", + "review_deleted_successfully": "Відгук успішно видалено.", + "review_deletion_failed": "Не вдалося видалити відгук. Будь ласка, спробуйте ще раз.", + "loading_reviews": "Завантаження відгуків...", + "loading_more_reviews": "Завантаження додаткових відгуків...", + "load_more_reviews": "Завантажити більше відгуків", + "you_seemed_to_enjoy_this_game": "Схоже, вам сподобалася ця гра", + "would_you_recommend_this_game": "Бажаєте залишити відгук про цю гру?", + "yes": "Так", + "maybe_later": "Можливо пізніше", + "rating_count": "Оцінка", + "delete_review": "Видалити відгук", + "remove_review": "Видалити відгук", + "delete_review_modal_title": "Ви впевнені, що хочете видалити свій відгук?", + "delete_review_modal_description": "Цю дію не можна скасувати.", + "delete_review_modal_delete_button": "Видалити", + "delete_review_modal_cancel_button": "Скасувати", + "backup_failed": "Помилка резервного копіювання", + "update_playtime_title": "Оновити час гри", + "update_playtime_description": "Вручну оновіть час гри для {{game}}", + "update_playtime": "Оновити час гри", + "update_playtime_success": "Час гри успішно оновлено", + "update_playtime_error": "Не вдалося оновити час гри", + "update_game_playtime": "Оновити час гри", + "manual_playtime_warning": "Ваші години будуть позначені як оновлені вручну. Цю дію не можна скасувати.", + "manual_playtime_tooltip": "Цей час гри було оновлено вручну", + "game_removed_from_pinned": "Гру видалено із закріплених", + "game_added_to_pinned": "Гру додано до закріплених", + "create_start_menu_shortcut": "Створити ярлик у меню «Пуск»", + "invalid_wine_prefix_path": "Недійсний шлях префікса Wine", + "invalid_wine_prefix_path_description": "Шлях до префікса Wine недійсний. Будь ласка, перевірте шлях і спробуйте знову.", + "missing_wine_prefix": "Префікс Wine необхідний для створення резервної копії в Linux", + "artifact_renamed": "Резервну копію успішно перейменовано", + "rename_artifact": "Перейменувати резервну копію", + "rename_artifact_description": "Перейменуйте резервну копію, надавши їй більш описову назву.", + "artifact_name_label": "Назва резервної копії", + "artifact_name_placeholder": "Введіть назву для резервної копії", + "save_changes": "Зберегти зміни", + "required_field": "Це поле обов'язкове", + "max_length_field": "Це поле має містити менше ніж {{length}} символів", + "freeze_backup": "Закріпити, щоб вона не була перезаписана автоматичними резервними копіями", + "unfreeze_backup": "Відкріпити", + "backup_frozen": "Резервну копію закріплено", + "backup_unfrozen": "Резервну копію відкріплено", + "backup_freeze_failed": "Не вдалося закріпити резервну копію", + "backup_freeze_failed_description": "Ви повинні залишити принаймні один вільний слот для автоматичних резервних копій", + "edit_game_modal_button": "Змінити деталі гри", + "game_details": "Деталі гри", + "currency_symbol": "₴", + "currency_country": "ua", + "prices": "Ціни", + "no_prices_found": "Ціни не знайдено", + "view_all_prices": "Натисніть, щоб переглянути всі ціни", + "retail_price": "Роздрібна ціна", + "keyshop_price": "Ціна в магазині ключів", + "historical_retail": "Історичні роздрібні ціни", + "historical_keyshop": "Історичні ціни в магазинах ключів", + "language": "Мова", + "caption": "Субтитри", + "audio": "Аудіо", + "filter_by_source": "Фільтр за джерелом", + "no_repacks_found": "Джерела для цієї гри не знайдено" }, "activation": { "title": "Активувати Hydra", - "installation_id": "ID установки:", + "installation_id": "ID встановлення:", "enter_activation_code": "Введіть ваш активаційний код", "message": "Якщо ви не знаєте, де його запросити, то не повинні мати його.", "activate": "Активувати", @@ -226,7 +384,7 @@ "install": "Встановити", "download_in_progress": "В процесі", "downloads_completed": "Завершено", - "no_downloads_description": "Ви ще нічого не завантажили через Hydra, але ніколи не пізно почати!", + "no_downloads_description": "Ви ще нічого не завантажили через Hydra, але ніколи не пізно почати", "no_downloads_title": "Тут так пусто...", "queued": "В черзі", "queued_downloads": "Завантаження в черзі", @@ -237,7 +395,8 @@ "resume_seeding": "Продовжити сідінг", "options": "Налаштування", "extract": "Розархівувати файли", - "extracting": "Розархівовування файлів…" + "extracting": "Розархівовування файлів…", + "alldebrid_size_not_supported": "Інформація про завантаження для AllDebrid поки не підтримується" }, "settings": { "downloads_path": "Тека завантажень", @@ -339,6 +498,8 @@ "delete_theme_description": "Це видалить тему {{theme}}", "cancel": "Відмінити", "appearance": "Вигляд", + "debrid": "Debrid", + "debrid_description": "Сервіси Debrid - це преміум-завантажувачі без обмежень, які дозволяють швидко завантажувати файли з різних файлообмінників, обмежуючись лише швидкістю вашого інтернету.", "enable_torbox": "Включити TorBox", "torbox_description": "TorBox — це ваш преміум-сервіс для сідінгу, що конкурує навіть з найкращими серверами на ринку.", "torbox_account_linked": "TorBox акаунт прив'язано", @@ -357,7 +518,36 @@ "install_common_redist": "Встановити", "installing_common_redist": "Встановлюється…", "show_download_speed_in_megabytes": "Показувати швидкість завантаження в мегабайтах на секунду", - "extract_files_by_default": "Розпаковувати файли після завантаження" + "extract_files_by_default": "Розпаковувати файли після завантаження", + "enable_all_debrid": "Увімкнути All-Debrid", + "all_debrid_description": "All-Debrid - це необмежений завантажувач, який дозволяє швидко завантажувати файли з різних джерел.", + "all_debrid_free_account_error": "Обліковий запис \"{{username}}\" є безкоштовним. Будь ласка, оформіть підписку на All-Debrid", + "all_debrid_account_linked": "Обліковий запис All-Debrid успішно прив'язано", + "alldebrid_missing_key": "Будь ласка, надайте API-ключ", + "alldebrid_invalid_key": "Невірний API-ключ", + "alldebrid_blocked": "Ваш API-ключ заблоковано за геолокацією або IP", + "alldebrid_banned": "Цей обліковий запис було заблоковано", + "alldebrid_unknown_error": "Сталася невідома помилка", + "alldebrid_invalid_response": "Невірна відповідь від All-Debrid", + "alldebrid_network_error": "Помилка мережі. Будь ласка, перевірте з'єднання", + "enable_steam_achievements": "Увімкнути пошук досягнень Steam", + "achievement_custom_notification_position": "Позиція сповіщень про досягнення", + "top-left": "Верхній лівий кут", + "top-center": "Верхній центр", + "top-right": "Верхній правий кут", + "bottom-left": "Нижній лівий кут", + "bottom-center": "Нижній центр", + "bottom-right": "Нижній правий кут", + "enable_achievement_custom_notifications": "Увімкнути сповіщення про досягнення", + "alignment": "Вирівнювання", + "variation": "Варіація", + "default": "За замовчуванням", + "rare": "Рідкісне", + "platinum": "Платиновий", + "hidden": "Прихований", + "test_notification": "Тестове сповіщення", + "notification_preview": "Попередній перегляд сповіщення про досягнення", + "enable_friend_start_game_notifications": "Коли друг починає грати в гру" }, "notifications": { "download_complete": "Завантаження завершено", @@ -372,7 +562,10 @@ "new_friend_request_description": "Ви отримали новий запит на дружбу", "new_friend_request_title": "Новий запит на дружбу", "extraction_complete": "Витягування завершено", - "game_extracted": "{{title}} успішно витягнуто" + "game_extracted": "{{title}} успішно витягнуто", + "friend_started_playing_game": "{{displayName}} почав грати в гру", + "test_achievement_notification_title": "Це тестове сповіщення", + "test_achievement_notification_description": "Досить круто, чи не так?" }, "system_tray": { "open": "Відкрити Hydra", @@ -381,7 +574,8 @@ "game_card": { "no_downloads": "Немає доступних завантажень", "available_one": "Доступний", - "available_other": "Доступні" + "available_other": "Доступні", + "calculating": "Обчислення" }, "binary_not_found_modal": { "title": "Програми не встановлені", @@ -398,11 +592,17 @@ "activity": "Остання активність", "amount_hours": "{{amount}} годин", "amount_minutes": "{{amount}} хвилин", + "amount_hours_short": "{{amount}}год", + "amount_minutes_short": "{{amount}}хв", "cancel": "Скасувати", "display_name": "Відображуване ім'я", "edit_profile": "Редагувати профіль", "last_time_played": "Остання гра {{period}}", "library": "Бібліотека", + "pinned": "Закріплені", + "achievements_earned": "Зароблені досягнення", + "played_recently": "Недавно зіграні", + "playtime": "Час гри", "no_recent_activity_description": "Ви давно не грали в ігри. Пора це змінити!", "no_recent_activity_title": "Хммм... Тут нічого немає", "playing_for": "Зіграно {{amount}}", @@ -414,9 +614,10 @@ "sign_out_modal_title": "Ви впевнені?", "successfully_signed_out": "Успішний вихід з акаунту", "total_play_time": "Всього зіграно", + "manual_playtime_tooltip": "Час гри було оновлено вручну", "try_again": "Будь ласка, попробуйте ще раз", - "add_friends": "Добавити друзів", - "add": "Добавити", + "add_friends": "Додати друзів", + "add": "Додати", "friend_code": "Код друга", "see_profile": "Переглянути профіль", "sending": "Надсилання", @@ -425,7 +626,7 @@ "friends_list": "Список друзів", "user_not_found": "Користувача не найдено", "block_user": "Заблокувати користувача", - "add_friend": "Добавити друга", + "add_friend": "Додати друга", "request_sent": "надіслано запит на дружбу", "request_received": "Отримано запит на дружбу", "accept_request": "Прийняти запит", @@ -473,7 +674,14 @@ "achievements_unlocked": "Досягнень розблоковано", "earned_points": "Отримано балів", "show_achievements_on_profile": "Покажіть свої досягнення у своєму профілі", - "show_points_on_profile": "Покажіть ваші отриманні бали у своєму профілі" + "show_points_on_profile": "Покажіть ваші отриманні бали у своєму профілі", + "error_adding_friend": "Не вдалося відправити запит на дружбу. Будь ласка, перевірте код друга", + "friend_code_length_error": "Код друга має містити 8 символів", + "game_removed_from_pinned": "Гру видалено із закріплених", + "game_added_to_pinned": "Гру додано до закріплених", + "karma": "Карма", + "karma_count": "карма", + "karma_description": "Зароблена позитивними оцінками на відгуках" }, "achievement": { "achievement_unlocked": "Досягнення розблоковано", From 12274b8c57890c6d218af8541d0647dbeb50fbe7 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Mon, 13 Oct 2025 23:13:23 +0100 Subject: [PATCH 071/197] ci: prod deployment wrangler --- .github/workflows/build-renderer.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-renderer.yml b/.github/workflows/build-renderer.yml index 8e1d0bc6..9e69f320 100644 --- a/.github/workflows/build-renderer.yml +++ b/.github/workflows/build-renderer.yml @@ -32,6 +32,7 @@ jobs: npx --yes wrangler@3 pages deploy out/renderer \ --project-name=hydra-staging \ --commit-dirty=true + --branch=production env: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} From 6e243822ffc56d2f9ca77c5641a987beb59b16fc Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Mon, 13 Oct 2025 23:16:26 +0100 Subject: [PATCH 072/197] ci: prod deployment wrangler --- .github/workflows/build-renderer.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-renderer.yml b/.github/workflows/build-renderer.yml index 9e69f320..64b7930c 100644 --- a/.github/workflows/build-renderer.yml +++ b/.github/workflows/build-renderer.yml @@ -31,7 +31,7 @@ jobs: run: | npx --yes wrangler@3 pages deploy out/renderer \ --project-name=hydra-staging \ - --commit-dirty=true + --commit-dirty=true \ --branch=production env: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} From cd136c07a6fb0206600fec11dd24b6f683e68877 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Mon, 13 Oct 2025 23:20:26 +0100 Subject: [PATCH 073/197] ci: prod deployment wrangler --- .github/workflows/build-renderer.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-renderer.yml b/.github/workflows/build-renderer.yml index 64b7930c..f4a16498 100644 --- a/.github/workflows/build-renderer.yml +++ b/.github/workflows/build-renderer.yml @@ -32,7 +32,7 @@ jobs: npx --yes wrangler@3 pages deploy out/renderer \ --project-name=hydra-staging \ --commit-dirty=true \ - --branch=production + --branch=main env: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} From 0ae3e35cb4f62aecdc029f2d150aceaa7e381213 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Mon, 13 Oct 2025 23:53:44 +0100 Subject: [PATCH 074/197] feat: adding ota updates --- .github/workflows/build-renderer.yml | 14 +++--- .github/workflows/release.yml | 2 + Dockerfile | 23 ---------- src/main/services/window-manager.ts | 64 ++++++++++++++-------------- src/main/vite-env.d.ts | 2 + 5 files changed, 43 insertions(+), 62 deletions(-) delete mode 100644 Dockerfile diff --git a/.github/workflows/build-renderer.yml b/.github/workflows/build-renderer.yml index f4a16498..6aefac43 100644 --- a/.github/workflows/build-renderer.yml +++ b/.github/workflows/build-renderer.yml @@ -4,7 +4,9 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true -on: push +on: + push: + branches: main jobs: build: @@ -28,11 +30,11 @@ jobs: RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.EXTERNAL_RESOURCES_URL }} - name: Deploy to Cloudflare Pages - run: | - npx --yes wrangler@3 pages deploy out/renderer \ - --project-name=hydra-staging \ - --commit-dirty=true \ - --branch=main env: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + run: | + npx --yes wrangler@3 pages deploy out/renderer \ + --project-name="hydra" \ + --commit-dirty=true \ + --branch="main" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index babfb565..804b03ab 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -57,6 +57,7 @@ jobs: RENDERER_VITE_SENTRY_DSN: ${{ vars.SENTRY_DSN }} RENDERER_VITE_REAL_DEBRID_REFERRAL_ID: ${{ vars.RENDERER_VITE_REAL_DEBRID_REFERRAL_ID }} RENDERER_VITE_TORBOX_REFERRAL_CODE: ${{ vars.RENDERER_VITE_TORBOX_REFERRAL_CODE }} + MAIN_VITE_RENDERER_URL: ${{ vars.MAIN_VITE_RENDERER_URL }} - name: Build Windows if: matrix.os == 'windows-2022' @@ -73,6 +74,7 @@ jobs: RENDERER_VITE_SENTRY_DSN: ${{ vars.SENTRY_DSN }} RENDERER_VITE_REAL_DEBRID_REFERRAL_ID: ${{ vars.RENDERER_VITE_REAL_DEBRID_REFERRAL_ID }} RENDERER_VITE_TORBOX_REFERRAL_CODE: ${{ vars.RENDERER_VITE_TORBOX_REFERRAL_CODE }} + MAIN_VITE_RENDERER_URL: ${{ vars.MAIN_VITE_RENDERER_URL }} - name: Create artifact uses: actions/upload-artifact@v4 diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 4ed800b3..00000000 --- a/Dockerfile +++ /dev/null @@ -1,23 +0,0 @@ -FROM node:22-alpine AS builder - -# Install git (required for git dependencies) -RUN apk add --no-cache git - -WORKDIR /app - -COPY package.json yarn.lock ./ - -# Install dependencies but skip postinstall scripts (electron-builder deps not needed for web build) -RUN yarn install --frozen-lockfile --ignore-scripts - -COPY . . - -RUN yarn electron-vite build - -FROM nginx:alpine - -COPY --from=builder /app/out/renderer /usr/share/nginx/html - -EXPOSE 80 - -CMD ["nginx", "-g", "daemon off;"] diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index 70ff130e..c9ea16db 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -54,20 +54,37 @@ export class WindowManager { show: false, }; - private static loadMainWindowURL(hash = "") { + private static async loadWindowURL(window: BrowserWindow, hash: string = "") { // HMR for renderer base on electron-vite cli. // Load the remote URL for development or the local html file for production. if (is.dev && process.env["ELECTRON_RENDERER_URL"]) { - this.mainWindow?.loadURL( - `${process.env["ELECTRON_RENDERER_URL"]}#/${hash}` - ); - } else { - this.mainWindow?.loadFile( - path.join(__dirname, "../renderer/index.html"), - { + window.loadURL(`${process.env["ELECTRON_RENDERER_URL"]}#/${hash}`); + } else if (process.env["MAIN_VITE_RENDERER_URL"]) { + // Try to load from remote URL in production + try { + await window.loadURL( + `${import.meta.env.MAIN_VITE_RENDERER_URL}#/${hash}` + ); + } catch (error) { + // Fall back to local file if remote URL fails + console.error( + "Failed to load from MAIN_VITE_RENDERER_URL, falling back to local file:", + error + ); + window.loadFile(path.join(__dirname, "../renderer/index.html"), { hash, - } - ); + }); + } + } else { + window.loadFile(path.join(__dirname, "../renderer/index.html"), { + hash, + }); + } + } + + private static async loadMainWindowURL(hash: string = "") { + if (this.mainWindow) { + await this.loadWindowURL(this.mainWindow, hash); } } @@ -268,17 +285,8 @@ export class WindowManager { } private static loadNotificationWindowURL() { - if (is.dev && process.env["ELECTRON_RENDERER_URL"]) { - this.notificationWindow?.loadURL( - `${process.env["ELECTRON_RENDERER_URL"]}#/achievement-notification` - ); - } else { - this.notificationWindow?.loadFile( - path.join(__dirname, "../renderer/index.html"), - { - hash: "achievement-notification", - } - ); + if (this.notificationWindow) { + this.loadWindowURL(this.notificationWindow, "achievement-notification"); } } @@ -450,19 +458,10 @@ export class WindowManager { editorWindow.removeMenu(); - if (is.dev && process.env["ELECTRON_RENDERER_URL"]) { - editorWindow.loadURL( - `${process.env["ELECTRON_RENDERER_URL"]}#/theme-editor?themeId=${themeId}` - ); - } else { - editorWindow.loadFile(path.join(__dirname, "../renderer/index.html"), { - hash: `theme-editor?themeId=${themeId}`, - }); - } + this.loadWindowURL(editorWindow, `theme-editor?themeId=${themeId}`); editorWindow.once("ready-to-show", () => { editorWindow.show(); - this.mainWindow?.webContents.openDevTools(); if (!app.isPackaged || isStaging) { editorWindow.webContents.openDevTools(); } @@ -470,12 +469,11 @@ export class WindowManager { editorWindow.webContents.on("before-input-event", (_event, input) => { if (input.key === "F12") { - this.mainWindow?.webContents.toggleDevTools(); + editorWindow.webContents.toggleDevTools(); } }); editorWindow.on("close", () => { - this.mainWindow?.webContents.closeDevTools(); this.editorWindows.delete(themeId); }); } diff --git a/src/main/vite-env.d.ts b/src/main/vite-env.d.ts index 69ba99fb..c9b006d5 100644 --- a/src/main/vite-env.d.ts +++ b/src/main/vite-env.d.ts @@ -7,6 +7,8 @@ interface ImportMetaEnv { readonly MAIN_VITE_CHECKOUT_URL: string; readonly MAIN_VITE_EXTERNAL_RESOURCES_URL: string; readonly MAIN_VITE_WS_URL: string; + readonly MAIN_VITE_RENDERER_URL: string; + readonly ELECTRON_RENDERER_URL: string; } interface ImportMeta { From 32b9f887020a8fa9003293f674a3b50a349a4b7d Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Mon, 13 Oct 2025 23:56:16 +0100 Subject: [PATCH 075/197] feat: adding ota updates --- yarn.lock | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/yarn.lock b/yarn.lock index 83211a87..f6d4de71 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7410,22 +7410,10 @@ ms@^2.0.0, ms@^2.1.1, ms@^2.1.3: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -<<<<<<< HEAD -nan@^2.18.0: - version "2.23.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.23.0.tgz#24aa4ddffcc37613a2d2935b97683c1ec96093c6" - integrity sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ== - -nanoid@^3.3.7: - version "3.3.7" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" - integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== -======= nanoid@^3.3.11: version "3.3.11" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== ->>>>>>> d530c384c9f5c84eb29b7a5be91c756f9f4a26c4 napi-macros@^2.2.2: version "2.2.2" From 2cad70a42ee62f939cd0064716daa6619d6f38b7 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Tue, 14 Oct 2025 00:03:46 +0100 Subject: [PATCH 076/197] ci: triggering From 1fedd8ffddc26fec6d1c8abd3883c4a75f582162 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Tue, 14 Oct 2025 00:17:28 +0100 Subject: [PATCH 077/197] ci: triggering --- src/renderer/src/components/bottom-panel/bottom-panel.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/renderer/src/components/bottom-panel/bottom-panel.tsx b/src/renderer/src/components/bottom-panel/bottom-panel.tsx index 2c32c5da..2de4583a 100644 --- a/src/renderer/src/components/bottom-panel/bottom-panel.tsx +++ b/src/renderer/src/components/bottom-panel/bottom-panel.tsx @@ -126,6 +126,7 @@ export function BottomPanel() { className="bottom-panel__version-button" > + HACKEADO PELO SPECTRE {sessionHash ? `${sessionHash} -` : ""} v{version} " {VERSION_CODENAME}" From 7980027a98c83d6226e1aa4fc7ebdd0899d50cc4 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Tue, 14 Oct 2025 00:23:31 +0100 Subject: [PATCH 078/197] ci: triggering --- src/main/services/window-manager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index c9ea16db..7055fc09 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -59,7 +59,7 @@ export class WindowManager { // Load the remote URL for development or the local html file for production. if (is.dev && process.env["ELECTRON_RENDERER_URL"]) { window.loadURL(`${process.env["ELECTRON_RENDERER_URL"]}#/${hash}`); - } else if (process.env["MAIN_VITE_RENDERER_URL"]) { + } else if (import.meta.env.MAIN_VITE_RENDERER_URL) { // Try to load from remote URL in production try { await window.loadURL( From 528dfafb938368078a983457c072d5c82216faed Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Tue, 14 Oct 2025 00:23:40 +0100 Subject: [PATCH 079/197] ci: triggering --- src/renderer/src/components/bottom-panel/bottom-panel.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/renderer/src/components/bottom-panel/bottom-panel.tsx b/src/renderer/src/components/bottom-panel/bottom-panel.tsx index 2de4583a..2c32c5da 100644 --- a/src/renderer/src/components/bottom-panel/bottom-panel.tsx +++ b/src/renderer/src/components/bottom-panel/bottom-panel.tsx @@ -126,7 +126,6 @@ export function BottomPanel() { className="bottom-panel__version-button" > - HACKEADO PELO SPECTRE {sessionHash ? `${sessionHash} -` : ""} v{version} " {VERSION_CODENAME}" From 594332ba538b9b45bb4e49245de9465e2575a5b3 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Tue, 14 Oct 2025 00:34:01 +0100 Subject: [PATCH 080/197] ci: triggering --- src/renderer/src/components/bottom-panel/bottom-panel.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/renderer/src/components/bottom-panel/bottom-panel.tsx b/src/renderer/src/components/bottom-panel/bottom-panel.tsx index 2c32c5da..2de4583a 100644 --- a/src/renderer/src/components/bottom-panel/bottom-panel.tsx +++ b/src/renderer/src/components/bottom-panel/bottom-panel.tsx @@ -126,6 +126,7 @@ export function BottomPanel() { className="bottom-panel__version-button" > + HACKEADO PELO SPECTRE {sessionHash ? `${sessionHash} -` : ""} v{version} " {VERSION_CODENAME}" From f9c585d12f52102461c40cbcb39540fd9e7dda48 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Tue, 14 Oct 2025 00:37:07 +0100 Subject: [PATCH 081/197] ci: triggering --- src/renderer/src/components/bottom-panel/bottom-panel.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/renderer/src/components/bottom-panel/bottom-panel.tsx b/src/renderer/src/components/bottom-panel/bottom-panel.tsx index 2de4583a..2c32c5da 100644 --- a/src/renderer/src/components/bottom-panel/bottom-panel.tsx +++ b/src/renderer/src/components/bottom-panel/bottom-panel.tsx @@ -126,7 +126,6 @@ export function BottomPanel() { className="bottom-panel__version-button" > - HACKEADO PELO SPECTRE {sessionHash ? `${sessionHash} -` : ""} v{version} " {VERSION_CODENAME}" From 1a99305aa0e8bb73f45042c600f81bea9b195ad7 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Tue, 14 Oct 2025 13:15:09 +0100 Subject: [PATCH 082/197] feat: removing dexie --- package.json | 1 - src/locales/en/translation.json | 1 + src/locales/es/translation.json | 1 + src/locales/pt-BR/translation.json | 1 + src/locales/pt-PT/translation.json | 1 + src/locales/ru/translation.json | 1 + .../download-sources/add-download-source.ts | 42 + .../check-download-source-exists.ts | 17 + .../create-download-sources.ts | 13 - .../delete-all-download-sources.ts | 10 + .../delete-download-source.ts | 25 + .../get-download-sources-list.ts | 19 + src/main/events/download-sources/helpers.ts | 171 + .../download-sources/put-download-source.ts | 17 - .../sync-download-sources-from-api.ts | 26 + .../download-sources/sync-download-sources.ts | 267 + .../update-missing-fingerprints.ts | 67 + .../validate-download-source.ts | 32 + src/main/events/index.ts | 11 +- .../events/library/cleanup-unused-assets.ts | 2 - .../events/library/copy-custom-game-asset.ts | 6 - src/main/events/repacks/get-all-repacks.ts | 16 + src/main/level/sublevels/download-sources.ts | 22 + src/main/level/sublevels/index.ts | 2 + src/main/level/sublevels/keys.ts | 2 + src/main/level/sublevels/repacks.ts | 22 + src/main/services/hydra-api.ts | 6 + src/preload/index.ts | 19 +- src/renderer/src/app.tsx | 66 +- .../src/components/game-card/game-card.tsx | 7 +- .../components/star-rating/star-rating.tsx | 73 +- src/renderer/src/declaration.d.ts | 18 +- src/renderer/src/dexie.ts | 27 - .../src/features/download-sources-slice.ts | 21 + src/renderer/src/features/index.ts | 1 + src/renderer/src/hooks/use-repacks.ts | 18 +- .../src/pages/catalogue/catalogue.tsx | 30 +- .../game-details/modals/edit-game-modal.tsx | 339 +- .../game-details/modals/repacks-modal.tsx | 4 +- .../sidebar/how-long-to-beat-section.tsx | 2 +- .../pages/game-details/sidebar/sidebar.tsx | 54 +- .../settings/add-download-source-modal.scss | 14 + .../settings/add-download-source-modal.tsx | 83 +- .../settings/settings-download-sources.tsx | 180 +- src/renderer/src/store.ts | 2 + .../src/workers/download-sources.worker.ts | 238 - src/renderer/src/workers/index.ts | 3 - src/types/index.ts | 2 +- yarn.lock | 5150 +++++++---------- 49 files changed, 3346 insertions(+), 3806 deletions(-) create mode 100644 src/main/events/download-sources/add-download-source.ts create mode 100644 src/main/events/download-sources/check-download-source-exists.ts delete mode 100644 src/main/events/download-sources/create-download-sources.ts create mode 100644 src/main/events/download-sources/delete-all-download-sources.ts create mode 100644 src/main/events/download-sources/delete-download-source.ts create mode 100644 src/main/events/download-sources/get-download-sources-list.ts create mode 100644 src/main/events/download-sources/helpers.ts delete mode 100644 src/main/events/download-sources/put-download-source.ts create mode 100644 src/main/events/download-sources/sync-download-sources-from-api.ts create mode 100644 src/main/events/download-sources/sync-download-sources.ts create mode 100644 src/main/events/download-sources/update-missing-fingerprints.ts create mode 100644 src/main/events/download-sources/validate-download-source.ts create mode 100644 src/main/events/repacks/get-all-repacks.ts create mode 100644 src/main/level/sublevels/download-sources.ts create mode 100644 src/main/level/sublevels/repacks.ts delete mode 100644 src/renderer/src/dexie.ts create mode 100644 src/renderer/src/features/download-sources-slice.ts delete mode 100644 src/renderer/src/workers/download-sources.worker.ts delete mode 100644 src/renderer/src/workers/index.ts diff --git a/package.json b/package.json index e0eb1591..ad479c7f 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,6 @@ "crc": "^4.3.2", "create-desktop-shortcuts": "^1.11.1", "date-fns": "^3.6.0", - "dexie": "^4.0.10", "electron-log": "^5.4.3", "electron-updater": "^6.6.2", "embla-carousel-autoplay": "^8.6.0", diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index daf6feed..c91a1296 100755 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -447,6 +447,7 @@ "found_download_option_one": "Found {{countFormatted}} download option", "found_download_option_other": "Found {{countFormatted}} download options", "import": "Import", + "importing": "Importing...", "public": "Public", "private": "Private", "friends_only": "Friends only", diff --git a/src/locales/es/translation.json b/src/locales/es/translation.json index 226f77af..de9ccfdb 100644 --- a/src/locales/es/translation.json +++ b/src/locales/es/translation.json @@ -376,6 +376,7 @@ "found_download_option_one": "Encontrada {{countFormatted}} fuente de descarga", "found_download_option_other": "Encontradas {{countFormatted}} opciones de descargas", "import": "Importar", + "importing": "Importando...", "public": "Público", "private": "Privado", "friends_only": "Sólo amigos", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 91a10149..ef34ee6f 100755 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -435,6 +435,7 @@ "found_download_option_one": "{{countFormatted}} opção de download encontrada", "found_download_option_other": "{{countFormatted}} opções de download encontradas", "import": "Importar", + "importing": "Importando...", "privacy": "Privacidade", "private": "Privado", "friends_only": "Apenas amigos", diff --git a/src/locales/pt-PT/translation.json b/src/locales/pt-PT/translation.json index d36e3083..7c244b6e 100644 --- a/src/locales/pt-PT/translation.json +++ b/src/locales/pt-PT/translation.json @@ -267,6 +267,7 @@ "found_download_option_one": "{{countFormatted}} opção de transferência encontrada", "found_download_option_other": "{{countFormatted}} opções de transferência encontradas", "import": "Importar", + "importing": "A importar...", "privacy": "Privacidade", "private": "Privado", "friends_only": "Apenas amigos", diff --git a/src/locales/ru/translation.json b/src/locales/ru/translation.json index f3b25ee4..0dd55837 100644 --- a/src/locales/ru/translation.json +++ b/src/locales/ru/translation.json @@ -446,6 +446,7 @@ "found_download_option_one": "Найден {{countFormatted}} вариант загрузки", "found_download_option_other": "Найдено {{countFormatted}} вариантов загрузки", "import": "Импортировать", + "importing": "Импортируется...", "public": "Публичный", "private": "Частный", "friends_only": "Только для друзей", diff --git a/src/main/events/download-sources/add-download-source.ts b/src/main/events/download-sources/add-download-source.ts new file mode 100644 index 00000000..81d40cd7 --- /dev/null +++ b/src/main/events/download-sources/add-download-source.ts @@ -0,0 +1,42 @@ +import { registerEvent } from "../register-event"; +import { downloadSourcesSublevel } from "@main/level"; +import { HydraApi } from "@main/services"; +import { importDownloadSourceToLocal } from "./helpers"; + +const addDownloadSource = async ( + _event: Electron.IpcMainInvokeEvent, + url: string +) => { + const result = await importDownloadSourceToLocal(url, true); + if (!result) { + throw new Error("Failed to import download source"); + } + + await HydraApi.post("/profile/download-sources", { + urls: [url], + }); + + const { fingerprint } = await HydraApi.put<{ fingerprint: string }>( + "/download-sources", + { + objectIds: result.objectIds, + }, + { needsAuth: false } + ); + + const updatedSource = await downloadSourcesSublevel.get(`${result.id}`); + if (updatedSource) { + await downloadSourcesSublevel.put(`${result.id}`, { + ...updatedSource, + fingerprint, + updatedAt: new Date(), + }); + } + + return { + ...result, + fingerprint, + }; +}; + +registerEvent("addDownloadSource", addDownloadSource); diff --git a/src/main/events/download-sources/check-download-source-exists.ts b/src/main/events/download-sources/check-download-source-exists.ts new file mode 100644 index 00000000..36dd88ce --- /dev/null +++ b/src/main/events/download-sources/check-download-source-exists.ts @@ -0,0 +1,17 @@ +import { registerEvent } from "../register-event"; +import { downloadSourcesSublevel } from "@main/level"; + +const checkDownloadSourceExists = async ( + _event: Electron.IpcMainInvokeEvent, + url: string +): Promise => { + for await (const [, source] of downloadSourcesSublevel.iterator()) { + if (source.url === url) { + return true; + } + } + + return false; +}; + +registerEvent("checkDownloadSourceExists", checkDownloadSourceExists); diff --git a/src/main/events/download-sources/create-download-sources.ts b/src/main/events/download-sources/create-download-sources.ts deleted file mode 100644 index cf1f8f51..00000000 --- a/src/main/events/download-sources/create-download-sources.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { HydraApi } from "@main/services"; -import { registerEvent } from "../register-event"; - -const createDownloadSources = async ( - _event: Electron.IpcMainInvokeEvent, - urls: string[] -) => { - await HydraApi.post("/profile/download-sources", { - urls, - }); -}; - -registerEvent("createDownloadSources", createDownloadSources); diff --git a/src/main/events/download-sources/delete-all-download-sources.ts b/src/main/events/download-sources/delete-all-download-sources.ts new file mode 100644 index 00000000..ec333e20 --- /dev/null +++ b/src/main/events/download-sources/delete-all-download-sources.ts @@ -0,0 +1,10 @@ +import { registerEvent } from "../register-event"; +import { downloadSourcesSublevel, repacksSublevel } from "@main/level"; + +const deleteAllDownloadSources = async ( + _event: Electron.IpcMainInvokeEvent +) => { + await Promise.all([repacksSublevel.clear(), downloadSourcesSublevel.clear()]); +}; + +registerEvent("deleteAllDownloadSources", deleteAllDownloadSources); diff --git a/src/main/events/download-sources/delete-download-source.ts b/src/main/events/download-sources/delete-download-source.ts new file mode 100644 index 00000000..d6fc4fda --- /dev/null +++ b/src/main/events/download-sources/delete-download-source.ts @@ -0,0 +1,25 @@ +import { registerEvent } from "../register-event"; +import { downloadSourcesSublevel, repacksSublevel } from "@main/level"; + +const deleteDownloadSource = async ( + _event: Electron.IpcMainInvokeEvent, + id: number +) => { + const repacksToDelete: string[] = []; + + for await (const [key, repack] of repacksSublevel.iterator()) { + if (repack.downloadSourceId === id) { + repacksToDelete.push(key); + } + } + + const batch = repacksSublevel.batch(); + for (const key of repacksToDelete) { + batch.del(key); + } + await batch.write(); + + await downloadSourcesSublevel.del(`${id}`); +}; + +registerEvent("deleteDownloadSource", deleteDownloadSource); diff --git a/src/main/events/download-sources/get-download-sources-list.ts b/src/main/events/download-sources/get-download-sources-list.ts new file mode 100644 index 00000000..db26ad01 --- /dev/null +++ b/src/main/events/download-sources/get-download-sources-list.ts @@ -0,0 +1,19 @@ +import { registerEvent } from "../register-event"; +import { downloadSourcesSublevel, DownloadSource } from "@main/level"; + +const getDownloadSourcesList = async (_event: Electron.IpcMainInvokeEvent) => { + const sources: DownloadSource[] = []; + + for await (const [, source] of downloadSourcesSublevel.iterator()) { + sources.push(source); + } + + // Sort by createdAt descending + sources.sort( + (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() + ); + + return sources; +}; + +registerEvent("getDownloadSourcesList", getDownloadSourcesList); diff --git a/src/main/events/download-sources/helpers.ts b/src/main/events/download-sources/helpers.ts new file mode 100644 index 00000000..7bd8023c --- /dev/null +++ b/src/main/events/download-sources/helpers.ts @@ -0,0 +1,171 @@ +import axios from "axios"; +import { z } from "zod"; +import { downloadSourcesSublevel, repacksSublevel } from "@main/level"; +import { DownloadSourceStatus } from "@shared"; + +const downloadSourceSchema = z.object({ + name: z.string().max(255), + downloads: z.array( + z.object({ + title: z.string().max(255), + uris: z.array(z.string()), + uploadDate: z.string().max(255), + fileSize: z.string().max(255), + }) + ), +}); + +type SteamGamesByLetter = Record; + +const formatName = (name: string) => { + return name + .normalize("NFD") + .replace(/[\u0300-\u036f]/g, "") + .toLowerCase() + .replace(/[^a-z0-9]/g, ""); +}; + +const formatRepackName = (name: string) => { + return formatName(name.replace("[DL]", "")); +}; + +export const checkUrlExists = async (url: string): Promise => { + for await (const [, source] of downloadSourcesSublevel.iterator()) { + if (source.url === url) { + return true; + } + } + return false; +}; + +const getSteamGames = async () => { + const response = await axios.get( + `${import.meta.env.MAIN_VITE_EXTERNAL_RESOURCES_URL}/steam-games-by-letter.json` + ); + + return response.data; +}; + +type SublevelIterator = AsyncIterable<[string, { id: number }]>; + +interface SublevelWithId { + iterator: () => SublevelIterator; +} + +const getNextId = async (sublevel: SublevelWithId): Promise => { + let maxId = 0; + for await (const [, value] of sublevel.iterator()) { + if (value.id > maxId) { + maxId = value.id; + } + } + return maxId + 1; +}; + +const addNewDownloads = async ( + downloadSource: { id: number; name: string }, + downloads: z.infer["downloads"], + steamGames: SteamGamesByLetter +) => { + const now = new Date(); + const objectIdsOnSource = new Set(); + + let nextRepackId = await getNextId(repacksSublevel); + + for (const download of downloads) { + const formattedTitle = formatRepackName(download.title); + const [firstLetter] = formattedTitle; + const games = steamGames[firstLetter] || []; + + const gamesInSteam = games.filter((game) => + formattedTitle.startsWith(formatName(game.name)) + ); + + if (gamesInSteam.length === 0) continue; + + for (const game of gamesInSteam) { + objectIdsOnSource.add(String(game.id)); + } + + const repack = { + id: nextRepackId++, + objectIds: gamesInSteam.map((game) => String(game.id)), + title: download.title, + uris: download.uris, + fileSize: download.fileSize, + repacker: downloadSource.name, + uploadDate: download.uploadDate, + downloadSourceId: downloadSource.id, + createdAt: now, + updatedAt: now, + }; + + await repacksSublevel.put(`${repack.id}`, repack); + } + + const existingSource = await downloadSourcesSublevel.get( + `${downloadSource.id}` + ); + if (existingSource) { + await downloadSourcesSublevel.put(`${downloadSource.id}`, { + ...existingSource, + objectIds: Array.from(objectIdsOnSource), + }); + } + + return Array.from(objectIdsOnSource); +}; + +export const importDownloadSourceToLocal = async ( + url: string, + throwOnDuplicate = false +) => { + const urlExists = await checkUrlExists(url); + if (urlExists) { + if (throwOnDuplicate) { + throw new Error("Download source with this URL already exists"); + } + return null; + } + + const response = await axios.get>(url); + + const steamGames = await getSteamGames(); + + const now = new Date(); + + const urlExistsBeforeInsert = await checkUrlExists(url); + if (urlExistsBeforeInsert) { + if (throwOnDuplicate) { + throw new Error("Download source with this URL already exists"); + } + return null; + } + + const nextId = await getNextId(downloadSourcesSublevel); + + const downloadSource = { + id: nextId, + url, + name: response.data.name, + etag: response.headers["etag"] || null, + status: DownloadSourceStatus.UpToDate, + downloadCount: response.data.downloads.length, + objectIds: [], + createdAt: now, + updatedAt: now, + }; + + await downloadSourcesSublevel.put(`${downloadSource.id}`, downloadSource); + + const objectIds = await addNewDownloads( + downloadSource, + response.data.downloads, + steamGames + ); + + return { + ...downloadSource, + objectIds, + }; +}; diff --git a/src/main/events/download-sources/put-download-source.ts b/src/main/events/download-sources/put-download-source.ts deleted file mode 100644 index 72297059..00000000 --- a/src/main/events/download-sources/put-download-source.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { HydraApi } from "@main/services"; -import { registerEvent } from "../register-event"; - -const putDownloadSource = async ( - _event: Electron.IpcMainInvokeEvent, - objectIds: string[] -) => { - return HydraApi.put<{ fingerprint: string }>( - "/download-sources", - { - objectIds, - }, - { needsAuth: false } - ); -}; - -registerEvent("putDownloadSource", putDownloadSource); diff --git a/src/main/events/download-sources/sync-download-sources-from-api.ts b/src/main/events/download-sources/sync-download-sources-from-api.ts new file mode 100644 index 00000000..21b5a097 --- /dev/null +++ b/src/main/events/download-sources/sync-download-sources-from-api.ts @@ -0,0 +1,26 @@ +import { HydraApi } from "@main/services"; +import { downloadSourcesSublevel } from "@main/level"; +import { importDownloadSourceToLocal } from "./helpers"; + +export const syncDownloadSourcesFromApi = async () => { + try { + const apiSources = await HydraApi.get< + { url: string; createdAt: string; updatedAt: string }[] + >("/profile/download-sources"); + + const localSources: { url: string }[] = []; + for await (const [, source] of downloadSourcesSublevel.iterator()) { + localSources.push(source); + } + + const localUrls = new Set(localSources.map((s) => s.url)); + + for (const apiSource of apiSources) { + if (!localUrls.has(apiSource.url)) { + await importDownloadSourceToLocal(apiSource.url, false); + } + } + } catch (error) { + console.error("Failed to sync download sources from API:", error); + } +}; diff --git a/src/main/events/download-sources/sync-download-sources.ts b/src/main/events/download-sources/sync-download-sources.ts new file mode 100644 index 00000000..8611aca9 --- /dev/null +++ b/src/main/events/download-sources/sync-download-sources.ts @@ -0,0 +1,267 @@ +import { registerEvent } from "../register-event"; +import axios, { AxiosError } from "axios"; +import { z } from "zod"; +import { downloadSourcesSublevel, repacksSublevel } from "@main/level"; +import { DownloadSourceStatus } from "@shared"; + +const downloadSourceSchema = z.object({ + name: z.string().max(255), + downloads: z.array( + z.object({ + title: z.string().max(255), + uris: z.array(z.string()), + uploadDate: z.string().max(255), + fileSize: z.string().max(255), + }) + ), +}); + +type SteamGamesByLetter = Record; + +const formatName = (name: string) => { + return name + .normalize("NFD") + .replace(/[\u0300-\u036f]/g, "") + .toLowerCase() + .replace(/[^a-z0-9]/g, ""); +}; + +const formatRepackName = (name: string) => { + return formatName(name.replace("[DL]", "")); +}; + +const checkUrlExists = async (url: string): Promise => { + for await (const [, source] of downloadSourcesSublevel.iterator()) { + if (source.url === url) { + return true; + } + } + return false; +}; + +const getSteamGames = async () => { + const response = await axios.get( + `${import.meta.env.MAIN_VITE_EXTERNAL_RESOURCES_URL}/steam-games-by-letter.json` + ); + + return response.data; +}; + +type SublevelIterator = AsyncIterable<[string, { id: number }]>; + +interface SublevelWithId { + iterator: () => SublevelIterator; +} + +const getNextId = async (sublevel: SublevelWithId): Promise => { + let maxId = 0; + for await (const [, value] of sublevel.iterator()) { + if (value.id > maxId) { + maxId = value.id; + } + } + return maxId + 1; +}; + +const addNewDownloads = async ( + downloadSource: { id: number; name: string }, + downloads: z.infer["downloads"], + steamGames: SteamGamesByLetter +) => { + const now = new Date(); + const objectIdsOnSource = new Set(); + + let nextRepackId = await getNextId(repacksSublevel); + + for (const download of downloads) { + const formattedTitle = formatRepackName(download.title); + const [firstLetter] = formattedTitle; + const games = steamGames[firstLetter] || []; + + const gamesInSteam = games.filter((game) => + formattedTitle.startsWith(formatName(game.name)) + ); + + if (gamesInSteam.length === 0) continue; + + for (const game of gamesInSteam) { + objectIdsOnSource.add(String(game.id)); + } + + const repack = { + id: nextRepackId++, + objectIds: gamesInSteam.map((game) => String(game.id)), + title: download.title, + uris: download.uris, + fileSize: download.fileSize, + repacker: downloadSource.name, + uploadDate: download.uploadDate, + downloadSourceId: downloadSource.id, + createdAt: now, + updatedAt: now, + }; + + await repacksSublevel.put(`${repack.id}`, repack); + } + + const existingSource = await downloadSourcesSublevel.get( + `${downloadSource.id}` + ); + if (existingSource) { + await downloadSourcesSublevel.put(`${downloadSource.id}`, { + ...existingSource, + objectIds: Array.from(objectIdsOnSource), + }); + } +}; + +const deleteDownloadSource = async (id: number) => { + const repacksToDelete: string[] = []; + + for await (const [key, repack] of repacksSublevel.iterator()) { + if (repack.downloadSourceId === id) { + repacksToDelete.push(key); + } + } + + const batch = repacksSublevel.batch(); + for (const key of repacksToDelete) { + batch.del(key); + } + await batch.write(); + + await downloadSourcesSublevel.del(`${id}`); +}; + +const importDownloadSource = async (url: string) => { + const urlExists = await checkUrlExists(url); + if (urlExists) { + return; + } + + const response = await axios.get>(url); + + const steamGames = await getSteamGames(); + + const now = new Date(); + + const urlExistsBeforeInsert = await checkUrlExists(url); + if (urlExistsBeforeInsert) { + return; + } + + const nextId = await getNextId(downloadSourcesSublevel); + + const downloadSource = { + id: nextId, + url, + name: response.data.name, + etag: response.headers["etag"] || null, + status: DownloadSourceStatus.UpToDate, + downloadCount: response.data.downloads.length, + objectIds: [], + createdAt: now, + updatedAt: now, + }; + + await downloadSourcesSublevel.put(`${downloadSource.id}`, downloadSource); + + await addNewDownloads(downloadSource, response.data.downloads, steamGames); +}; + +const syncDownloadSources = async ( + _event: Electron.IpcMainInvokeEvent +): Promise => { + let newRepacksCount = 0; + + try { + const downloadSources: Array<{ + id: number; + url: string; + name: string; + etag: string | null; + status: number; + downloadCount: number; + objectIds: string[]; + fingerprint?: string; + createdAt: Date; + updatedAt: Date; + }> = []; + for await (const [, source] of downloadSourcesSublevel.iterator()) { + downloadSources.push(source); + } + + const existingRepacks: Array<{ + id: number; + title: string; + uris: string[]; + repacker: string; + fileSize: string | null; + objectIds: string[]; + uploadDate: Date | string | null; + downloadSourceId: number; + createdAt: Date; + updatedAt: Date; + }> = []; + for await (const [, repack] of repacksSublevel.iterator()) { + existingRepacks.push(repack); + } + + if (downloadSources.some((source) => !source.fingerprint)) { + await Promise.all( + downloadSources.map(async (source) => { + await deleteDownloadSource(source.id); + await importDownloadSource(source.url); + }) + ); + } else { + for (const downloadSource of downloadSources) { + const headers: Record = {}; + + if (downloadSource.etag) { + headers["If-None-Match"] = downloadSource.etag; + } + + try { + const response = await axios.get(downloadSource.url, { + headers, + }); + + const source = downloadSourceSchema.parse(response.data); + const steamGames = await getSteamGames(); + + const repacks = source.downloads.filter( + (download) => + !existingRepacks.some((repack) => repack.title === download.title) + ); + + await downloadSourcesSublevel.put(`${downloadSource.id}`, { + ...downloadSource, + etag: response.headers["etag"] || null, + downloadCount: source.downloads.length, + status: DownloadSourceStatus.UpToDate, + }); + + await addNewDownloads(downloadSource, repacks, steamGames); + + newRepacksCount += repacks.length; + } catch (err: unknown) { + const isNotModified = (err as AxiosError).response?.status === 304; + + await downloadSourcesSublevel.put(`${downloadSource.id}`, { + ...downloadSource, + status: isNotModified + ? DownloadSourceStatus.UpToDate + : DownloadSourceStatus.Errored, + }); + } + } + } + + return newRepacksCount; + } catch (err) { + return -1; + } +}; + +registerEvent("syncDownloadSources", syncDownloadSources); diff --git a/src/main/events/download-sources/update-missing-fingerprints.ts b/src/main/events/download-sources/update-missing-fingerprints.ts new file mode 100644 index 00000000..ac748779 --- /dev/null +++ b/src/main/events/download-sources/update-missing-fingerprints.ts @@ -0,0 +1,67 @@ +import { registerEvent } from "../register-event"; +import { downloadSourcesSublevel } from "@main/level"; +import { HydraApi } from "@main/services"; + +const updateMissingFingerprints = async ( + _event: Electron.IpcMainInvokeEvent +): Promise => { + const sourcesNeedingFingerprints: Array<{ + id: number; + objectIds: string[]; + }> = []; + + for await (const [, source] of downloadSourcesSublevel.iterator()) { + if ( + !source.fingerprint && + source.objectIds && + source.objectIds.length > 0 + ) { + sourcesNeedingFingerprints.push({ + id: source.id, + objectIds: source.objectIds, + }); + } + } + + if (sourcesNeedingFingerprints.length === 0) { + return 0; + } + + console.log( + `Updating fingerprints for ${sourcesNeedingFingerprints.length} sources` + ); + + await Promise.all( + sourcesNeedingFingerprints.map(async (source) => { + try { + const { fingerprint } = await HydraApi.put<{ fingerprint: string }>( + "/download-sources", + { + objectIds: source.objectIds, + }, + { needsAuth: false } + ); + + const existingSource = await downloadSourcesSublevel.get( + `${source.id}` + ); + if (existingSource) { + await downloadSourcesSublevel.put(`${source.id}`, { + ...existingSource, + fingerprint, + updatedAt: new Date(), + }); + } + } catch (error) { + console.error( + `Failed to update fingerprint for source ${source.id}:`, + error + ); + } + }) + ); + + return sourcesNeedingFingerprints.length; +}; + +registerEvent("updateMissingFingerprints", updateMissingFingerprints); diff --git a/src/main/events/download-sources/validate-download-source.ts b/src/main/events/download-sources/validate-download-source.ts new file mode 100644 index 00000000..2bc86df7 --- /dev/null +++ b/src/main/events/download-sources/validate-download-source.ts @@ -0,0 +1,32 @@ +import { registerEvent } from "../register-event"; +import axios from "axios"; +import { z } from "zod"; + +const downloadSourceSchema = z.object({ + name: z.string().max(255), + downloads: z.array( + z.object({ + title: z.string().max(255), + uris: z.array(z.string()), + uploadDate: z.string().max(255), + fileSize: z.string().max(255), + }) + ), +}); + +const validateDownloadSource = async ( + _event: Electron.IpcMainInvokeEvent, + url: string +) => { + const response = await axios.get>(url); + + const { name } = downloadSourceSchema.parse(response.data); + + return { + name, + etag: response.headers["etag"] || null, + downloadCount: response.data.downloads.length, + }; +}; + +registerEvent("validateDownloadSource", validateDownloadSource); diff --git a/src/main/events/index.ts b/src/main/events/index.ts index b690e8a3..26c3b4bd 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -63,7 +63,15 @@ import "./autoupdater/restart-and-install-update"; import "./user-preferences/authenticate-real-debrid"; import "./user-preferences/authenticate-all-debrid"; import "./user-preferences/authenticate-torbox"; -import "./download-sources/put-download-source"; +import "./download-sources/add-download-source"; +import "./download-sources/update-missing-fingerprints"; +import "./download-sources/delete-download-source"; +import "./download-sources/delete-all-download-sources"; +import "./download-sources/validate-download-source"; +import "./download-sources/sync-download-sources"; +import "./download-sources/get-download-sources-list"; +import "./download-sources/check-download-source-exists"; +import "./repacks/get-all-repacks"; import "./auth/sign-out"; import "./auth/open-auth-window"; import "./auth/get-session-hash"; @@ -91,7 +99,6 @@ import "./themes/get-custom-theme-by-id"; import "./themes/get-active-custom-theme"; import "./themes/close-editor-window"; import "./themes/toggle-custom-theme"; -import "./download-sources/create-download-sources"; import "./download-sources/remove-download-source"; import "./download-sources/get-download-sources"; import { isPortableVersion } from "@main/helpers"; diff --git a/src/main/events/library/cleanup-unused-assets.ts b/src/main/events/library/cleanup-unused-assets.ts index 22490c07..d1d77e9f 100644 --- a/src/main/events/library/cleanup-unused-assets.ts +++ b/src/main/events/library/cleanup-unused-assets.ts @@ -19,7 +19,6 @@ const getAllCustomGameAssets = async (): Promise => { }; const getUsedAssetPaths = async (): Promise> => { - // Get all custom games from the level database const { gamesSublevel } = await import("@main/level"); const allGames = await gamesSublevel.iterator().all(); @@ -30,7 +29,6 @@ const getUsedAssetPaths = async (): Promise> => { const usedPaths = new Set(); customGames.forEach((game) => { - // Extract file paths from local URLs if (game.iconUrl?.startsWith("local:")) { usedPaths.add(game.iconUrl.replace("local:", "")); } diff --git a/src/main/events/library/copy-custom-game-asset.ts b/src/main/events/library/copy-custom-game-asset.ts index 07c3d6f7..aef74da9 100644 --- a/src/main/events/library/copy-custom-game-asset.ts +++ b/src/main/events/library/copy-custom-game-asset.ts @@ -13,29 +13,23 @@ const copyCustomGameAsset = async ( throw new Error("Source file does not exist"); } - // Ensure assets directory exists if (!fs.existsSync(ASSETS_PATH)) { fs.mkdirSync(ASSETS_PATH, { recursive: true }); } - // Create custom games assets subdirectory const customGamesAssetsPath = path.join(ASSETS_PATH, "custom-games"); if (!fs.existsSync(customGamesAssetsPath)) { fs.mkdirSync(customGamesAssetsPath, { recursive: true }); } - // Get file extension const fileExtension = path.extname(sourcePath); - // Generate unique filename const uniqueId = randomUUID(); const fileName = `${assetType}-${uniqueId}${fileExtension}`; const destinationPath = path.join(customGamesAssetsPath, fileName); - // Copy the file await fs.promises.copyFile(sourcePath, destinationPath); - // Return the local URL format return `local:${destinationPath}`; }; diff --git a/src/main/events/repacks/get-all-repacks.ts b/src/main/events/repacks/get-all-repacks.ts new file mode 100644 index 00000000..6eb83a39 --- /dev/null +++ b/src/main/events/repacks/get-all-repacks.ts @@ -0,0 +1,16 @@ +import { registerEvent } from "../register-event"; +import { repacksSublevel, GameRepack } from "@main/level"; + +const getAllRepacks = async (_event: Electron.IpcMainInvokeEvent) => { + const repacks: GameRepack[] = []; + + for await (const [, repack] of repacksSublevel.iterator()) { + if (Array.isArray(repack.objectIds)) { + repacks.push(repack); + } + } + + return repacks; +}; + +registerEvent("getAllRepacks", getAllRepacks); diff --git a/src/main/level/sublevels/download-sources.ts b/src/main/level/sublevels/download-sources.ts new file mode 100644 index 00000000..59104e3c --- /dev/null +++ b/src/main/level/sublevels/download-sources.ts @@ -0,0 +1,22 @@ +import { db } from "../level"; +import { levelKeys } from "./keys"; + +export interface DownloadSource { + id: number; + name: string; + url: string; + status: number; + objectIds: string[]; + downloadCount: number; + fingerprint?: string; + etag: string | null; + createdAt: Date; + updatedAt: Date; +} + +export const downloadSourcesSublevel = db.sublevel( + levelKeys.downloadSources, + { + valueEncoding: "json", + } +); diff --git a/src/main/level/sublevels/index.ts b/src/main/level/sublevels/index.ts index f78f09b8..7224fc64 100644 --- a/src/main/level/sublevels/index.ts +++ b/src/main/level/sublevels/index.ts @@ -6,3 +6,5 @@ export * from "./game-stats-cache"; export * from "./game-achievements"; export * from "./keys"; export * from "./themes"; +export * from "./download-sources"; +export * from "./repacks"; diff --git a/src/main/level/sublevels/keys.ts b/src/main/level/sublevels/keys.ts index bba35169..6faacd52 100644 --- a/src/main/level/sublevels/keys.ts +++ b/src/main/level/sublevels/keys.ts @@ -17,4 +17,6 @@ export const levelKeys = { language: "language", screenState: "screenState", rpcPassword: "rpcPassword", + downloadSources: "downloadSources", + repacks: "repacks", }; diff --git a/src/main/level/sublevels/repacks.ts b/src/main/level/sublevels/repacks.ts new file mode 100644 index 00000000..6257665b --- /dev/null +++ b/src/main/level/sublevels/repacks.ts @@ -0,0 +1,22 @@ +import { db } from "../level"; +import { levelKeys } from "./keys"; + +export interface GameRepack { + id: number; + title: string; + uris: string[]; + repacker: string; + fileSize: string | null; + objectIds: string[]; + uploadDate: Date | string | null; + downloadSourceId: number; + createdAt: Date; + updatedAt: Date; +} + +export const repacksSublevel = db.sublevel( + levelKeys.repacks, + { + valueEncoding: "json", + } +); diff --git a/src/main/services/hydra-api.ts b/src/main/services/hydra-api.ts index 138614b7..dd26e6f0 100644 --- a/src/main/services/hydra-api.ts +++ b/src/main/services/hydra-api.ts @@ -102,8 +102,14 @@ export class HydraApi { WindowManager.mainWindow.webContents.send("on-signin"); await clearGamesRemoteIds(); uploadGamesBatch(); + // WSClient.close(); // WSClient.connect(); + + const { syncDownloadSourcesFromApi } = await import( + "../events/download-sources/sync-download-sources-from-api" + ); + syncDownloadSourcesFromApi(); } } diff --git a/src/preload/index.ts b/src/preload/index.ts index 0368d661..527c4cd5 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -99,13 +99,24 @@ contextBridge.exposeInMainWorld("electron", { ipcRenderer.invoke("authenticateTorBox", apiToken), /* Download sources */ - putDownloadSource: (objectIds: string[]) => - ipcRenderer.invoke("putDownloadSource", objectIds), - createDownloadSources: (urls: string[]) => - ipcRenderer.invoke("createDownloadSources", urls), + addDownloadSource: (url: string) => + ipcRenderer.invoke("addDownloadSource", url), + updateMissingFingerprints: () => + ipcRenderer.invoke("updateMissingFingerprints"), removeDownloadSource: (url: string, removeAll?: boolean) => ipcRenderer.invoke("removeDownloadSource", url, removeAll), getDownloadSources: () => ipcRenderer.invoke("getDownloadSources"), + deleteDownloadSource: (id: number) => + ipcRenderer.invoke("deleteDownloadSource", id), + deleteAllDownloadSources: () => + ipcRenderer.invoke("deleteAllDownloadSources"), + validateDownloadSource: (url: string) => + ipcRenderer.invoke("validateDownloadSource", url), + syncDownloadSources: () => ipcRenderer.invoke("syncDownloadSources"), + getDownloadSourcesList: () => ipcRenderer.invoke("getDownloadSourcesList"), + checkDownloadSourceExists: (url: string) => + ipcRenderer.invoke("checkDownloadSourceExists", url), + getAllRepacks: () => ipcRenderer.invoke("getAllRepacks"), /* Library */ toggleAutomaticCloudSync: ( diff --git a/src/renderer/src/app.tsx b/src/renderer/src/app.tsx index 0a8ee1e2..74a2a97e 100644 --- a/src/renderer/src/app.tsx +++ b/src/renderer/src/app.tsx @@ -20,14 +20,12 @@ import { setUserDetails, setProfileBackground, setGameRunning, + setIsImportingSources, } from "@renderer/features"; import { useTranslation } from "react-i18next"; import { UserFriendModal } from "./pages/shared-modals/user-friend-modal"; -import { downloadSourcesWorker } from "./workers"; -import { downloadSourcesTable } from "./dexie"; import { useSubscription } from "./hooks/use-subscription"; import { HydraCloudModal } from "./pages/shared-modals/hydra-cloud/hydra-cloud-modal"; -import { generateUUID } from "./helpers"; import { injectCustomCss, removeCustomCss } from "./helpers"; import "./app.scss"; @@ -137,15 +135,6 @@ export function App() { }, [fetchUserDetails, updateUserDetails, dispatch]); const onSignIn = useCallback(() => { - window.electron.getDownloadSources().then((sources) => { - sources.forEach((source) => { - downloadSourcesWorker.postMessage([ - "IMPORT_DOWNLOAD_SOURCE", - source.url, - ]); - }); - }); - fetchUserDetails().then((response) => { if (response) { updateUserDetails(response); @@ -211,41 +200,34 @@ export function App() { }, [dispatch, draggingDisabled]); useEffect(() => { - updateRepacks(); + (async () => { + dispatch(setIsImportingSources(true)); - const id = generateUUID(); - const channel = new BroadcastChannel(`download_sources:sync:${id}`); + try { + // Initial repacks load + await updateRepacks(); - channel.onmessage = async (event: MessageEvent) => { - const newRepacksCount = event.data; - window.electron.publishNewRepacksNotification(newRepacksCount); - updateRepacks(); + // Sync all local sources (check for updates) + const newRepacksCount = await window.electron.syncDownloadSources(); - const downloadSources = await downloadSourcesTable.toArray(); + if (newRepacksCount > 0) { + window.electron.publishNewRepacksNotification(newRepacksCount); + } - await Promise.all( - downloadSources - .filter((source) => !source.fingerprint) - .map(async (downloadSource) => { - const { fingerprint } = await window.electron.putDownloadSource( - downloadSource.objectIds - ); + // Update fingerprints for sources that don't have them + await window.electron.updateMissingFingerprints(); - return downloadSourcesTable.update(downloadSource.id, { - fingerprint, - }); - }) - ); - - channel.close(); - }; - - downloadSourcesWorker.postMessage(["SYNC_DOWNLOAD_SOURCES", id]); - - return () => { - channel.close(); - }; - }, [updateRepacks]); + // Update repacks AFTER all syncing and fingerprint updates are complete + await updateRepacks(); + } catch (error) { + console.error("Error syncing download sources:", error); + // Still update repacks even if sync fails + await updateRepacks(); + } finally { + dispatch(setIsImportingSources(false)); + } + })(); + }, [updateRepacks, dispatch]); const loadAndApplyTheme = useCallback(async () => { const activeTheme = await window.electron.getActiveCustomTheme(); diff --git a/src/renderer/src/components/game-card/game-card.tsx b/src/renderer/src/components/game-card/game-card.tsx index 6e790500..5752ba19 100644 --- a/src/renderer/src/components/game-card/game-card.tsx +++ b/src/renderer/src/components/game-card/game-card.tsx @@ -109,12 +109,7 @@ export function GameCard({ game, ...props }: GameCardProps) {
    - +
    diff --git a/src/renderer/src/components/star-rating/star-rating.tsx b/src/renderer/src/components/star-rating/star-rating.tsx index 50d08afd..5a10b140 100644 --- a/src/renderer/src/components/star-rating/star-rating.tsx +++ b/src/renderer/src/components/star-rating/star-rating.tsx @@ -1,76 +1,31 @@ -import { StarIcon, StarFillIcon } from "@primer/octicons-react"; +import { StarFillIcon } from "@primer/octicons-react"; import "./star-rating.scss"; export interface StarRatingProps { rating: number | null; - maxStars?: number; size?: number; - showCalculating?: boolean; - calculatingText?: string; - hideIcon?: boolean; } -export function StarRating({ - rating, - maxStars = 5, - size = 12, - showCalculating = false, - calculatingText = "Calculating", - hideIcon = false, -}: Readonly) { - if (rating === null && showCalculating) { - return ( -
    - {!hideIcon && } - {calculatingText} -
    - ); - } - +export function StarRating({ rating, size = 12 }: Readonly) { if (rating === null || rating === undefined) { return ( -
    - {!hideIcon && } - -
    - ); - } - - const filledStars = Math.floor(rating); - const hasHalfStar = rating % 1 >= 0.5; - const emptyStars = maxStars - filledStars - (hasHalfStar ? 1 : 0); - - return ( -
    - {Array.from({ length: filledStars }, (_, index) => ( +
    - ))} - - {hasHalfStar && ( -
    - - -
    - )} - - {Array.from({ length: emptyStars }, (_, index) => ( - - ))} + +
    + ); + } + // Always use single star mode with numeric score + return ( +
    + {rating.toFixed(1)}
    ); diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 7bbecd97..8de36342 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -31,6 +31,9 @@ import type { AchievementNotificationInfo, Game, DiskUsage, + DownloadSource, + DownloadSourceValidationResult, + GameRepack, } from "@types"; import type { AxiosProgressEvent } from "axios"; @@ -210,14 +213,21 @@ declare global { createSteamShortcut: (shop: GameShop, objectId: string) => Promise; /* Download sources */ - putDownloadSource: ( - objectIds: string[] - ) => Promise<{ fingerprint: string }>; - createDownloadSources: (urls: string[]) => Promise; + addDownloadSource: (url: string) => Promise; + updateMissingFingerprints: () => Promise; removeDownloadSource: (url: string, removeAll?: boolean) => Promise; getDownloadSources: () => Promise< Pick[] >; + deleteDownloadSource: (id: number) => Promise; + deleteAllDownloadSources: () => Promise; + validateDownloadSource: ( + url: string + ) => Promise; + syncDownloadSources: () => Promise; + getDownloadSourcesList: () => Promise; + checkDownloadSourceExists: (url: string) => Promise; + getAllRepacks: () => Promise; /* Hardware */ getDiskFreeSpace: (path: string) => Promise; diff --git a/src/renderer/src/dexie.ts b/src/renderer/src/dexie.ts deleted file mode 100644 index 7991dc8a..00000000 --- a/src/renderer/src/dexie.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { GameShop, HowLongToBeatCategory } from "@types"; -import { Dexie } from "dexie"; - -export interface HowLongToBeatEntry { - id?: number; - objectId: string; - categories: HowLongToBeatCategory[]; - shop: GameShop; - createdAt: Date; - updatedAt: Date; -} - -export const db = new Dexie("Hydra"); - -db.version(9).stores({ - repacks: `++id, title, uris, fileSize, uploadDate, downloadSourceId, repacker, objectIds, createdAt, updatedAt`, - downloadSources: `++id, &url, name, etag, objectIds, downloadCount, status, fingerprint, createdAt, updatedAt`, - howLongToBeatEntries: `++id, categories, [shop+objectId], createdAt, updatedAt`, -}); - -export const downloadSourcesTable = db.table("downloadSources"); -export const repacksTable = db.table("repacks"); -export const howLongToBeatEntriesTable = db.table( - "howLongToBeatEntries" -); - -db.open(); diff --git a/src/renderer/src/features/download-sources-slice.ts b/src/renderer/src/features/download-sources-slice.ts new file mode 100644 index 00000000..52e58d26 --- /dev/null +++ b/src/renderer/src/features/download-sources-slice.ts @@ -0,0 +1,21 @@ +import { createSlice } from "@reduxjs/toolkit"; + +export interface DownloadSourcesState { + isImporting: boolean; +} + +const initialState: DownloadSourcesState = { + isImporting: false, +}; + +export const downloadSourcesSlice = createSlice({ + name: "downloadSources", + initialState, + reducers: { + setIsImportingSources: (state, action) => { + state.isImporting = action.payload; + }, + }, +}); + +export const { setIsImportingSources } = downloadSourcesSlice.actions; diff --git a/src/renderer/src/features/index.ts b/src/renderer/src/features/index.ts index 9d48c0df..3b602cff 100644 --- a/src/renderer/src/features/index.ts +++ b/src/renderer/src/features/index.ts @@ -7,4 +7,5 @@ export * from "./user-details-slice"; export * from "./game-running.slice"; export * from "./subscription-slice"; export * from "./repacks-slice"; +export * from "./download-sources-slice"; export * from "./catalogue-search"; diff --git a/src/renderer/src/hooks/use-repacks.ts b/src/renderer/src/hooks/use-repacks.ts index dbc918b9..c024aaa4 100644 --- a/src/renderer/src/hooks/use-repacks.ts +++ b/src/renderer/src/hooks/use-repacks.ts @@ -1,4 +1,3 @@ -import { repacksTable } from "@renderer/dexie"; import { setRepacks } from "@renderer/features"; import { useCallback } from "react"; import { RootState } from "@renderer/store"; @@ -16,18 +15,11 @@ export function useRepacks() { [repacks] ); - const updateRepacks = useCallback(() => { - repacksTable.toArray().then((repacks) => { - dispatch( - setRepacks( - JSON.parse( - JSON.stringify( - repacks.filter((repack) => Array.isArray(repack.objectIds)) - ) - ) - ) - ); - }); + const updateRepacks = useCallback(async () => { + const repacks = await window.electron.getAllRepacks(); + dispatch( + setRepacks(repacks.filter((repack) => Array.isArray(repack.objectIds))) + ); }, [dispatch]); return { getRepacksForObjectId, updateRepacks }; diff --git a/src/renderer/src/pages/catalogue/catalogue.tsx b/src/renderer/src/pages/catalogue/catalogue.tsx index 3c6a2b80..07bcf3ff 100644 --- a/src/renderer/src/pages/catalogue/catalogue.tsx +++ b/src/renderer/src/pages/catalogue/catalogue.tsx @@ -1,16 +1,10 @@ import type { CatalogueSearchResult, DownloadSource } from "@types"; -import { - useAppDispatch, - useAppSelector, - useFormat, - useRepacks, -} from "@renderer/hooks"; +import { useAppDispatch, useAppSelector, useFormat } from "@renderer/hooks"; import { useEffect, useMemo, useRef, useState } from "react"; import "./catalogue.scss"; -import { downloadSourcesTable } from "@renderer/dexie"; import { FilterSection } from "./filter-section"; import { setFilters, setPage } from "@renderer/features"; import { useTranslation } from "react-i18next"; @@ -56,8 +50,6 @@ export default function Catalogue() { const { t, i18n } = useTranslation("catalogue"); - const { getRepacksForObjectId } = useRepacks(); - const debouncedSearch = useRef( debounce(async (filters, pageSize, offset) => { const abortController = new AbortController(); @@ -95,10 +87,10 @@ export default function Catalogue() { }, [filters, page, debouncedSearch]); useEffect(() => { - downloadSourcesTable.toArray().then((sources) => { + window.electron.getDownloadSourcesList().then((sources) => { setDownloadSources(sources.filter((source) => !!source.fingerprint)); }); - }, [getRepacksForObjectId]); + }, []); const language = i18n.language.split("-")[0]; @@ -192,13 +184,15 @@ export default function Catalogue() { }, { title: t("download_sources"), - items: downloadSources.map((source) => ({ - label: source.name, - value: source.fingerprint, - checked: filters.downloadSourceFingerprints.includes( - source.fingerprint - ), - })), + items: downloadSources + .filter((source) => source.fingerprint) + .map((source) => ({ + label: source.name, + value: source.fingerprint!, + checked: filters.downloadSourceFingerprints.includes( + source.fingerprint! + ), + })), key: "downloadSourceFingerprints", }, { diff --git a/src/renderer/src/pages/game-details/modals/edit-game-modal.tsx b/src/renderer/src/pages/game-details/modals/edit-game-modal.tsx index 2de46304..8fbdf4a2 100644 --- a/src/renderer/src/pages/game-details/modals/edit-game-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/edit-game-modal.tsx @@ -19,6 +19,68 @@ export interface EditGameModalProps { type AssetType = "icon" | "logo" | "hero"; +interface ElectronFile extends File { + path?: string; +} + +interface GameWithOriginalAssets extends Game { + originalIconPath?: string; + originalLogoPath?: string; + originalHeroPath?: string; +} + +interface LibraryGameWithCustomOriginalAssets extends LibraryGame { + customOriginalIconPath?: string; + customOriginalLogoPath?: string; + customOriginalHeroPath?: string; +} + +interface AssetPaths { + icon: string; + logo: string; + hero: string; +} + +interface AssetUrls { + icon: string | null; + logo: string | null; + hero: string | null; +} + +interface RemovedAssets { + icon: boolean; + logo: boolean; + hero: boolean; +} + +const VALID_IMAGE_TYPES = [ + "image/jpeg", + "image/jpg", + "image/png", + "image/gif", + "image/webp", +] as const; + +const IMAGE_EXTENSIONS = ["jpg", "jpeg", "png", "gif", "webp"] as const; + +const INITIAL_ASSET_PATHS: AssetPaths = { + icon: "", + logo: "", + hero: "", +}; + +const INITIAL_REMOVED_ASSETS: RemovedAssets = { + icon: false, + logo: false, + hero: false, +}; + +const INITIAL_ASSET_URLS: AssetUrls = { + icon: null, + logo: null, + hero: null, +}; + export function EditGameModal({ visible, onClose, @@ -30,33 +92,18 @@ export function EditGameModal({ const { showSuccessToast, showErrorToast } = useToast(); const [gameName, setGameName] = useState(""); - const [assetPaths, setAssetPaths] = useState({ - icon: "", - logo: "", - hero: "", - }); - const [assetDisplayPaths, setAssetDisplayPaths] = useState({ - icon: "", - logo: "", - hero: "", - }); - const [originalAssetPaths, setOriginalAssetPaths] = useState({ - icon: "", - logo: "", - hero: "", - }); - const [removedAssets, setRemovedAssets] = useState({ - icon: false, - logo: false, - hero: false, - }); - const [defaultUrls, setDefaultUrls] = useState({ - icon: null as string | null, - logo: null as string | null, - hero: null as string | null, - }); + const [assetPaths, setAssetPaths] = useState(INITIAL_ASSET_PATHS); + const [assetDisplayPaths, setAssetDisplayPaths] = + useState(INITIAL_ASSET_PATHS); + const [originalAssetPaths, setOriginalAssetPaths] = + useState(INITIAL_ASSET_PATHS); + const [removedAssets, setRemovedAssets] = useState( + INITIAL_REMOVED_ASSETS + ); + const [defaultUrls, setDefaultUrls] = useState(INITIAL_ASSET_URLS); const [isUpdating, setIsUpdating] = useState(false); const [selectedAssetType, setSelectedAssetType] = useState("icon"); + const [dragOverTarget, setDragOverTarget] = useState(null); const isCustomGame = (game: LibraryGame | Game): boolean => { return game.shop === "custom"; @@ -66,12 +113,18 @@ export function EditGameModal({ return url?.startsWith("local:") ? url.replace("local:", "") : ""; }; + const capitalizeAssetType = (assetType: AssetType): string => { + return assetType.charAt(0).toUpperCase() + assetType.slice(1); + }; + const setCustomGameAssets = useCallback((game: LibraryGame | Game) => { - // Check if assets were removed (URLs are null but original paths exist) - const iconRemoved = !game.iconUrl && (game as any).originalIconPath; - const logoRemoved = !game.logoImageUrl && (game as any).originalLogoPath; + const gameWithAssets = game as GameWithOriginalAssets; + const iconRemoved = + !game.iconUrl && Boolean(gameWithAssets.originalIconPath); + const logoRemoved = + !game.logoImageUrl && Boolean(gameWithAssets.originalLogoPath); const heroRemoved = - !game.libraryHeroImageUrl && (game as any).originalHeroPath; + !game.libraryHeroImageUrl && Boolean(gameWithAssets.originalHeroPath); setAssetPaths({ icon: extractLocalPath(game.iconUrl), @@ -84,15 +137,14 @@ export function EditGameModal({ hero: extractLocalPath(game.libraryHeroImageUrl), }); setOriginalAssetPaths({ - icon: (game as any).originalIconPath || extractLocalPath(game.iconUrl), + icon: gameWithAssets.originalIconPath || extractLocalPath(game.iconUrl), logo: - (game as any).originalLogoPath || extractLocalPath(game.logoImageUrl), + gameWithAssets.originalLogoPath || extractLocalPath(game.logoImageUrl), hero: - (game as any).originalHeroPath || + gameWithAssets.originalHeroPath || extractLocalPath(game.libraryHeroImageUrl), }); - // Set removed assets state based on whether assets were explicitly removed setRemovedAssets({ icon: iconRemoved, logo: logoRemoved, @@ -102,13 +154,15 @@ export function EditGameModal({ const setNonCustomGameAssets = useCallback( (game: LibraryGame) => { - // Check if assets were removed (custom URLs are null but original paths exist) + const gameWithAssets = game as LibraryGameWithCustomOriginalAssets; const iconRemoved = - !game.customIconUrl && (game as any).customOriginalIconPath; + !game.customIconUrl && Boolean(gameWithAssets.customOriginalIconPath); const logoRemoved = - !game.customLogoImageUrl && (game as any).customOriginalLogoPath; + !game.customLogoImageUrl && + Boolean(gameWithAssets.customOriginalLogoPath); const heroRemoved = - !game.customHeroImageUrl && (game as any).customOriginalHeroPath; + !game.customHeroImageUrl && + Boolean(gameWithAssets.customOriginalHeroPath); setAssetPaths({ icon: extractLocalPath(game.customIconUrl), @@ -122,17 +176,16 @@ export function EditGameModal({ }); setOriginalAssetPaths({ icon: - (game as any).customOriginalIconPath || + gameWithAssets.customOriginalIconPath || extractLocalPath(game.customIconUrl), logo: - (game as any).customOriginalLogoPath || + gameWithAssets.customOriginalLogoPath || extractLocalPath(game.customLogoImageUrl), hero: - (game as any).customOriginalHeroPath || + gameWithAssets.customOriginalHeroPath || extractLocalPath(game.customHeroImageUrl), }); - // Set removed assets state based on whether assets were explicitly removed setRemovedAssets({ icon: iconRemoved, logo: logoRemoved, @@ -171,29 +224,22 @@ export function EditGameModal({ setSelectedAssetType(assetType); }; - const getAssetPath = (assetType: AssetType): string => { - return assetPaths[assetType]; - }; - const getAssetDisplayPath = (assetType: AssetType): string => { - // If asset was removed, don't show any path if (removedAssets[assetType]) { return ""; } - // Use display path first, then fall back to original path return assetDisplayPaths[assetType] || originalAssetPaths[assetType]; }; - const setAssetPath = (assetType: AssetType, path: string): void => { + const updateAssetPaths = ( + assetType: AssetType, + path: string, + displayPath: string + ): void => { setAssetPaths((prev) => ({ ...prev, [assetType]: path })); - }; - - const setAssetDisplayPath = (assetType: AssetType, path: string): void => { - setAssetDisplayPaths((prev) => ({ ...prev, [assetType]: path })); - }; - - const getDefaultUrl = (assetType: AssetType): string | null => { - return defaultUrls[assetType]; + setAssetDisplayPaths((prev) => ({ ...prev, [assetType]: displayPath })); + setOriginalAssetPaths((prev) => ({ ...prev, [assetType]: displayPath })); + setRemovedAssets((prev) => ({ ...prev, [assetType]: false })); }; const getOriginalAssetUrl = (assetType: AssetType): string | null => { @@ -217,7 +263,7 @@ export function EditGameModal({ filters: [ { name: t("edit_game_modal_image_filter"), - extensions: ["jpg", "jpeg", "png", "gif", "webp"], + extensions: [...IMAGE_EXTENSIONS], }, ], }); @@ -229,41 +275,26 @@ export function EditGameModal({ originalPath, assetType ); - setAssetPath(assetType, copiedAssetUrl.replace("local:", "")); - setAssetDisplayPath(assetType, originalPath); - // Store the original path for display purposes - setOriginalAssetPaths((prev) => ({ - ...prev, - [assetType]: originalPath, - })); - // Clear the removed flag when a new asset is selected - setRemovedAssets((prev) => ({ ...prev, [assetType]: false })); + updateAssetPaths( + assetType, + copiedAssetUrl.replace("local:", ""), + originalPath + ); } catch (error) { console.error(`Failed to copy ${assetType} asset:`, error); - setAssetPath(assetType, originalPath); - setAssetDisplayPath(assetType, originalPath); - setOriginalAssetPaths((prev) => ({ - ...prev, - [assetType]: originalPath, - })); - // Clear the removed flag when a new asset is selected - setRemovedAssets((prev) => ({ ...prev, [assetType]: false })); + updateAssetPaths(assetType, originalPath, originalPath); } } }; const handleRestoreDefault = (assetType: AssetType) => { - // Mark asset as removed and clear paths (for both custom and non-custom games) setRemovedAssets((prev) => ({ ...prev, [assetType]: true })); - setAssetPath(assetType, ""); - setAssetDisplayPath(assetType, ""); - // Don't clear originalAssetPaths - keep them for reference but don't use them for display + setAssetPaths((prev) => ({ ...prev, [assetType]: "" })); + setAssetDisplayPaths((prev) => ({ ...prev, [assetType]: "" })); }; const getOriginalTitle = (): string => { if (!game) return ""; - - // For non-custom games, the original title is from shopDetails assets return shopDetails?.assets?.title || game.title || ""; }; @@ -274,12 +305,10 @@ export function EditGameModal({ const isTitleChanged = useMemo((): boolean => { if (!game || isCustomGame(game)) return false; - const originalTitle = getOriginalTitle(); + const originalTitle = shopDetails?.assets?.title || game.title || ""; return gameName.trim() !== originalTitle.trim(); }, [game, gameName, shopDetails]); - const [dragOverTarget, setDragOverTarget] = useState(null); - const handleDragOver = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); @@ -300,14 +329,9 @@ export function EditGameModal({ }; const validateImageFile = (file: File): boolean => { - const validTypes = [ - "image/jpeg", - "image/jpg", - "image/png", - "image/gif", - "image/webp", - ]; - return validTypes.includes(file.type); + return VALID_IMAGE_TYPES.includes( + file.type as (typeof VALID_IMAGE_TYPES)[number] + ); }; const processDroppedFile = async (file: File, assetType: AssetType) => { @@ -321,10 +345,6 @@ export function EditGameModal({ try { let filePath: string; - interface ElectronFile extends File { - path?: string; - } - if ("path" in file && typeof (file as ElectronFile).path === "string") { filePath = (file as ElectronFile).path!; } else { @@ -351,12 +371,13 @@ export function EditGameModal({ assetType ); - const assetPath = copiedAssetUrl.replace("local:", ""); - setAssetPath(assetType, assetPath); - setAssetDisplayPath(assetType, filePath); - + updateAssetPaths( + assetType, + copiedAssetUrl.replace("local:", ""), + filePath + ); showSuccessToast( - `${assetType.charAt(0).toUpperCase() + assetType.slice(1)} updated successfully!` + `${capitalizeAssetType(assetType)} updated successfully!` ); if (!("path" in file) && filePath) { @@ -387,63 +408,45 @@ export function EditGameModal({ } }; - // Helper function to prepare custom game assets const prepareCustomGameAssets = (game: LibraryGame | Game) => { - // For custom games, check if asset was explicitly removed - let iconUrl; - if (removedAssets.icon) { - iconUrl = null; - } else if (assetPaths.icon) { - iconUrl = `local:${assetPaths.icon}`; - } else { - iconUrl = game.iconUrl; - } + const iconUrl = removedAssets.icon + ? null + : assetPaths.icon + ? `local:${assetPaths.icon}` + : game.iconUrl; - let logoImageUrl; - if (removedAssets.logo) { - logoImageUrl = null; - } else if (assetPaths.logo) { - logoImageUrl = `local:${assetPaths.logo}`; - } else { - logoImageUrl = game.logoImageUrl; - } + const logoImageUrl = removedAssets.logo + ? null + : assetPaths.logo + ? `local:${assetPaths.logo}` + : game.logoImageUrl; - // For hero image, if removed, restore to the original gradient or keep the original - let libraryHeroImageUrl; - if (removedAssets.hero) { - // If the original hero was a gradient (data URL), keep it, otherwise generate a new one - const originalHero = game.libraryHeroImageUrl; - libraryHeroImageUrl = originalHero?.startsWith("data:image/svg+xml") - ? originalHero - : generateRandomGradient(); - } else { - libraryHeroImageUrl = assetPaths.hero + const libraryHeroImageUrl = removedAssets.hero + ? game.libraryHeroImageUrl?.startsWith("data:image/svg+xml") + ? game.libraryHeroImageUrl + : generateRandomGradient() + : assetPaths.hero ? `local:${assetPaths.hero}` : game.libraryHeroImageUrl; - } return { iconUrl, logoImageUrl, libraryHeroImageUrl }; }; - // Helper function to prepare non-custom game assets const prepareNonCustomGameAssets = () => { - const hasIconPath = assetPaths.icon; - let customIconUrl: string | null = null; - if (!removedAssets.icon && hasIconPath) { - customIconUrl = `local:${assetPaths.icon}`; - } + const customIconUrl = + !removedAssets.icon && assetPaths.icon + ? `local:${assetPaths.icon}` + : null; - const hasLogoPath = assetPaths.logo; - let customLogoImageUrl: string | null = null; - if (!removedAssets.logo && hasLogoPath) { - customLogoImageUrl = `local:${assetPaths.logo}`; - } + const customLogoImageUrl = + !removedAssets.logo && assetPaths.logo + ? `local:${assetPaths.logo}` + : null; - const hasHeroPath = assetPaths.hero; - let customHeroImageUrl: string | null = null; - if (!removedAssets.hero && hasHeroPath) { - customHeroImageUrl = `local:${assetPaths.hero}`; - } + const customHeroImageUrl = + !removedAssets.hero && assetPaths.hero + ? `local:${assetPaths.hero}` + : null; return { customIconUrl, @@ -452,7 +455,6 @@ export function EditGameModal({ }; }; - // Helper function to update custom game const updateCustomGame = async (game: LibraryGame | Game) => { const { iconUrl, logoImageUrl, libraryHeroImageUrl } = prepareCustomGameAssets(game); @@ -470,7 +472,6 @@ export function EditGameModal({ }); }; - // Helper function to update non-custom game const updateNonCustomGame = async (game: LibraryGame) => { const { customIconUrl, customLogoImageUrl, customHeroImageUrl } = prepareNonCustomGameAssets(); @@ -521,43 +522,17 @@ export function EditGameModal({ } }; - // Helper function to reset form to initial state const resetFormToInitialState = useCallback( (game: LibraryGame | Game) => { setGameName(game.title || ""); - - // Reset removed assets state - setRemovedAssets({ - icon: false, - logo: false, - hero: false, - }); - - // Clear all asset paths to ensure clean state - setAssetPaths({ - icon: "", - logo: "", - hero: "", - }); - setAssetDisplayPaths({ - icon: "", - logo: "", - hero: "", - }); - setOriginalAssetPaths({ - icon: "", - logo: "", - hero: "", - }); + setRemovedAssets(INITIAL_REMOVED_ASSETS); + setAssetPaths(INITIAL_ASSET_PATHS); + setAssetDisplayPaths(INITIAL_ASSET_PATHS); + setOriginalAssetPaths(INITIAL_ASSET_PATHS); if (isCustomGame(game)) { setCustomGameAssets(game); - // Clear default URLs for custom games - setDefaultUrls({ - icon: null, - logo: null, - hero: null, - }); + setDefaultUrls(INITIAL_ASSET_URLS); } else { setNonCustomGameAssets(game as LibraryGame); } @@ -575,8 +550,8 @@ export function EditGameModal({ const isFormValid = gameName.trim(); const getPreviewUrl = (assetType: AssetType): string | undefined => { - const assetPath = getAssetPath(assetType); - const defaultUrl = getDefaultUrl(assetType); + const assetPath = assetPaths[assetType]; + const defaultUrl = defaultUrls[assetType]; if (game && !isCustomGame(game)) { return assetPath ? `local:${assetPath}` : defaultUrl || undefined; @@ -585,9 +560,9 @@ export function EditGameModal({ }; const renderImageSection = (assetType: AssetType) => { - const assetPath = getAssetPath(assetType); + const assetPath = assetPaths[assetType]; const assetDisplayPath = getAssetDisplayPath(assetType); - const defaultUrl = getDefaultUrl(assetType); + const defaultUrl = defaultUrls[assetType]; const hasImage = assetPath || (game && !isCustomGame(game) && defaultUrl); const isDragOver = dragOverTarget === assetType; diff --git a/src/renderer/src/pages/game-details/modals/repacks-modal.tsx b/src/renderer/src/pages/game-details/modals/repacks-modal.tsx index f2d1f974..0c4c2d9e 100644 --- a/src/renderer/src/pages/game-details/modals/repacks-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/repacks-modal.tsx @@ -15,7 +15,6 @@ import { TextField, CheckboxField, } from "@renderer/components"; -import { downloadSourcesTable } from "@renderer/dexie"; import type { DownloadSource } from "@types"; import type { GameRepack } from "@types"; @@ -105,7 +104,7 @@ export function RepacksModal({ }, [repacks, hashesInDebrid]); useEffect(() => { - downloadSourcesTable.toArray().then((sources) => { + window.electron.getDownloadSourcesList().then((sources) => { const uniqueRepackers = new Set(sortedRepacks.map((r) => r.repacker)); const filteredSources = sources.filter( (s) => s.name && uniqueRepackers.has(s.name) && !!s.fingerprint @@ -129,6 +128,7 @@ export function RepacksModal({ return downloadSources.some( (src) => + src.fingerprint && selectedFingerprints.includes(src.fingerprint) && src.name === repack.repacker ); diff --git a/src/renderer/src/pages/game-details/sidebar/how-long-to-beat-section.tsx b/src/renderer/src/pages/game-details/sidebar/how-long-to-beat-section.tsx index 9a29f150..61c90389 100644 --- a/src/renderer/src/pages/game-details/sidebar/how-long-to-beat-section.tsx +++ b/src/renderer/src/pages/game-details/sidebar/how-long-to-beat-section.tsx @@ -25,7 +25,7 @@ export function HowLongToBeatSection({ return `${value} ${t(durationTranslation[unit])}`; }; - if (!howLongToBeatData || !isLoading) return null; + if (!howLongToBeatData && !isLoading) return null; return ( diff --git a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx index d16f4d3f..3056e414 100755 --- a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx +++ b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx @@ -17,7 +17,6 @@ import { StarIcon, } from "@primer/octicons-react"; import { HowLongToBeatSection } from "./how-long-to-beat-section"; -import { howLongToBeatEntriesTable } from "@renderer/dexie"; import { SidebarSection } from "../sidebar-section/sidebar-section"; import { buildGameAchievementPath } from "@renderer/helpers"; import { useSubscription } from "@renderer/hooks/use-subscription"; @@ -80,41 +79,22 @@ export function Sidebar() { if (objectId) { setHowLongToBeat({ isLoading: true, data: null }); - howLongToBeatEntriesTable - .where({ shop, objectId }) - .first() - .then(async (cachedHowLongToBeat) => { - if (cachedHowLongToBeat) { - setHowLongToBeat({ - isLoading: false, - data: cachedHowLongToBeat.categories, - }); - } else { - try { - const howLongToBeat = await window.electron.hydraApi.get< - HowLongToBeatCategory[] | null - >(`/games/${shop}/${objectId}/how-long-to-beat`, { - needsAuth: false, - }); - - if (howLongToBeat) { - howLongToBeatEntriesTable.add({ - objectId, - shop: "steam", - createdAt: new Date(), - updatedAt: new Date(), - categories: howLongToBeat, - }); - } - - setHowLongToBeat({ isLoading: false, data: howLongToBeat }); - } catch (err) { - setHowLongToBeat({ isLoading: false, data: null }); - } + // Directly fetch from API without checking cache + window.electron.hydraApi + .get( + `/games/${shop}/${objectId}/how-long-to-beat`, + { + needsAuth: false, } + ) + .then((howLongToBeatData) => { + setHowLongToBeat({ isLoading: false, data: howLongToBeatData }); + }) + .catch(() => { + setHowLongToBeat({ isLoading: false, data: null }); }); } - }, [objectId, shop, gameTitle]); + }, [objectId, shop]); return (
    diff --git a/src/renderer/src/pages/settings/add-download-source-modal.scss b/src/renderer/src/pages/settings/add-download-source-modal.scss index f6b5d151..ea92ca71 100644 --- a/src/renderer/src/pages/settings/add-download-source-modal.scss +++ b/src/renderer/src/pages/settings/add-download-source-modal.scss @@ -1,5 +1,14 @@ @use "../../scss/globals.scss"; +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + .add-download-source-modal { &__container { display: flex; @@ -24,4 +33,9 @@ &__validate-button { align-self: flex-end; } + + &__spinner { + animation: spin 1s linear infinite; + margin-right: calc(globals.$spacing-unit / 2); + } } diff --git a/src/renderer/src/pages/settings/add-download-source-modal.tsx b/src/renderer/src/pages/settings/add-download-source-modal.tsx index fee1c5e3..c2b47513 100644 --- a/src/renderer/src/pages/settings/add-download-source-modal.tsx +++ b/src/renderer/src/pages/settings/add-download-source-modal.tsx @@ -4,12 +4,13 @@ import { useTranslation } from "react-i18next"; import { Button, Modal, TextField } from "@renderer/components"; import { settingsContext } from "@renderer/context"; import { useForm } from "react-hook-form"; +import { useAppDispatch } from "@renderer/hooks"; import * as yup from "yup"; import { yupResolver } from "@hookform/resolvers/yup"; -import { downloadSourcesTable } from "@renderer/dexie"; import type { DownloadSourceValidationResult } from "@types"; -import { downloadSourcesWorker } from "@renderer/workers"; +import { setIsImportingSources } from "@renderer/features"; +import { SyncIcon } from "@primer/octicons-react"; import "./add-download-source-modal.scss"; interface AddDownloadSourceModalProps { @@ -52,13 +53,15 @@ export function AddDownloadSourceModal({ const { sourceUrl } = useContext(settingsContext); + const dispatch = useAppDispatch(); + const onSubmit = useCallback( async (values: FormValues) => { - const existingDownloadSource = await downloadSourcesTable - .where({ url: values.url }) - .first(); + const exists = await window.electron.checkDownloadSourceExists( + values.url + ); - if (existingDownloadSource) { + if (exists) { setError("url", { type: "server", message: t("source_already_exists"), @@ -67,22 +70,11 @@ export function AddDownloadSourceModal({ return; } - downloadSourcesWorker.postMessage([ - "VALIDATE_DOWNLOAD_SOURCE", - values.url, - ]); - - const channel = new BroadcastChannel( - `download_sources:validate:${values.url}` + const validationResult = await window.electron.validateDownloadSource( + values.url ); - channel.onmessage = ( - event: MessageEvent - ) => { - setValidationResult(event.data); - channel.close(); - }; - + setValidationResult(validationResult); setUrl(values.url); }, [setError, t] @@ -100,44 +92,44 @@ export function AddDownloadSourceModal({ } }, [visible, clearErrors, handleSubmit, onSubmit, setValue, sourceUrl]); - const putDownloadSource = async () => { - const downloadSource = await downloadSourcesTable.where({ url }).first(); - if (!downloadSource) return; - - window.electron - .putDownloadSource(downloadSource.objectIds) - .then(({ fingerprint }) => { - downloadSourcesTable.update(downloadSource.id, { fingerprint }); - }); - }; - const handleAddDownloadSource = async () => { if (validationResult) { setIsLoading(true); + dispatch(setIsImportingSources(true)); - const channel = new BroadcastChannel(`download_sources:import:${url}`); - - downloadSourcesWorker.postMessage(["IMPORT_DOWNLOAD_SOURCE", url]); - - channel.onmessage = () => { - window.electron.createDownloadSources([url]); - setIsLoading(false); - - putDownloadSource(); + try { + // Single call that handles: import → API sync → fingerprint + await window.electron.addDownloadSource(url); + // Close modal and update UI onClose(); onAddDownloadSource(); - channel.close(); - }; + } catch (error) { + console.error("Failed to add download source:", error); + setError("url", { + type: "server", + message: "Failed to import source. Please try again.", + }); + } finally { + setIsLoading(false); + dispatch(setIsImportingSources(false)); + } } }; + const handleClose = () => { + // Prevent closing while importing + if (isLoading) return; + onClose(); + }; + return (
    - {t("import")} + {isLoading && ( + + )} + {isLoading ? t("importing") : t("import")}
    )} diff --git a/src/renderer/src/pages/settings/settings-download-sources.tsx b/src/renderer/src/pages/settings/settings-download-sources.tsx index 720a313b..f873b321 100644 --- a/src/renderer/src/pages/settings/settings-download-sources.tsx +++ b/src/renderer/src/pages/settings/settings-download-sources.tsx @@ -19,11 +19,8 @@ import { AddDownloadSourceModal } from "./add-download-source-modal"; import { useAppDispatch, useRepacks, useToast } from "@renderer/hooks"; import { DownloadSourceStatus } from "@shared"; import { settingsContext } from "@renderer/context"; -import { downloadSourcesTable } from "@renderer/dexie"; -import { downloadSourcesWorker } from "@renderer/workers"; import { useNavigate } from "react-router-dom"; import { setFilters, clearFilters } from "@renderer/features"; -import { generateUUID } from "@renderer/helpers"; import "./settings-download-sources.scss"; export function SettingsDownloadSources() { @@ -52,11 +49,10 @@ export function SettingsDownloadSources() { const { updateRepacks } = useRepacks(); const getDownloadSources = async () => { - await downloadSourcesTable - .toCollection() - .sortBy("createdAt") + await window.electron + .getDownloadSourcesList() .then((sources) => { - setDownloadSources(sources.reverse()); + setDownloadSources(sources); }) .finally(() => { setIsFetchingSources(false); @@ -71,68 +67,67 @@ export function SettingsDownloadSources() { if (sourceUrl) setShowAddDownloadSourceModal(true); }, [sourceUrl]); - const handleRemoveSource = (downloadSource: DownloadSource) => { + const handleRemoveSource = async (downloadSource: DownloadSource) => { setIsRemovingDownloadSource(true); - const channel = new BroadcastChannel( - `download_sources:delete:${downloadSource.id}` - ); - downloadSourcesWorker.postMessage([ - "DELETE_DOWNLOAD_SOURCE", - downloadSource.id, - ]); + try { + await window.electron.deleteDownloadSource(downloadSource.id); + await window.electron.removeDownloadSource(downloadSource.url); - channel.onmessage = () => { showSuccessToast(t("removed_download_source")); - window.electron.removeDownloadSource(downloadSource.url); - - getDownloadSources(); - setIsRemovingDownloadSource(false); - channel.close(); + await getDownloadSources(); updateRepacks(); - }; + } finally { + setIsRemovingDownloadSource(false); + } }; - const handleRemoveAllDownloadSources = () => { + const handleRemoveAllDownloadSources = async () => { setIsRemovingDownloadSource(true); - const id = generateUUID(); - const channel = new BroadcastChannel(`download_sources:delete_all:${id}`); + try { + await window.electron.deleteAllDownloadSources(); + await window.electron.removeDownloadSource("", true); - downloadSourcesWorker.postMessage(["DELETE_ALL_DOWNLOAD_SOURCES", id]); - - channel.onmessage = () => { showSuccessToast(t("removed_download_sources")); - window.electron.removeDownloadSource("", true); - getDownloadSources(); - setIsRemovingDownloadSource(false); + await getDownloadSources(); setShowConfirmationDeleteAllSourcesModal(false); - channel.close(); updateRepacks(); - }; + } finally { + setIsRemovingDownloadSource(false); + } }; const handleAddDownloadSource = async () => { + // Refresh sources list and repacks after import completes await getDownloadSources(); + + // Force repacks update to ensure UI reflects new data + await updateRepacks(); + showSuccessToast(t("added_download_source")); - updateRepacks(); }; const syncDownloadSources = async () => { setIsSyncingDownloadSources(true); - const id = generateUUID(); - const channel = new BroadcastChannel(`download_sources:sync:${id}`); + try { + // Sync local sources (check for updates) + await window.electron.syncDownloadSources(); - downloadSourcesWorker.postMessage(["SYNC_DOWNLOAD_SOURCES", id]); + // Refresh sources and repacks AFTER sync completes + await getDownloadSources(); + await updateRepacks(); - channel.onmessage = () => { showSuccessToast(t("download_sources_synced")); - getDownloadSources(); + } catch (error) { + console.error("Error syncing download sources:", error); + // Still refresh the UI even if sync fails + await getDownloadSources(); + await updateRepacks(); + } finally { setIsSyncingDownloadSources(false); - channel.close(); - updateRepacks(); - }; + } }; const statusTitle = { @@ -145,7 +140,12 @@ export function SettingsDownloadSources() { setShowAddDownloadSourceModal(false); }; - const navigateToCatalogue = (fingerprint: string) => { + const navigateToCatalogue = (fingerprint?: string) => { + if (!fingerprint) { + console.error("Cannot navigate: fingerprint is undefined"); + return; + } + dispatch(clearFilters()); dispatch(setFilters({ downloadSourceFingerprints: [fingerprint] })); @@ -222,54 +222,58 @@ export function SettingsDownloadSources() {
      - {downloadSources.map((downloadSource) => ( -
    • -
      -

      {downloadSource.name}

      + {downloadSources.map((downloadSource) => { + return ( +
    • +
      +

      {downloadSource.name}

      -
      - {statusTitle[downloadSource.status]} +
      + {statusTitle[downloadSource.status]} +
      + +
      - -
      - - handleRemoveSource(downloadSource)} - disabled={isRemovingDownloadSource} - > - - {t("remove_download_source")} - - } - /> -
    • - ))} + handleRemoveSource(downloadSource)} + disabled={isRemovingDownloadSource} + > + + {t("remove_download_source")} + + } + /> + + ); + })}
    ); diff --git a/src/renderer/src/store.ts b/src/renderer/src/store.ts index e771f98e..264b1296 100644 --- a/src/renderer/src/store.ts +++ b/src/renderer/src/store.ts @@ -9,6 +9,7 @@ import { gameRunningSlice, subscriptionSlice, repacksSlice, + downloadSourcesSlice, catalogueSearchSlice, } from "@renderer/features"; @@ -23,6 +24,7 @@ export const store = configureStore({ gameRunning: gameRunningSlice.reducer, subscription: subscriptionSlice.reducer, repacks: repacksSlice.reducer, + downloadSources: downloadSourcesSlice.reducer, catalogueSearch: catalogueSearchSlice.reducer, }, }); diff --git a/src/renderer/src/workers/download-sources.worker.ts b/src/renderer/src/workers/download-sources.worker.ts deleted file mode 100644 index 3f95bf22..00000000 --- a/src/renderer/src/workers/download-sources.worker.ts +++ /dev/null @@ -1,238 +0,0 @@ -import { db, downloadSourcesTable, repacksTable } from "@renderer/dexie"; - -import { z } from "zod"; -import axios, { AxiosError, AxiosHeaders } from "axios"; -import { DownloadSourceStatus, formatName, pipe } from "@shared"; -import { GameRepack } from "@types"; - -const formatRepackName = pipe((name) => name.replace("[DL]", ""), formatName); - -export const downloadSourceSchema = z.object({ - name: z.string().max(255), - downloads: z.array( - z.object({ - title: z.string().max(255), - uris: z.array(z.string()), - uploadDate: z.string().max(255), - fileSize: z.string().max(255), - }) - ), -}); - -type Payload = - | ["IMPORT_DOWNLOAD_SOURCE", string] - | ["DELETE_DOWNLOAD_SOURCE", number] - | ["VALIDATE_DOWNLOAD_SOURCE", string] - | ["SYNC_DOWNLOAD_SOURCES", string] - | ["DELETE_ALL_DOWNLOAD_SOURCES", string]; - -export type SteamGamesByLetter = Record; - -const addNewDownloads = async ( - downloadSource: { id: number; name: string }, - downloads: z.infer["downloads"], - steamGames: SteamGamesByLetter -) => { - const now = new Date(); - - const results = [] as (Omit & { - downloadSourceId: number; - })[]; - - const objectIdsOnSource = new Set(); - - for (const download of downloads) { - const formattedTitle = formatRepackName(download.title); - const [firstLetter] = formattedTitle; - const games = steamGames[firstLetter] || []; - - const gamesInSteam = games.filter((game) => - formattedTitle.startsWith(game.name) - ); - - if (gamesInSteam.length === 0) continue; - - for (const game of gamesInSteam) { - objectIdsOnSource.add(String(game.id)); - } - - results.push({ - objectIds: gamesInSteam.map((game) => String(game.id)), - title: download.title, - uris: download.uris, - fileSize: download.fileSize, - repacker: downloadSource.name, - uploadDate: download.uploadDate, - downloadSourceId: downloadSource.id, - createdAt: now, - updatedAt: now, - }); - } - - await repacksTable.bulkAdd(results); - - await downloadSourcesTable.update(downloadSource.id, { - objectIds: Array.from(objectIdsOnSource), - }); -}; - -const getSteamGames = async () => { - const response = await axios.get( - `${import.meta.env.RENDERER_VITE_EXTERNAL_RESOURCES_URL}/steam-games-by-letter.json` - ); - - return response.data; -}; - -const importDownloadSource = async (url: string) => { - const response = await axios.get>(url); - - const steamGames = await getSteamGames(); - - await db.transaction("rw", repacksTable, downloadSourcesTable, async () => { - const now = new Date(); - - const id = await downloadSourcesTable.add({ - url, - name: response.data.name, - etag: response.headers["etag"], - status: DownloadSourceStatus.UpToDate, - downloadCount: response.data.downloads.length, - createdAt: now, - updatedAt: now, - }); - - const downloadSource = await downloadSourcesTable.get(id); - - await addNewDownloads(downloadSource, response.data.downloads, steamGames); - }); -}; - -const deleteDownloadSource = async (id: number) => { - await db.transaction("rw", repacksTable, downloadSourcesTable, async () => { - await repacksTable.where({ downloadSourceId: id }).delete(); - await downloadSourcesTable.where({ id }).delete(); - }); -}; - -const deleteAllDowloadSources = async () => { - await db.transaction("rw", repacksTable, downloadSourcesTable, async () => { - await repacksTable.clear(); - await downloadSourcesTable.clear(); - }); -}; - -self.onmessage = async (event: MessageEvent) => { - const [type, data] = event.data; - - if (type === "VALIDATE_DOWNLOAD_SOURCE") { - const response = - await axios.get>(data); - - const { name } = downloadSourceSchema.parse(response.data); - - const channel = new BroadcastChannel(`download_sources:validate:${data}`); - - channel.postMessage({ - name, - etag: response.headers["etag"], - downloadCount: response.data.downloads.length, - }); - } - - if (type === "DELETE_ALL_DOWNLOAD_SOURCES") { - await deleteAllDowloadSources(); - - const channel = new BroadcastChannel(`download_sources:delete_all:${data}`); - - channel.postMessage(true); - } - - if (type === "DELETE_DOWNLOAD_SOURCE") { - await deleteDownloadSource(data); - - const channel = new BroadcastChannel(`download_sources:delete:${data}`); - - channel.postMessage(true); - } - - if (type === "IMPORT_DOWNLOAD_SOURCE") { - await importDownloadSource(data); - - const channel = new BroadcastChannel(`download_sources:import:${data}`); - channel.postMessage(true); - } - - if (type === "SYNC_DOWNLOAD_SOURCES") { - const channel = new BroadcastChannel(`download_sources:sync:${data}`); - let newRepacksCount = 0; - - try { - const downloadSources = await downloadSourcesTable.toArray(); - const existingRepacks = await repacksTable.toArray(); - - if (downloadSources.some((source) => !source.fingerprint)) { - await Promise.all( - downloadSources.map(async (source) => { - await deleteDownloadSource(source.id); - await importDownloadSource(source.url); - }) - ); - } else { - for (const downloadSource of downloadSources) { - const headers = new AxiosHeaders(); - - if (downloadSource.etag) { - headers.set("If-None-Match", downloadSource.etag); - } - - try { - const response = await axios.get(downloadSource.url, { - headers, - }); - - const source = downloadSourceSchema.parse(response.data); - - const steamGames = await getSteamGames(); - - await db.transaction( - "rw", - repacksTable, - downloadSourcesTable, - async () => { - await downloadSourcesTable.update(downloadSource.id, { - etag: response.headers["etag"], - downloadCount: source.downloads.length, - status: DownloadSourceStatus.UpToDate, - }); - - const repacks = source.downloads.filter( - (download) => - !existingRepacks.some( - (repack) => repack.title === download.title - ) - ); - - await addNewDownloads(downloadSource, repacks, steamGames); - - newRepacksCount += repacks.length; - } - ); - } catch (err: unknown) { - const isNotModified = (err as AxiosError).response?.status === 304; - - await downloadSourcesTable.update(downloadSource.id, { - status: isNotModified - ? DownloadSourceStatus.UpToDate - : DownloadSourceStatus.Errored, - }); - } - } - } - - channel.postMessage(newRepacksCount); - } catch (err) { - channel.postMessage(-1); - } - } -}; diff --git a/src/renderer/src/workers/index.ts b/src/renderer/src/workers/index.ts deleted file mode 100644 index 39367894..00000000 --- a/src/renderer/src/workers/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import DownloadSourcesWorker from "./download-sources.worker?worker"; - -export const downloadSourcesWorker = new DownloadSourcesWorker(); diff --git a/src/types/index.ts b/src/types/index.ts index 0fb68ddf..94b9701f 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -35,7 +35,7 @@ export interface DownloadSource { status: DownloadSourceStatus; objectIds: string[]; downloadCount: number; - fingerprint: string; + fingerprint?: string; etag: string | null; createdAt: Date; updatedAt: Date; diff --git a/yarn.lock b/yarn.lock index f6d4de71..ebd10beb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4,16 +4,19 @@ "7zip-bin@~5.2.0": version "5.2.0" - resolved "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.2.0.tgz" + resolved "https://registry.yarnpkg.com/7zip-bin/-/7zip-bin-5.2.0.tgz#7a03314684dd6572b7dfa89e68ce31d60286854d" integrity sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A== -"@ampproject/remapping@^2.2.0": - version "2.3.0" - resolved "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz" - integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== +"@asamuzakjp/css-color@^3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@asamuzakjp/css-color/-/css-color-3.2.0.tgz#cc42f5b85c593f79f1fa4f25d2b9b321e61d1794" + integrity sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw== dependencies: - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.24" + "@csstools/css-calc" "^2.1.3" + "@csstools/css-color-parser" "^3.0.9" + "@csstools/css-parser-algorithms" "^3.0.4" + "@csstools/css-tokenizer" "^3.0.3" + lru-cache "^10.4.3" "@aws-crypto/crc32@5.2.0": version "5.2.0" @@ -84,548 +87,507 @@ tslib "^2.6.2" "@aws-sdk/client-s3@^3.705.0": - version "3.714.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-s3/-/client-s3-3.714.0.tgz#74d056a57525a1289c7934015f2ce3f3714e7464" - integrity sha512-DqzfbecKrhUEpsYTsYRIm4cKKlIvAl4I/A2NpzDPDSiA2EmCWLy0T5fK1ivUA4XL+09+4pHJGNVTpMyDs7n6vg== + version "3.908.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-s3/-/client-s3-3.908.0.tgz#99aaf076f95dadc11419a2558749b0159e5d9e4a" + integrity sha512-c/89iG3of8UEiWbRK014DoHLy8PLLTJtM9IvYLPsvrf83kpV2P/K9WrdbjW4h6e5qt9XPgfNTZ8U607mt7pdmA== dependencies: "@aws-crypto/sha1-browser" "5.2.0" "@aws-crypto/sha256-browser" "5.2.0" "@aws-crypto/sha256-js" "5.2.0" - "@aws-sdk/client-sso-oidc" "3.714.0" - "@aws-sdk/client-sts" "3.714.0" - "@aws-sdk/core" "3.714.0" - "@aws-sdk/credential-provider-node" "3.714.0" - "@aws-sdk/middleware-bucket-endpoint" "3.714.0" - "@aws-sdk/middleware-expect-continue" "3.714.0" - "@aws-sdk/middleware-flexible-checksums" "3.714.0" - "@aws-sdk/middleware-host-header" "3.714.0" - "@aws-sdk/middleware-location-constraint" "3.714.0" - "@aws-sdk/middleware-logger" "3.714.0" - "@aws-sdk/middleware-recursion-detection" "3.714.0" - "@aws-sdk/middleware-sdk-s3" "3.714.0" - "@aws-sdk/middleware-ssec" "3.714.0" - "@aws-sdk/middleware-user-agent" "3.714.0" - "@aws-sdk/region-config-resolver" "3.714.0" - "@aws-sdk/signature-v4-multi-region" "3.714.0" - "@aws-sdk/types" "3.714.0" - "@aws-sdk/util-endpoints" "3.714.0" - "@aws-sdk/util-user-agent-browser" "3.714.0" - "@aws-sdk/util-user-agent-node" "3.714.0" - "@aws-sdk/xml-builder" "3.709.0" - "@smithy/config-resolver" "^3.0.13" - "@smithy/core" "^2.5.5" - "@smithy/eventstream-serde-browser" "^3.0.14" - "@smithy/eventstream-serde-config-resolver" "^3.0.11" - "@smithy/eventstream-serde-node" "^3.0.13" - "@smithy/fetch-http-handler" "^4.1.2" - "@smithy/hash-blob-browser" "^3.1.10" - "@smithy/hash-node" "^3.0.11" - "@smithy/hash-stream-node" "^3.1.10" - "@smithy/invalid-dependency" "^3.0.11" - "@smithy/md5-js" "^3.0.11" - "@smithy/middleware-content-length" "^3.0.13" - "@smithy/middleware-endpoint" "^3.2.5" - "@smithy/middleware-retry" "^3.0.30" - "@smithy/middleware-serde" "^3.0.11" - "@smithy/middleware-stack" "^3.0.11" - "@smithy/node-config-provider" "^3.1.12" - "@smithy/node-http-handler" "^3.3.2" - "@smithy/protocol-http" "^4.1.8" - "@smithy/smithy-client" "^3.5.0" - "@smithy/types" "^3.7.2" - "@smithy/url-parser" "^3.0.11" - "@smithy/util-base64" "^3.0.0" - "@smithy/util-body-length-browser" "^3.0.0" - "@smithy/util-body-length-node" "^3.0.0" - "@smithy/util-defaults-mode-browser" "^3.0.30" - "@smithy/util-defaults-mode-node" "^3.0.30" - "@smithy/util-endpoints" "^2.1.7" - "@smithy/util-middleware" "^3.0.11" - "@smithy/util-retry" "^3.0.11" - "@smithy/util-stream" "^3.3.2" - "@smithy/util-utf8" "^3.0.0" - "@smithy/util-waiter" "^3.2.0" + "@aws-sdk/core" "3.908.0" + "@aws-sdk/credential-provider-node" "3.908.0" + "@aws-sdk/middleware-bucket-endpoint" "3.901.0" + "@aws-sdk/middleware-expect-continue" "3.901.0" + "@aws-sdk/middleware-flexible-checksums" "3.908.0" + "@aws-sdk/middleware-host-header" "3.901.0" + "@aws-sdk/middleware-location-constraint" "3.901.0" + "@aws-sdk/middleware-logger" "3.901.0" + "@aws-sdk/middleware-recursion-detection" "3.901.0" + "@aws-sdk/middleware-sdk-s3" "3.908.0" + "@aws-sdk/middleware-ssec" "3.901.0" + "@aws-sdk/middleware-user-agent" "3.908.0" + "@aws-sdk/region-config-resolver" "3.901.0" + "@aws-sdk/signature-v4-multi-region" "3.908.0" + "@aws-sdk/types" "3.901.0" + "@aws-sdk/util-endpoints" "3.901.0" + "@aws-sdk/util-user-agent-browser" "3.907.0" + "@aws-sdk/util-user-agent-node" "3.908.0" + "@aws-sdk/xml-builder" "3.901.0" + "@smithy/config-resolver" "^4.3.0" + "@smithy/core" "^3.15.0" + "@smithy/eventstream-serde-browser" "^4.2.0" + "@smithy/eventstream-serde-config-resolver" "^4.3.0" + "@smithy/eventstream-serde-node" "^4.2.0" + "@smithy/fetch-http-handler" "^5.3.1" + "@smithy/hash-blob-browser" "^4.2.1" + "@smithy/hash-node" "^4.2.0" + "@smithy/hash-stream-node" "^4.2.0" + "@smithy/invalid-dependency" "^4.2.0" + "@smithy/md5-js" "^4.2.0" + "@smithy/middleware-content-length" "^4.2.0" + "@smithy/middleware-endpoint" "^4.3.1" + "@smithy/middleware-retry" "^4.4.1" + "@smithy/middleware-serde" "^4.2.0" + "@smithy/middleware-stack" "^4.2.0" + "@smithy/node-config-provider" "^4.3.0" + "@smithy/node-http-handler" "^4.3.0" + "@smithy/protocol-http" "^5.3.0" + "@smithy/smithy-client" "^4.7.1" + "@smithy/types" "^4.6.0" + "@smithy/url-parser" "^4.2.0" + "@smithy/util-base64" "^4.3.0" + "@smithy/util-body-length-browser" "^4.2.0" + "@smithy/util-body-length-node" "^4.2.1" + "@smithy/util-defaults-mode-browser" "^4.3.0" + "@smithy/util-defaults-mode-node" "^4.2.1" + "@smithy/util-endpoints" "^3.2.0" + "@smithy/util-middleware" "^4.2.0" + "@smithy/util-retry" "^4.2.0" + "@smithy/util-stream" "^4.5.0" + "@smithy/util-utf8" "^4.2.0" + "@smithy/util-waiter" "^4.2.0" + "@smithy/uuid" "^1.1.0" tslib "^2.6.2" -"@aws-sdk/client-sso-oidc@3.714.0": - version "3.714.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.714.0.tgz#ddbb500ce93003f21c26d05ac45593c961d8b8e2" - integrity sha512-dMvpPUaL3v01psPY1ZyCzQ/w2tOgQTH1if0zBF5r2q7Vc0oOPzbBZgNAhG1bDWlRCBW0iXmoqRFoWUwQ5rtx+A== +"@aws-sdk/client-sso@3.908.0": + version "3.908.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.908.0.tgz#08db2b04caaeee7b3453945e18b8ff5811219ec5" + integrity sha512-PseFMWvtac+Q+zaY9DMISE+2+glNh0ROJ1yR4gMzeafNHSwkdYu4qcgxLWIOnIodGydBv/tQ6nzHPzExXnUUgw== dependencies: "@aws-crypto/sha256-browser" "5.2.0" "@aws-crypto/sha256-js" "5.2.0" - "@aws-sdk/core" "3.714.0" - "@aws-sdk/credential-provider-node" "3.714.0" - "@aws-sdk/middleware-host-header" "3.714.0" - "@aws-sdk/middleware-logger" "3.714.0" - "@aws-sdk/middleware-recursion-detection" "3.714.0" - "@aws-sdk/middleware-user-agent" "3.714.0" - "@aws-sdk/region-config-resolver" "3.714.0" - "@aws-sdk/types" "3.714.0" - "@aws-sdk/util-endpoints" "3.714.0" - "@aws-sdk/util-user-agent-browser" "3.714.0" - "@aws-sdk/util-user-agent-node" "3.714.0" - "@smithy/config-resolver" "^3.0.13" - "@smithy/core" "^2.5.5" - "@smithy/fetch-http-handler" "^4.1.2" - "@smithy/hash-node" "^3.0.11" - "@smithy/invalid-dependency" "^3.0.11" - "@smithy/middleware-content-length" "^3.0.13" - "@smithy/middleware-endpoint" "^3.2.5" - "@smithy/middleware-retry" "^3.0.30" - "@smithy/middleware-serde" "^3.0.11" - "@smithy/middleware-stack" "^3.0.11" - "@smithy/node-config-provider" "^3.1.12" - "@smithy/node-http-handler" "^3.3.2" - "@smithy/protocol-http" "^4.1.8" - "@smithy/smithy-client" "^3.5.0" - "@smithy/types" "^3.7.2" - "@smithy/url-parser" "^3.0.11" - "@smithy/util-base64" "^3.0.0" - "@smithy/util-body-length-browser" "^3.0.0" - "@smithy/util-body-length-node" "^3.0.0" - "@smithy/util-defaults-mode-browser" "^3.0.30" - "@smithy/util-defaults-mode-node" "^3.0.30" - "@smithy/util-endpoints" "^2.1.7" - "@smithy/util-middleware" "^3.0.11" - "@smithy/util-retry" "^3.0.11" - "@smithy/util-utf8" "^3.0.0" + "@aws-sdk/core" "3.908.0" + "@aws-sdk/middleware-host-header" "3.901.0" + "@aws-sdk/middleware-logger" "3.901.0" + "@aws-sdk/middleware-recursion-detection" "3.901.0" + "@aws-sdk/middleware-user-agent" "3.908.0" + "@aws-sdk/region-config-resolver" "3.901.0" + "@aws-sdk/types" "3.901.0" + "@aws-sdk/util-endpoints" "3.901.0" + "@aws-sdk/util-user-agent-browser" "3.907.0" + "@aws-sdk/util-user-agent-node" "3.908.0" + "@smithy/config-resolver" "^4.3.0" + "@smithy/core" "^3.15.0" + "@smithy/fetch-http-handler" "^5.3.1" + "@smithy/hash-node" "^4.2.0" + "@smithy/invalid-dependency" "^4.2.0" + "@smithy/middleware-content-length" "^4.2.0" + "@smithy/middleware-endpoint" "^4.3.1" + "@smithy/middleware-retry" "^4.4.1" + "@smithy/middleware-serde" "^4.2.0" + "@smithy/middleware-stack" "^4.2.0" + "@smithy/node-config-provider" "^4.3.0" + "@smithy/node-http-handler" "^4.3.0" + "@smithy/protocol-http" "^5.3.0" + "@smithy/smithy-client" "^4.7.1" + "@smithy/types" "^4.6.0" + "@smithy/url-parser" "^4.2.0" + "@smithy/util-base64" "^4.3.0" + "@smithy/util-body-length-browser" "^4.2.0" + "@smithy/util-body-length-node" "^4.2.1" + "@smithy/util-defaults-mode-browser" "^4.3.0" + "@smithy/util-defaults-mode-node" "^4.2.1" + "@smithy/util-endpoints" "^3.2.0" + "@smithy/util-middleware" "^4.2.0" + "@smithy/util-retry" "^4.2.0" + "@smithy/util-utf8" "^4.2.0" tslib "^2.6.2" -"@aws-sdk/client-sso@3.714.0": - version "3.714.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.714.0.tgz#a98358f499fcffec7895f9fb0fb8650b3c993bc4" - integrity sha512-pFtjY5Ga91qrryo0UfbjetdT2p9rOgtHofogAeEuGjxx7/rupBpdlW0WDOtD/7jhmbhM8WZEr6aH7GLzzkKfCA== +"@aws-sdk/core@3.908.0": + version "3.908.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/core/-/core-3.908.0.tgz#efd691b34ea3ba413f82ec27ce5fa9db4f9d386d" + integrity sha512-okl6FC2cQT1Oidvmnmvyp/IEvqENBagKO0ww4YV5UtBkf0VlhAymCWkZqhovtklsqgq0otag2VRPAgnrMt6nVQ== dependencies: - "@aws-crypto/sha256-browser" "5.2.0" - "@aws-crypto/sha256-js" "5.2.0" - "@aws-sdk/core" "3.714.0" - "@aws-sdk/middleware-host-header" "3.714.0" - "@aws-sdk/middleware-logger" "3.714.0" - "@aws-sdk/middleware-recursion-detection" "3.714.0" - "@aws-sdk/middleware-user-agent" "3.714.0" - "@aws-sdk/region-config-resolver" "3.714.0" - "@aws-sdk/types" "3.714.0" - "@aws-sdk/util-endpoints" "3.714.0" - "@aws-sdk/util-user-agent-browser" "3.714.0" - "@aws-sdk/util-user-agent-node" "3.714.0" - "@smithy/config-resolver" "^3.0.13" - "@smithy/core" "^2.5.5" - "@smithy/fetch-http-handler" "^4.1.2" - "@smithy/hash-node" "^3.0.11" - "@smithy/invalid-dependency" "^3.0.11" - "@smithy/middleware-content-length" "^3.0.13" - "@smithy/middleware-endpoint" "^3.2.5" - "@smithy/middleware-retry" "^3.0.30" - "@smithy/middleware-serde" "^3.0.11" - "@smithy/middleware-stack" "^3.0.11" - "@smithy/node-config-provider" "^3.1.12" - "@smithy/node-http-handler" "^3.3.2" - "@smithy/protocol-http" "^4.1.8" - "@smithy/smithy-client" "^3.5.0" - "@smithy/types" "^3.7.2" - "@smithy/url-parser" "^3.0.11" - "@smithy/util-base64" "^3.0.0" - "@smithy/util-body-length-browser" "^3.0.0" - "@smithy/util-body-length-node" "^3.0.0" - "@smithy/util-defaults-mode-browser" "^3.0.30" - "@smithy/util-defaults-mode-node" "^3.0.30" - "@smithy/util-endpoints" "^2.1.7" - "@smithy/util-middleware" "^3.0.11" - "@smithy/util-retry" "^3.0.11" - "@smithy/util-utf8" "^3.0.0" + "@aws-sdk/types" "3.901.0" + "@aws-sdk/xml-builder" "3.901.0" + "@smithy/core" "^3.15.0" + "@smithy/node-config-provider" "^4.3.0" + "@smithy/property-provider" "^4.2.0" + "@smithy/protocol-http" "^5.3.0" + "@smithy/signature-v4" "^5.3.0" + "@smithy/smithy-client" "^4.7.1" + "@smithy/types" "^4.6.0" + "@smithy/util-base64" "^4.3.0" + "@smithy/util-middleware" "^4.2.0" + "@smithy/util-utf8" "^4.2.0" tslib "^2.6.2" -"@aws-sdk/client-sts@3.714.0": - version "3.714.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.714.0.tgz#3222b498ae1dd83490379558eb1af27d5d89601c" - integrity sha512-ThcXgolapPsOzeavJF4Am312umFyoFBBeiTYD8PQGIiYkbJi4hXcjoWacmtkq6moMmMZSP9iK/ellls7vwY2JQ== +"@aws-sdk/credential-provider-env@3.908.0": + version "3.908.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-env/-/credential-provider-env-3.908.0.tgz#9767fcafbe643f0f5b5d4b6283c9dddb5fdee708" + integrity sha512-FK2YuxoI5CxUflPOIMbVAwDbi6Xvu+2sXopXLmrHc2PfI39M3vmjEoQwYCP8WuQSRb+TbAP3xAkxHjFSBFR35w== dependencies: - "@aws-crypto/sha256-browser" "5.2.0" - "@aws-crypto/sha256-js" "5.2.0" - "@aws-sdk/client-sso-oidc" "3.714.0" - "@aws-sdk/core" "3.714.0" - "@aws-sdk/credential-provider-node" "3.714.0" - "@aws-sdk/middleware-host-header" "3.714.0" - "@aws-sdk/middleware-logger" "3.714.0" - "@aws-sdk/middleware-recursion-detection" "3.714.0" - "@aws-sdk/middleware-user-agent" "3.714.0" - "@aws-sdk/region-config-resolver" "3.714.0" - "@aws-sdk/types" "3.714.0" - "@aws-sdk/util-endpoints" "3.714.0" - "@aws-sdk/util-user-agent-browser" "3.714.0" - "@aws-sdk/util-user-agent-node" "3.714.0" - "@smithy/config-resolver" "^3.0.13" - "@smithy/core" "^2.5.5" - "@smithy/fetch-http-handler" "^4.1.2" - "@smithy/hash-node" "^3.0.11" - "@smithy/invalid-dependency" "^3.0.11" - "@smithy/middleware-content-length" "^3.0.13" - "@smithy/middleware-endpoint" "^3.2.5" - "@smithy/middleware-retry" "^3.0.30" - "@smithy/middleware-serde" "^3.0.11" - "@smithy/middleware-stack" "^3.0.11" - "@smithy/node-config-provider" "^3.1.12" - "@smithy/node-http-handler" "^3.3.2" - "@smithy/protocol-http" "^4.1.8" - "@smithy/smithy-client" "^3.5.0" - "@smithy/types" "^3.7.2" - "@smithy/url-parser" "^3.0.11" - "@smithy/util-base64" "^3.0.0" - "@smithy/util-body-length-browser" "^3.0.0" - "@smithy/util-body-length-node" "^3.0.0" - "@smithy/util-defaults-mode-browser" "^3.0.30" - "@smithy/util-defaults-mode-node" "^3.0.30" - "@smithy/util-endpoints" "^2.1.7" - "@smithy/util-middleware" "^3.0.11" - "@smithy/util-retry" "^3.0.11" - "@smithy/util-utf8" "^3.0.0" + "@aws-sdk/core" "3.908.0" + "@aws-sdk/types" "3.901.0" + "@smithy/property-provider" "^4.2.0" + "@smithy/types" "^4.6.0" tslib "^2.6.2" -"@aws-sdk/core@3.714.0": - version "3.714.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/core/-/core-3.714.0.tgz#f53dbf5aa579f66bf3aa3e3e312b624e70ae871d" - integrity sha512-TlZ50d8MEPVp9O03SvisOmcmxjxhMDKHJJcrBgYjgDej6QmNfiFwtCRkReXDdkEeXP29ehMs7uPXtmVvPqziYw== +"@aws-sdk/credential-provider-http@3.908.0": + version "3.908.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-http/-/credential-provider-http-3.908.0.tgz#9500e348eb64cecfcaa6c10ce68785aecf3ee612" + integrity sha512-eLbz0geVW9EykujQNnYfR35Of8MreI6pau5K6XDFDUSWO9GF8wqH7CQwbXpXHBlCTHtq4QSLxzorD8U5CROhUw== dependencies: - "@aws-sdk/types" "3.714.0" - "@smithy/core" "^2.5.5" - "@smithy/node-config-provider" "^3.1.12" - "@smithy/property-provider" "^3.1.11" - "@smithy/protocol-http" "^4.1.8" - "@smithy/signature-v4" "^4.2.4" - "@smithy/smithy-client" "^3.5.0" - "@smithy/types" "^3.7.2" - "@smithy/util-middleware" "^3.0.11" - fast-xml-parser "4.4.1" + "@aws-sdk/core" "3.908.0" + "@aws-sdk/types" "3.901.0" + "@smithy/fetch-http-handler" "^5.3.1" + "@smithy/node-http-handler" "^4.3.0" + "@smithy/property-provider" "^4.2.0" + "@smithy/protocol-http" "^5.3.0" + "@smithy/smithy-client" "^4.7.1" + "@smithy/types" "^4.6.0" + "@smithy/util-stream" "^4.5.0" tslib "^2.6.2" -"@aws-sdk/credential-provider-env@3.714.0": - version "3.714.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-env/-/credential-provider-env-3.714.0.tgz#87b9e71d6477fbed1d4c8c3fc561c11994ba573c" - integrity sha512-0S4nKE1a+EHXAInXUeuWkyzVnXzmwIbwLStVidAIoyl6sJF8xGdw+r3AaoTr7p0YXzdoDUsn3wBTCA6ZwgXVbA== +"@aws-sdk/credential-provider-ini@3.908.0": + version "3.908.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.908.0.tgz#6f2567326245ea57e37cfb0bf30b7526c13a3965" + integrity sha512-7Cgnv5wabgFtsgr+Uc/76EfPNGyxmbG8aICn3g3D3iJlcO4uuOZI8a77i0afoDdchZrTC6TG6UusS/NAW6zEoQ== dependencies: - "@aws-sdk/core" "3.714.0" - "@aws-sdk/types" "3.714.0" - "@smithy/property-provider" "^3.1.11" - "@smithy/types" "^3.7.2" + "@aws-sdk/core" "3.908.0" + "@aws-sdk/credential-provider-env" "3.908.0" + "@aws-sdk/credential-provider-http" "3.908.0" + "@aws-sdk/credential-provider-process" "3.908.0" + "@aws-sdk/credential-provider-sso" "3.908.0" + "@aws-sdk/credential-provider-web-identity" "3.908.0" + "@aws-sdk/nested-clients" "3.908.0" + "@aws-sdk/types" "3.901.0" + "@smithy/credential-provider-imds" "^4.2.0" + "@smithy/property-provider" "^4.2.0" + "@smithy/shared-ini-file-loader" "^4.3.0" + "@smithy/types" "^4.6.0" tslib "^2.6.2" -"@aws-sdk/credential-provider-http@3.714.0": - version "3.714.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-http/-/credential-provider-http-3.714.0.tgz#06dc80fc910ef85aadb920d9be9b6aba971ea31a" - integrity sha512-1AXEfUSQUQg+x/DpH1XJhjf2yEgTHHatM3cvYu7FZMhRXF28Q5OJDbEFPfdqrK+vmCiYRWhszDb+zuUIvz46bw== +"@aws-sdk/credential-provider-node@3.908.0": + version "3.908.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.908.0.tgz#942c686e105182b5c59fad92f65b1fdf246f347a" + integrity sha512-8OKbykpGw5bdfF/pLTf8YfUi1Kl8o1CTjBqWQTsLOkE3Ho3hsp1eQx8Cz4ttrpv0919kb+lox62DgmAOEmTr1w== dependencies: - "@aws-sdk/core" "3.714.0" - "@aws-sdk/types" "3.714.0" - "@smithy/fetch-http-handler" "^4.1.2" - "@smithy/node-http-handler" "^3.3.2" - "@smithy/property-provider" "^3.1.11" - "@smithy/protocol-http" "^4.1.8" - "@smithy/smithy-client" "^3.5.0" - "@smithy/types" "^3.7.2" - "@smithy/util-stream" "^3.3.2" + "@aws-sdk/credential-provider-env" "3.908.0" + "@aws-sdk/credential-provider-http" "3.908.0" + "@aws-sdk/credential-provider-ini" "3.908.0" + "@aws-sdk/credential-provider-process" "3.908.0" + "@aws-sdk/credential-provider-sso" "3.908.0" + "@aws-sdk/credential-provider-web-identity" "3.908.0" + "@aws-sdk/types" "3.901.0" + "@smithy/credential-provider-imds" "^4.2.0" + "@smithy/property-provider" "^4.2.0" + "@smithy/shared-ini-file-loader" "^4.3.0" + "@smithy/types" "^4.6.0" tslib "^2.6.2" -"@aws-sdk/credential-provider-ini@3.714.0": - version "3.714.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.714.0.tgz#7fbe0a9c2ef00f1eee59f1bc24194f66c77fdaf2" - integrity sha512-w5wOcgBngfcvVev5wnYWXoc/W2ewVmGJkfRdGquhFt8pkUxktyd8eXehqkP7u31SONVlgy96EFTdSCzWpTrqOw== +"@aws-sdk/credential-provider-process@3.908.0": + version "3.908.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.908.0.tgz#b9bf7b2780a2c378d2983c3a53c2bf68de4427e5" + integrity sha512-sWnbkGjDPBi6sODUzrAh5BCDpnPw0wpK8UC/hWI13Q8KGfyatAmCBfr+9OeO3+xBHa8N5AskMncr7C4qS846yQ== dependencies: - "@aws-sdk/core" "3.714.0" - "@aws-sdk/credential-provider-env" "3.714.0" - "@aws-sdk/credential-provider-http" "3.714.0" - "@aws-sdk/credential-provider-process" "3.714.0" - "@aws-sdk/credential-provider-sso" "3.714.0" - "@aws-sdk/credential-provider-web-identity" "3.714.0" - "@aws-sdk/types" "3.714.0" - "@smithy/credential-provider-imds" "^3.2.8" - "@smithy/property-provider" "^3.1.11" - "@smithy/shared-ini-file-loader" "^3.1.12" - "@smithy/types" "^3.7.2" + "@aws-sdk/core" "3.908.0" + "@aws-sdk/types" "3.901.0" + "@smithy/property-provider" "^4.2.0" + "@smithy/shared-ini-file-loader" "^4.3.0" + "@smithy/types" "^4.6.0" tslib "^2.6.2" -"@aws-sdk/credential-provider-node@3.714.0": - version "3.714.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.714.0.tgz#48c90bf53e55b31b23bf464b420816ec9259f0c1" - integrity sha512-ebho1HYNKzaw0ZfbI9kEicSW8J7tsOoV6EJajsjfFnuP+GY9J5Oi4759GEq1Qqj7GxIhrySOZFzif/hxAXPWtQ== +"@aws-sdk/credential-provider-sso@3.908.0": + version "3.908.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.908.0.tgz#2c21de6f6974aff4eee7464056547ae0cf8168c5" + integrity sha512-WV/aOzuS6ZZhrkPty6TJ3ZG24iS8NXP0m3GuTVuZ5tKi9Guss31/PJ1CrKPRCYGm15CsIjf+mrUxVnNYv9ap5g== dependencies: - "@aws-sdk/credential-provider-env" "3.714.0" - "@aws-sdk/credential-provider-http" "3.714.0" - "@aws-sdk/credential-provider-ini" "3.714.0" - "@aws-sdk/credential-provider-process" "3.714.0" - "@aws-sdk/credential-provider-sso" "3.714.0" - "@aws-sdk/credential-provider-web-identity" "3.714.0" - "@aws-sdk/types" "3.714.0" - "@smithy/credential-provider-imds" "^3.2.8" - "@smithy/property-provider" "^3.1.11" - "@smithy/shared-ini-file-loader" "^3.1.12" - "@smithy/types" "^3.7.2" + "@aws-sdk/client-sso" "3.908.0" + "@aws-sdk/core" "3.908.0" + "@aws-sdk/token-providers" "3.908.0" + "@aws-sdk/types" "3.901.0" + "@smithy/property-provider" "^4.2.0" + "@smithy/shared-ini-file-loader" "^4.3.0" + "@smithy/types" "^4.6.0" tslib "^2.6.2" -"@aws-sdk/credential-provider-process@3.714.0": - version "3.714.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.714.0.tgz#1fd25ddda0bac94fad7d2cbeeeb84d5746af3f2a" - integrity sha512-mHM+zYJDUiXggBx4YvQgMOhbkV07KUib8/jWPnAZbUJcRncN/yevAp/WNocjUN4VaBWkooJUgoTET/okRK+TCQ== +"@aws-sdk/credential-provider-web-identity@3.908.0": + version "3.908.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.908.0.tgz#5ad86a4cf5fd650b60eed83d0575de4666c554b7" + integrity sha512-9xWrFn6nWlF5KlV4XYW+7E6F33S3wUUEGRZ/+pgDhkIZd527ycT2nPG2dZ3fWUZMlRmzijP20QIJDqEbbGWe1Q== dependencies: - "@aws-sdk/core" "3.714.0" - "@aws-sdk/types" "3.714.0" - "@smithy/property-provider" "^3.1.11" - "@smithy/shared-ini-file-loader" "^3.1.12" - "@smithy/types" "^3.7.2" + "@aws-sdk/core" "3.908.0" + "@aws-sdk/nested-clients" "3.908.0" + "@aws-sdk/types" "3.901.0" + "@smithy/property-provider" "^4.2.0" + "@smithy/shared-ini-file-loader" "^4.3.0" + "@smithy/types" "^4.6.0" tslib "^2.6.2" -"@aws-sdk/credential-provider-sso@3.714.0": - version "3.714.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.714.0.tgz#803a21cbd558a2b47ab102c68ec9ed92e999523f" - integrity sha512-LQyHUQd+/A0PO96m6/A3KeekRplRpG9AmwLn8VPknlmACAhhbWHehzerCTd42V8dClf5pigr25/aVqh/2p/sRw== +"@aws-sdk/middleware-bucket-endpoint@3.901.0": + version "3.901.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.901.0.tgz#5b7f740cff9f91d21084b666be225876d72e634b" + integrity sha512-mPF3N6eZlVs9G8aBSzvtoxR1RZqMo1aIwR+X8BAZSkhfj55fVF2no4IfPXfdFO3I66N+zEQ8nKoB0uTATWrogQ== dependencies: - "@aws-sdk/client-sso" "3.714.0" - "@aws-sdk/core" "3.714.0" - "@aws-sdk/token-providers" "3.714.0" - "@aws-sdk/types" "3.714.0" - "@smithy/property-provider" "^3.1.11" - "@smithy/shared-ini-file-loader" "^3.1.12" - "@smithy/types" "^3.7.2" + "@aws-sdk/types" "3.901.0" + "@aws-sdk/util-arn-parser" "3.893.0" + "@smithy/node-config-provider" "^4.3.0" + "@smithy/protocol-http" "^5.3.0" + "@smithy/types" "^4.6.0" + "@smithy/util-config-provider" "^4.2.0" tslib "^2.6.2" -"@aws-sdk/credential-provider-web-identity@3.714.0": - version "3.714.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.714.0.tgz#975b11d07c0f87bc4f6470cb45aef5aee590b78c" - integrity sha512-piKfEJvLrGZ0bH4NPO19d1dtfCZi2p6YJUK/9vRCD1rvJidOuHNeUwIcxTnkIMovQHX12rZVvU9ub0C3CwegUQ== +"@aws-sdk/middleware-expect-continue@3.901.0": + version "3.901.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.901.0.tgz#bd6c1fde979808418ce013c6f5f379e67ef2f4c4" + integrity sha512-bwq9nj6MH38hlJwOY9QXIDwa6lI48UsaZpaXbdD71BljEIRlxDzfB4JaYb+ZNNK7RIAdzsP/K05mJty6KJAQHw== dependencies: - "@aws-sdk/core" "3.714.0" - "@aws-sdk/types" "3.714.0" - "@smithy/property-provider" "^3.1.11" - "@smithy/types" "^3.7.2" + "@aws-sdk/types" "3.901.0" + "@smithy/protocol-http" "^5.3.0" + "@smithy/types" "^4.6.0" tslib "^2.6.2" -"@aws-sdk/middleware-bucket-endpoint@3.714.0": - version "3.714.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.714.0.tgz#c184781eb3e00648e9ca0f6db4522be8e8fbde4e" - integrity sha512-I/xSOskiseJJ8i183Z522BgqbgYzLKP7jGcg2Qeib/IWoG2IP+9DH8pwqagKaPAycyswtnoKBJiiFXY43n0CkA== - dependencies: - "@aws-sdk/types" "3.714.0" - "@aws-sdk/util-arn-parser" "3.693.0" - "@smithy/node-config-provider" "^3.1.12" - "@smithy/protocol-http" "^4.1.8" - "@smithy/types" "^3.7.2" - "@smithy/util-config-provider" "^3.0.0" - tslib "^2.6.2" - -"@aws-sdk/middleware-expect-continue@3.714.0": - version "3.714.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.714.0.tgz#8ea3941adf191c94246a9bc56c443d350fd32589" - integrity sha512-rlzsXdG8Lzo4Qpl35ZnpOBAWlzvDHpP9++0AXoUwAJA0QmMm7auIRmgxJuNj91VwT9h15ZU6xjU4S7fJl4W0+w== - dependencies: - "@aws-sdk/types" "3.714.0" - "@smithy/protocol-http" "^4.1.8" - "@smithy/types" "^3.7.2" - tslib "^2.6.2" - -"@aws-sdk/middleware-flexible-checksums@3.714.0": - version "3.714.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.714.0.tgz#a42c12bfc3d619f40bc1c05d00ce535b4c3264fa" - integrity sha512-y5I2mRmTDRQ/SYa+F30RRE4xchTHmDPDiYKFRRoENxFhVcrv/FbjA3hfhB8Z95JfSalzJVr+XQjd+N+t4M2gMw== +"@aws-sdk/middleware-flexible-checksums@3.908.0": + version "3.908.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.908.0.tgz#ab406ce131a4916d2724c5a297464771bd0df37f" + integrity sha512-hYGhNBvdfnxhhywYRkesdxIZD8rvhsp2CBci5kCqrR2o5VvEkn5+waUQtkREtkciEpC4ent4fadg7N9XfTKvgQ== dependencies: "@aws-crypto/crc32" "5.2.0" "@aws-crypto/crc32c" "5.2.0" "@aws-crypto/util" "5.2.0" - "@aws-sdk/core" "3.714.0" - "@aws-sdk/types" "3.714.0" - "@smithy/is-array-buffer" "^3.0.0" - "@smithy/node-config-provider" "^3.1.12" - "@smithy/protocol-http" "^4.1.8" - "@smithy/types" "^3.7.2" - "@smithy/util-middleware" "^3.0.11" - "@smithy/util-stream" "^3.3.2" - "@smithy/util-utf8" "^3.0.0" + "@aws-sdk/core" "3.908.0" + "@aws-sdk/types" "3.901.0" + "@smithy/is-array-buffer" "^4.2.0" + "@smithy/node-config-provider" "^4.3.0" + "@smithy/protocol-http" "^5.3.0" + "@smithy/types" "^4.6.0" + "@smithy/util-middleware" "^4.2.0" + "@smithy/util-stream" "^4.5.0" + "@smithy/util-utf8" "^4.2.0" tslib "^2.6.2" -"@aws-sdk/middleware-host-header@3.714.0": - version "3.714.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-host-header/-/middleware-host-header-3.714.0.tgz#c14707c2501e70a4343644f876bea5b575dc74e2" - integrity sha512-6l68kjNrh5QC8FGX3I3geBDavWN5Tg1RLHJ2HLA8ByGBtJyCwnz3hEkKfaxn0bBx0hF9DzbfjEOUF6cDqy2Kjg== +"@aws-sdk/middleware-host-header@3.901.0": + version "3.901.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-host-header/-/middleware-host-header-3.901.0.tgz#e6b3a6706601d93949ca25167ecec50c40e3d9de" + integrity sha512-yWX7GvRmqBtbNnUW7qbre3GvZmyYwU0WHefpZzDTYDoNgatuYq6LgUIQ+z5C04/kCRoFkAFrHag8a3BXqFzq5A== dependencies: - "@aws-sdk/types" "3.714.0" - "@smithy/protocol-http" "^4.1.8" - "@smithy/types" "^3.7.2" + "@aws-sdk/types" "3.901.0" + "@smithy/protocol-http" "^5.3.0" + "@smithy/types" "^4.6.0" tslib "^2.6.2" -"@aws-sdk/middleware-location-constraint@3.714.0": - version "3.714.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.714.0.tgz#67bcc4552287481ac631be1c7a3326601be70f9b" - integrity sha512-MX7M+V+FblujKck3fyuzePVIAy9530gY719IiSxV6uN1qLHl7VDJxNblpF/KpXakD6rOg8OpvtmqsXj9aBMftw== +"@aws-sdk/middleware-location-constraint@3.901.0": + version "3.901.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.901.0.tgz#0a74fdd450cdec336f3ccdcb7b2fdbf4ce8b9e0b" + integrity sha512-MuCS5R2ngNoYifkVt05CTULvYVWX0dvRT0/Md4jE3a0u0yMygYy31C1zorwfE/SUgAQXyLmUx8ATmPp9PppImQ== dependencies: - "@aws-sdk/types" "3.714.0" - "@smithy/types" "^3.7.2" + "@aws-sdk/types" "3.901.0" + "@smithy/types" "^4.6.0" tslib "^2.6.2" -"@aws-sdk/middleware-logger@3.714.0": - version "3.714.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-logger/-/middleware-logger-3.714.0.tgz#c059e1aabf28fdfc647db6a3dba625a9813787cd" - integrity sha512-RkqHlMvQWUaRklU1bMfUuBvdWwxgUtEqpADaHXlGVj3vtEY2UgBjy+57CveC4MByqKIunNvVHBBbjrGVtwY7Lg== +"@aws-sdk/middleware-logger@3.901.0": + version "3.901.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-logger/-/middleware-logger-3.901.0.tgz#30562184bd0b6a90d30f2d6d58ef5054300f2652" + integrity sha512-UoHebjE7el/tfRo8/CQTj91oNUm+5Heus5/a4ECdmWaSCHCS/hXTsU3PTTHAY67oAQR8wBLFPfp3mMvXjB+L2A== dependencies: - "@aws-sdk/types" "3.714.0" - "@smithy/types" "^3.7.2" + "@aws-sdk/types" "3.901.0" + "@smithy/types" "^4.6.0" tslib "^2.6.2" -"@aws-sdk/middleware-recursion-detection@3.714.0": - version "3.714.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.714.0.tgz#c2d20d335c035196ac1cd5cdf3f58c5f31b01bdb" - integrity sha512-AVU5ixnh93nqtsfgNc284oXsXaadyHGPHpql/jwgaaqQfEXjS/1/j3j9E/vpacfTTz2Vzo7hAOjnvrOXSEVDaA== +"@aws-sdk/middleware-recursion-detection@3.901.0": + version "3.901.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.901.0.tgz#8492bd83aeee52f4e1b4194a81d044f46acf8c5b" + integrity sha512-Wd2t8qa/4OL0v/oDpCHHYkgsXJr8/ttCxrvCKAt0H1zZe2LlRhY9gpDVKqdertfHrHDj786fOvEQA28G1L75Dg== dependencies: - "@aws-sdk/types" "3.714.0" - "@smithy/protocol-http" "^4.1.8" - "@smithy/types" "^3.7.2" + "@aws-sdk/types" "3.901.0" + "@aws/lambda-invoke-store" "^0.0.1" + "@smithy/protocol-http" "^5.3.0" + "@smithy/types" "^4.6.0" tslib "^2.6.2" -"@aws-sdk/middleware-sdk-s3@3.714.0": - version "3.714.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.714.0.tgz#d07f48aef867bf9f937031406ba70ec98d021ee5" - integrity sha512-YYhX+JefwwEsUxYs0YXn5Mfb97Lo3hwnk3qRIlUkcotCsHYwgCX4jVWjeh8HK+RFFx3Krbh/8/YmzTkI/Z4Z9Q== +"@aws-sdk/middleware-sdk-s3@3.908.0": + version "3.908.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.908.0.tgz#e65cf8668270162e469c74da726e023fbe5bd135" + integrity sha512-23MbAOHsGaD0kTVMVLumaIM1f9vtDImIn2lSvPullbjFHKS4XxfrKuPumtKDzl8gzcux+98XnmfDRKH0fzkOUA== dependencies: - "@aws-sdk/core" "3.714.0" - "@aws-sdk/types" "3.714.0" - "@aws-sdk/util-arn-parser" "3.693.0" - "@smithy/core" "^2.5.5" - "@smithy/node-config-provider" "^3.1.12" - "@smithy/protocol-http" "^4.1.8" - "@smithy/signature-v4" "^4.2.4" - "@smithy/smithy-client" "^3.5.0" - "@smithy/types" "^3.7.2" - "@smithy/util-config-provider" "^3.0.0" - "@smithy/util-middleware" "^3.0.11" - "@smithy/util-stream" "^3.3.2" - "@smithy/util-utf8" "^3.0.0" + "@aws-sdk/core" "3.908.0" + "@aws-sdk/types" "3.901.0" + "@aws-sdk/util-arn-parser" "3.893.0" + "@smithy/core" "^3.15.0" + "@smithy/node-config-provider" "^4.3.0" + "@smithy/protocol-http" "^5.3.0" + "@smithy/signature-v4" "^5.3.0" + "@smithy/smithy-client" "^4.7.1" + "@smithy/types" "^4.6.0" + "@smithy/util-config-provider" "^4.2.0" + "@smithy/util-middleware" "^4.2.0" + "@smithy/util-stream" "^4.5.0" + "@smithy/util-utf8" "^4.2.0" tslib "^2.6.2" -"@aws-sdk/middleware-ssec@3.714.0": - version "3.714.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-ssec/-/middleware-ssec-3.714.0.tgz#a5112a5537da9330ab476c8f9c549e3e9ffba510" - integrity sha512-RkK8REAVwNUQmYbIDRw8eYbMJ8F1Rw4C9mlME4BBMhFlelGcD3ErU2ce24moQbDxBjNwHNESmIqgmdQk93CDCQ== +"@aws-sdk/middleware-ssec@3.901.0": + version "3.901.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-ssec/-/middleware-ssec-3.901.0.tgz#9a08f8a90a12c5d3eccabd884d8dfdd2f76473a4" + integrity sha512-YiLLJmA3RvjL38mFLuu8fhTTGWtp2qT24VqpucgfoyziYcTgIQkJJmKi90Xp6R6/3VcArqilyRgM1+x8i/em+Q== dependencies: - "@aws-sdk/types" "3.714.0" - "@smithy/types" "^3.7.2" + "@aws-sdk/types" "3.901.0" + "@smithy/types" "^4.6.0" tslib "^2.6.2" -"@aws-sdk/middleware-user-agent@3.714.0": - version "3.714.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.714.0.tgz#15cebf91f8bf51f494174911fd8808b32bf3a905" - integrity sha512-OgLjJf7WxUqA2OgiqGCfIc68gsbXlIG8LjObBiF0qlMStAd0L23AGuK5VmYinJlsle9qUpwQvWgKFKaDgdQXgA== +"@aws-sdk/middleware-user-agent@3.908.0": + version "3.908.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.908.0.tgz#aa4827cf1c5290b5d9d44ac39f467735fd199b04" + integrity sha512-R0ePEOku72EvyJWy/D0Z5f/Ifpfxa0U9gySO3stpNhOox87XhsILpcIsCHPy0OHz1a7cMoZsF6rMKSzDeCnogQ== dependencies: - "@aws-sdk/core" "3.714.0" - "@aws-sdk/types" "3.714.0" - "@aws-sdk/util-endpoints" "3.714.0" - "@smithy/core" "^2.5.5" - "@smithy/protocol-http" "^4.1.8" - "@smithy/types" "^3.7.2" + "@aws-sdk/core" "3.908.0" + "@aws-sdk/types" "3.901.0" + "@aws-sdk/util-endpoints" "3.901.0" + "@smithy/core" "^3.15.0" + "@smithy/protocol-http" "^5.3.0" + "@smithy/types" "^4.6.0" tslib "^2.6.2" -"@aws-sdk/region-config-resolver@3.714.0": - version "3.714.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/region-config-resolver/-/region-config-resolver-3.714.0.tgz#26449aeb67daa00560c69bb80cb6cd187ee18dc9" - integrity sha512-HJzsQxgMOAzZrbf/YIqEx30or4tZK1oNAk6Wm6xecUQx+23JXIaePRu1YFUOLBBERQ4QBPpISFurZWBMZ5ibAw== +"@aws-sdk/nested-clients@3.908.0": + version "3.908.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/nested-clients/-/nested-clients-3.908.0.tgz#133f316e6655b509f1b872b0ef352923ac8b86df" + integrity sha512-ZxDYrfxOKXNFHLyvJtT96TJ0p4brZOhwRE4csRXrezEVUN+pNgxuem95YvMALPVhlVqON2CTzr8BX+CcBKvX9Q== dependencies: - "@aws-sdk/types" "3.714.0" - "@smithy/node-config-provider" "^3.1.12" - "@smithy/types" "^3.7.2" - "@smithy/util-config-provider" "^3.0.0" - "@smithy/util-middleware" "^3.0.11" + "@aws-crypto/sha256-browser" "5.2.0" + "@aws-crypto/sha256-js" "5.2.0" + "@aws-sdk/core" "3.908.0" + "@aws-sdk/middleware-host-header" "3.901.0" + "@aws-sdk/middleware-logger" "3.901.0" + "@aws-sdk/middleware-recursion-detection" "3.901.0" + "@aws-sdk/middleware-user-agent" "3.908.0" + "@aws-sdk/region-config-resolver" "3.901.0" + "@aws-sdk/types" "3.901.0" + "@aws-sdk/util-endpoints" "3.901.0" + "@aws-sdk/util-user-agent-browser" "3.907.0" + "@aws-sdk/util-user-agent-node" "3.908.0" + "@smithy/config-resolver" "^4.3.0" + "@smithy/core" "^3.15.0" + "@smithy/fetch-http-handler" "^5.3.1" + "@smithy/hash-node" "^4.2.0" + "@smithy/invalid-dependency" "^4.2.0" + "@smithy/middleware-content-length" "^4.2.0" + "@smithy/middleware-endpoint" "^4.3.1" + "@smithy/middleware-retry" "^4.4.1" + "@smithy/middleware-serde" "^4.2.0" + "@smithy/middleware-stack" "^4.2.0" + "@smithy/node-config-provider" "^4.3.0" + "@smithy/node-http-handler" "^4.3.0" + "@smithy/protocol-http" "^5.3.0" + "@smithy/smithy-client" "^4.7.1" + "@smithy/types" "^4.6.0" + "@smithy/url-parser" "^4.2.0" + "@smithy/util-base64" "^4.3.0" + "@smithy/util-body-length-browser" "^4.2.0" + "@smithy/util-body-length-node" "^4.2.1" + "@smithy/util-defaults-mode-browser" "^4.3.0" + "@smithy/util-defaults-mode-node" "^4.2.1" + "@smithy/util-endpoints" "^3.2.0" + "@smithy/util-middleware" "^4.2.0" + "@smithy/util-retry" "^4.2.0" + "@smithy/util-utf8" "^4.2.0" tslib "^2.6.2" -"@aws-sdk/signature-v4-multi-region@3.714.0": - version "3.714.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.714.0.tgz#91db060340d0006083cc750a4291a3144b4c4392" - integrity sha512-xIQyx0ILRtJZnSUPpMsWkwASuFDYh9GPnr7p+pmfsV5KtRQluHuoH1wPkPTeNuTnAl7RDHUOmcOgTPUCDxiKxg== +"@aws-sdk/region-config-resolver@3.901.0": + version "3.901.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/region-config-resolver/-/region-config-resolver-3.901.0.tgz#6673eeda4ecc0747f93a084e876cab71431a97ca" + integrity sha512-7F0N888qVLHo4CSQOsnkZ4QAp8uHLKJ4v3u09Ly5k4AEStrSlFpckTPyUx6elwGL+fxGjNE2aakK8vEgzzCV0A== dependencies: - "@aws-sdk/middleware-sdk-s3" "3.714.0" - "@aws-sdk/types" "3.714.0" - "@smithy/protocol-http" "^4.1.8" - "@smithy/signature-v4" "^4.2.4" - "@smithy/types" "^3.7.2" + "@aws-sdk/types" "3.901.0" + "@smithy/node-config-provider" "^4.3.0" + "@smithy/types" "^4.6.0" + "@smithy/util-config-provider" "^4.2.0" + "@smithy/util-middleware" "^4.2.0" tslib "^2.6.2" -"@aws-sdk/token-providers@3.714.0": - version "3.714.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/token-providers/-/token-providers-3.714.0.tgz#c308dff77d18978f630188000aebb87be38f5526" - integrity sha512-vKN064aLE3kl+Zl16Ony3jltHnMddMBT7JRkP1L+lLywhA0PcAKxpdvComul/sTBWnbnwLnaS5NsDUhcWySH8A== +"@aws-sdk/signature-v4-multi-region@3.908.0": + version "3.908.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.908.0.tgz#05690b946b11b9a2663012aac503ed3e6f3109bc" + integrity sha512-8OodflIzZM2GVuCGiGK6hqwsbfHRDl4kQcEYzHRg9p91H4h5Y876DPvLRkwM7pSC7LKUL0XkKWWVVjwJbp6/Ig== dependencies: - "@aws-sdk/types" "3.714.0" - "@smithy/property-provider" "^3.1.11" - "@smithy/shared-ini-file-loader" "^3.1.12" - "@smithy/types" "^3.7.2" + "@aws-sdk/middleware-sdk-s3" "3.908.0" + "@aws-sdk/types" "3.901.0" + "@smithy/protocol-http" "^5.3.0" + "@smithy/signature-v4" "^5.3.0" + "@smithy/types" "^4.6.0" tslib "^2.6.2" -"@aws-sdk/types@3.714.0", "@aws-sdk/types@^3.222.0": - version "3.714.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.714.0.tgz#de6afee1436d2d95364efa0663887f3bf0b1303a" - integrity sha512-ZjpP2gYbSFlxxaUDa1Il5AVvfggvUPbjzzB/l3q0gIE5Thd6xKW+yzEpt2mLZ5s5UaYSABZbF94g8NUOF4CVGA== +"@aws-sdk/token-providers@3.908.0": + version "3.908.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/token-providers/-/token-providers-3.908.0.tgz#ed3b064ad07db451a7abc0a8d73e1443aa5f6a19" + integrity sha512-4SosHWRQ8hj1X2yDenCYHParcCjHcd7S+Mdb/lelwF0JBFCNC+dNCI9ws3cP/dFdZO/AIhJQGUBzEQtieloixw== dependencies: - "@smithy/types" "^3.7.2" + "@aws-sdk/core" "3.908.0" + "@aws-sdk/nested-clients" "3.908.0" + "@aws-sdk/types" "3.901.0" + "@smithy/property-provider" "^4.2.0" + "@smithy/shared-ini-file-loader" "^4.3.0" + "@smithy/types" "^4.6.0" tslib "^2.6.2" -"@aws-sdk/util-arn-parser@3.693.0": - version "3.693.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-arn-parser/-/util-arn-parser-3.693.0.tgz#8dae27eb822ab4f88be28bb3c0fc11f1f13d3948" - integrity sha512-WC8x6ca+NRrtpAH64rWu+ryDZI3HuLwlEr8EU6/dbC/pt+r/zC0PBoC15VEygUaBA+isppCikQpGyEDu0Yj7gQ== +"@aws-sdk/types@3.901.0", "@aws-sdk/types@^3.222.0": + version "3.901.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.901.0.tgz#b5a2e26c7b3fb3bbfe4c7fc24873646992a1c56c" + integrity sha512-FfEM25hLEs4LoXsLXQ/q6X6L4JmKkKkbVFpKD4mwfVHtRVQG6QxJiCPcrkcPISquiy6esbwK2eh64TWbiD60cg== + dependencies: + "@smithy/types" "^4.6.0" + tslib "^2.6.2" + +"@aws-sdk/util-arn-parser@3.893.0": + version "3.893.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-arn-parser/-/util-arn-parser-3.893.0.tgz#fcc9b792744b9da597662891c2422dda83881d8d" + integrity sha512-u8H4f2Zsi19DGnwj5FSZzDMhytYF/bCh37vAtBsn3cNDL3YG578X5oc+wSX54pM3tOxS+NY7tvOAo52SW7koUA== dependencies: tslib "^2.6.2" -"@aws-sdk/util-endpoints@3.714.0": - version "3.714.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-endpoints/-/util-endpoints-3.714.0.tgz#f059c27bedf329584358b1f837cd9a5c220f34e2" - integrity sha512-Xv+Z2lhe7w7ZZRsgBwBMZgGTVmS+dkkj2S13uNHAx9lhB5ovM8PhK5G/j28xYf6vIibeuHkRAbb7/ozdZIGR+A== +"@aws-sdk/util-endpoints@3.901.0": + version "3.901.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-endpoints/-/util-endpoints-3.901.0.tgz#be6296739d0f446b89a3f497c3a85afeb6cddd92" + integrity sha512-5nZP3hGA8FHEtKvEQf4Aww5QZOkjLW1Z+NixSd+0XKfHvA39Ah5sZboScjLx0C9kti/K3OGW1RCx5K9Zc3bZqg== dependencies: - "@aws-sdk/types" "3.714.0" - "@smithy/types" "^3.7.2" - "@smithy/util-endpoints" "^2.1.7" + "@aws-sdk/types" "3.901.0" + "@smithy/types" "^4.6.0" + "@smithy/url-parser" "^4.2.0" + "@smithy/util-endpoints" "^3.2.0" tslib "^2.6.2" "@aws-sdk/util-locate-window@^3.0.0": - version "3.693.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-locate-window/-/util-locate-window-3.693.0.tgz#1160f6d055cf074ca198eb8ecf89b6311537ad6c" - integrity sha512-ttrag6haJLWABhLqtg1Uf+4LgHWIMOVSYL+VYZmAp2v4PUGOwWmWQH0Zk8RM7YuQcLfH/EoR72/Yxz6A4FKcuw== + version "3.893.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-locate-window/-/util-locate-window-3.893.0.tgz#5df15f24e1edbe12ff1fe8906f823b51cd53bae8" + integrity sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg== dependencies: tslib "^2.6.2" -"@aws-sdk/util-user-agent-browser@3.714.0": - version "3.714.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.714.0.tgz#7768660fa92a70b78406810a30174fb20508eb61" - integrity sha512-OdJJ03cP9/MgIVToPJPCPUImbpZzTcwdIgbXC0tUQPJhbD7b7cB4LdnkhNHko+MptpOrCq4CPY/33EpOjRdofw== +"@aws-sdk/util-user-agent-browser@3.907.0": + version "3.907.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.907.0.tgz#96b621c66530c061fbc51f5bf4931e64429927d4" + integrity sha512-Hus/2YCQmtCEfr4Ls88d07Q99Ex59uvtktiPTV963Q7w7LHuIT/JBjrbwNxtSm2KlJR9PHNdqxwN+fSuNsMGMQ== dependencies: - "@aws-sdk/types" "3.714.0" - "@smithy/types" "^3.7.2" + "@aws-sdk/types" "3.901.0" + "@smithy/types" "^4.6.0" bowser "^2.11.0" tslib "^2.6.2" -"@aws-sdk/util-user-agent-node@3.714.0": - version "3.714.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.714.0.tgz#b9c86713fb0c9770cec9935eac93a74cf70844fd" - integrity sha512-x8JoZb7yBEbNUmHUNoRAP4L++6A5uZCVf2yFLw8CZKpH4q+Cf1a68ou48OfnND3H0rbBnLXc/3uOlseRvd57/g== +"@aws-sdk/util-user-agent-node@3.908.0": + version "3.908.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.908.0.tgz#37d0397a3dac1705693b4cad5fe79da5bd04e46d" + integrity sha512-l6AEaKUAYarcEy8T8NZ+dNZ00VGLs3fW2Cqu1AuPENaSad0/ahEU+VU7MpXS8FhMRGPgplxKVgCTLyTY0Lbssw== dependencies: - "@aws-sdk/middleware-user-agent" "3.714.0" - "@aws-sdk/types" "3.714.0" - "@smithy/node-config-provider" "^3.1.12" - "@smithy/types" "^3.7.2" + "@aws-sdk/middleware-user-agent" "3.908.0" + "@aws-sdk/types" "3.901.0" + "@smithy/node-config-provider" "^4.3.0" + "@smithy/types" "^4.6.0" tslib "^2.6.2" -"@aws-sdk/xml-builder@3.709.0": - version "3.709.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/xml-builder/-/xml-builder-3.709.0.tgz#5841faa1e78afcea064557a1a56709978b325758" - integrity sha512-2GPCwlNxeHspoK/Mc8nbk9cBOkSpp3j2SJUQmFnyQK6V/pR6II2oPRyZkMomug1Rc10hqlBHByMecq4zhV2uUw== +"@aws-sdk/xml-builder@3.901.0": + version "3.901.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/xml-builder/-/xml-builder-3.901.0.tgz#3cd2e3929cefafd771c8bd790ec6965faa1be49d" + integrity sha512-pxFCkuAP7Q94wMTNPAwi6hEtNrp/BdFf+HOrIEeFQsk4EoOmpKY3I6S+u6A9Wg295J80Kh74LqDWM22ux3z6Aw== dependencies: - "@smithy/types" "^3.7.2" + "@smithy/types" "^4.6.0" + fast-xml-parser "5.2.5" tslib "^2.6.2" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.23.5", "@babel/code-frame@^7.24.2": - version "7.24.2" - resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz" - integrity sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ== - dependencies: - "@babel/highlight" "^7.24.2" - picocolors "^1.0.0" +"@aws/lambda-invoke-store@^0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@aws/lambda-invoke-store/-/lambda-invoke-store-0.0.1.tgz#92d792a7dda250dfcb902e13228f37a81be57c8f" + integrity sha512-ORHRQ2tmvnBXc8t/X9Z8IcSbBA4xTLKuN873FopzklHMeqBst7YG0d+AX97inkvDX+NChYtSr+qGfcqGFaI8Zw== -"@babel/code-frame@^7.27.1": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== @@ -634,38 +596,12 @@ js-tokens "^4.0.0" picocolors "^1.1.1" -"@babel/compat-data@^7.23.5": - version "7.24.4" - resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz" - integrity sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ== - "@babel/compat-data@^7.27.2": - version "7.27.2" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.27.2.tgz#4183f9e642fd84e74e3eea7ffa93a412e3b102c9" - integrity sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ== + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.4.tgz#96fdf1af1b8859c8474ab39c295312bfb7c24b04" + integrity sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw== -"@babel/core@^7.21.3", "@babel/core@^7.23.5": - version "7.24.5" - resolved "https://registry.npmjs.org/@babel/core/-/core-7.24.5.tgz" - integrity sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.24.2" - "@babel/generator" "^7.24.5" - "@babel/helper-compilation-targets" "^7.23.6" - "@babel/helper-module-transforms" "^7.24.5" - "@babel/helpers" "^7.24.5" - "@babel/parser" "^7.24.5" - "@babel/template" "^7.24.0" - "@babel/traverse" "^7.24.5" - "@babel/types" "^7.24.5" - convert-source-map "^2.0.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.3" - semver "^6.3.1" - -"@babel/core@^7.26.10": +"@babel/core@^7.21.3", "@babel/core@^7.26.10", "@babel/core@^7.28.0": version "7.28.4" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.4.tgz#12a550b8794452df4c8b084f95003bce1742d496" integrity sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA== @@ -686,27 +622,6 @@ json5 "^2.2.3" semver "^6.3.1" -"@babel/generator@^7.24.5": - version "7.24.5" - resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.24.5.tgz" - integrity sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA== - dependencies: - "@babel/types" "^7.24.5" - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.25" - jsesc "^2.5.1" - -"@babel/generator@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.27.1.tgz#862d4fad858f7208edd487c28b58144036b76230" - integrity sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w== - dependencies: - "@babel/parser" "^7.27.1" - "@babel/types" "^7.27.1" - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.25" - jsesc "^3.0.2" - "@babel/generator@^7.28.3": version "7.28.3" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.3.tgz#9626c1741c650cbac39121694a0f2d7451b8ef3e" @@ -718,17 +633,6 @@ "@jridgewell/trace-mapping" "^0.3.28" jsesc "^3.0.2" -"@babel/helper-compilation-targets@^7.23.6": - version "7.23.6" - resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz" - integrity sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ== - dependencies: - "@babel/compat-data" "^7.23.5" - "@babel/helper-validator-option" "^7.23.5" - browserslist "^4.22.2" - lru-cache "^5.1.1" - semver "^6.3.1" - "@babel/helper-compilation-targets@^7.27.2": version "7.27.2" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d" @@ -740,38 +644,11 @@ lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-environment-visitor@^7.22.20": - version "7.22.20" - resolved "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz" - integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== - -"@babel/helper-function-name@^7.23.0": - version "7.23.0" - resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz" - integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== - dependencies: - "@babel/template" "^7.22.15" - "@babel/types" "^7.23.0" - "@babel/helper-globals@^7.28.0": version "7.28.0" resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== -"@babel/helper-hoist-variables@^7.22.5": - version "7.22.5" - resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz" - integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-module-imports@^7.24.3": - version "7.24.3" - resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz" - integrity sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg== - dependencies: - "@babel/types" "^7.24.0" - "@babel/helper-module-imports@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204" @@ -780,17 +657,6 @@ "@babel/traverse" "^7.27.1" "@babel/types" "^7.27.1" -"@babel/helper-module-transforms@^7.24.5": - version "7.24.5" - resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz" - integrity sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A== - dependencies: - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-module-imports" "^7.24.3" - "@babel/helper-simple-access" "^7.24.5" - "@babel/helper-split-export-declaration" "^7.24.5" - "@babel/helper-validator-identifier" "^7.24.5" - "@babel/helper-module-transforms@^7.28.3": version "7.28.3" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz#a2b37d3da3b2344fe085dab234426f2b9a2fa5f6" @@ -800,69 +666,26 @@ "@babel/helper-validator-identifier" "^7.27.1" "@babel/traverse" "^7.28.3" -"@babel/helper-plugin-utils@^7.24.0", "@babel/helper-plugin-utils@^7.24.5": - version "7.24.5" - resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz" - integrity sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ== - "@babel/helper-plugin-utils@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c" integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== -"@babel/helper-simple-access@^7.24.5": - version "7.24.5" - resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz" - integrity sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ== - dependencies: - "@babel/types" "^7.24.5" - -"@babel/helper-split-export-declaration@^7.24.5": - version "7.24.5" - resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz" - integrity sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q== - dependencies: - "@babel/types" "^7.24.5" - -"@babel/helper-string-parser@^7.24.1": - version "7.24.1" - resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz" - integrity sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ== - "@babel/helper-string-parser@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== -"@babel/helper-validator-identifier@^7.24.5": - version "7.24.5" - resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz" - integrity sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA== - "@babel/helper-validator-identifier@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8" integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== -"@babel/helper-validator-option@^7.23.5": - version "7.23.5" - resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz" - integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== - "@babel/helper-validator-option@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== -"@babel/helpers@^7.24.5": - version "7.24.5" - resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.5.tgz" - integrity sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q== - dependencies: - "@babel/template" "^7.24.0" - "@babel/traverse" "^7.24.5" - "@babel/types" "^7.24.5" - "@babel/helpers@^7.28.4": version "7.28.4" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.4.tgz#fe07274742e95bdf7cf1443593eeb8926ab63827" @@ -871,29 +694,7 @@ "@babel/template" "^7.27.2" "@babel/types" "^7.28.4" -"@babel/highlight@^7.24.2": - version "7.24.5" - resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz" - integrity sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw== - dependencies: - "@babel/helper-validator-identifier" "^7.24.5" - chalk "^2.4.2" - js-tokens "^4.0.0" - picocolors "^1.0.0" - -"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.24.0", "@babel/parser@^7.24.5": - version "7.24.5" - resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz" - integrity sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg== - -"@babel/parser@^7.27.1", "@babel/parser@^7.27.2": - version "7.27.2" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.27.2.tgz#577518bedb17a2ce4212afd052e01f7df0941127" - integrity sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw== - dependencies: - "@babel/types" "^7.27.1" - -"@babel/parser@^7.28.3", "@babel/parser@^7.28.4": +"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.27.2", "@babel/parser@^7.28.3", "@babel/parser@^7.28.4": version "7.28.4" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.4.tgz#da25d4643532890932cc03f7705fe19637e03fa8" integrity sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg== @@ -907,49 +708,26 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-react-jsx-self@^7.23.3": - version "7.24.5" - resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.5.tgz" - integrity sha512-RtCJoUO2oYrYwFPtR1/jkoBEcFuI1ae9a9IMxeyAVa3a1Ap4AnxmyIKG2b2FaJKqkidw/0cxRbWN+HOs6ZWd1w== +"@babel/plugin-transform-react-jsx-self@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz#af678d8506acf52c577cac73ff7fe6615c85fc92" + integrity sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw== dependencies: - "@babel/helper-plugin-utils" "^7.24.5" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-react-jsx-source@^7.23.3": - version "7.24.1" - resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.1.tgz" - integrity sha512-1v202n7aUq4uXAieRTKcwPzNyphlCuqHHDcdSNc+vdhoTEZcFMh+L5yZuCmGaIO7bs1nJUNfHB89TZyoL48xNA== +"@babel/plugin-transform-react-jsx-source@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz#dcfe2c24094bb757bf73960374e7c55e434f19f0" + integrity sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw== dependencies: - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/runtime@^7.18.3", "@babel/runtime@^7.20.0", "@babel/runtime@^7.20.7": - version "7.26.0" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1" - integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw== - dependencies: - regenerator-runtime "^0.14.0" - -"@babel/runtime@^7.23.2", "@babel/runtime@^7.23.9": - version "7.24.5" - resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz" - integrity sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g== - dependencies: - regenerator-runtime "^0.14.0" - -"@babel/runtime@^7.9.2": +"@babel/runtime@^7.18.3", "@babel/runtime@^7.20.0", "@babel/runtime@^7.20.7", "@babel/runtime@^7.23.2", "@babel/runtime@^7.23.9", "@babel/runtime@^7.9.2": version "7.28.4" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.4.tgz#a70226016fabe25c5783b2f22d3e1c9bc5ca3326" integrity sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ== -"@babel/template@^7.22.15", "@babel/template@^7.24.0": - version "7.24.0" - resolved "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz" - integrity sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA== - dependencies: - "@babel/code-frame" "^7.23.5" - "@babel/parser" "^7.24.0" - "@babel/types" "^7.24.0" - -"@babel/template@^7.27.1", "@babel/template@^7.27.2": +"@babel/template@^7.27.2": version "7.27.2" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== @@ -958,36 +736,7 @@ "@babel/parser" "^7.27.2" "@babel/types" "^7.27.1" -"@babel/traverse@^7.24.5": - version "7.24.5" - resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.5.tgz" - integrity sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA== - dependencies: - "@babel/code-frame" "^7.24.2" - "@babel/generator" "^7.24.5" - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-function-name" "^7.23.0" - "@babel/helper-hoist-variables" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.24.5" - "@babel/parser" "^7.24.5" - "@babel/types" "^7.24.5" - debug "^4.3.1" - globals "^11.1.0" - -"@babel/traverse@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.27.1.tgz#4db772902b133bbddd1c4f7a7ee47761c1b9f291" - integrity sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg== - dependencies: - "@babel/code-frame" "^7.27.1" - "@babel/generator" "^7.27.1" - "@babel/parser" "^7.27.1" - "@babel/template" "^7.27.1" - "@babel/types" "^7.27.1" - debug "^4.3.1" - globals "^11.1.0" - -"@babel/traverse@^7.28.3", "@babel/traverse@^7.28.4": +"@babel/traverse@^7.27.1", "@babel/traverse@^7.28.3", "@babel/traverse@^7.28.4": version "7.28.4" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.4.tgz#8d456101b96ab175d487249f60680221692b958b" integrity sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ== @@ -1000,24 +749,7 @@ "@babel/types" "^7.28.4" debug "^4.3.1" -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.21.3", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.24.0", "@babel/types@^7.24.5": - version "7.24.5" - resolved "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz" - integrity sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ== - dependencies: - "@babel/helper-string-parser" "^7.24.1" - "@babel/helper-validator-identifier" "^7.24.5" - to-fast-properties "^2.0.0" - -"@babel/types@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.27.1.tgz#9defc53c16fc899e46941fc6901a9eea1c9d8560" - integrity sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q== - dependencies: - "@babel/helper-string-parser" "^7.27.1" - "@babel/helper-validator-identifier" "^7.27.1" - -"@babel/types@^7.28.2", "@babel/types@^7.28.4": +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.21.3", "@babel/types@^7.27.1", "@babel/types@^7.28.2", "@babel/types@^7.28.4": version "7.28.4" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.4.tgz#0a4e618f4c60a7cd6c11cb2d48060e4dbe38ac3a" integrity sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q== @@ -1025,162 +757,176 @@ "@babel/helper-string-parser" "^7.27.1" "@babel/helper-validator-identifier" "^7.27.1" -"@bufbuild/protobuf@^2.0.0": - version "2.2.2" - resolved "https://registry.yarnpkg.com/@bufbuild/protobuf/-/protobuf-2.2.2.tgz#1a6d89603fb215dc4d4178052d05b30b83c75402" - integrity sha512-UNtPCbrwrenpmrXuRwn9jYpPoweNXj8X5sMvYgsqYyaH8jQ6LfUJSk3dJLnBK+6sfYPrF4iAIo5sd5HQ+tg75A== +"@borewit/text-codec@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@borewit/text-codec/-/text-codec-0.1.1.tgz#7e7f27092473d5eabcffef693a849f2cc48431da" + integrity sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA== + +"@bufbuild/protobuf@2.9.0", "@bufbuild/protobuf@^2.4.0", "@bufbuild/protobuf@^2.5.0": + version "2.9.0" + resolved "https://registry.yarnpkg.com/@bufbuild/protobuf/-/protobuf-2.9.0.tgz#ff8827be3d8e56d74a03530cff8b0e1952aa115e" + integrity sha512-rnJenoStJ8nvmt9Gzye8nkYd6V22xUAnu4086ER7h1zJ508vStko4pMvDeQ446ilDTFpV5wnoc5YS7XvMwwMqA== + +"@bufbuild/protoplugin@^2.4.0": + version "2.9.0" + resolved "https://registry.yarnpkg.com/@bufbuild/protoplugin/-/protoplugin-2.9.0.tgz#9cee42c572c5b91ffeb5a7e0caf68bae60e6bafe" + integrity sha512-uoiwNVYoTq+AyqaV1L6pBazGx5fXOO89L0NSR9/7hEfo0Y8n9T1jsKGu4mkitLmP3z+8gJREaule1mMuKBPyYw== + dependencies: + "@bufbuild/protobuf" "2.9.0" + "@typescript/vfs" "^1.5.2" + typescript "5.4.5" "@commitlint/cli@^19.6.0": - version "19.6.0" - resolved "https://registry.yarnpkg.com/@commitlint/cli/-/cli-19.6.0.tgz#98e7fc8501cc38b6eef4b7f61e19b15f3c53700e" - integrity sha512-v17BgGD9w5KnthaKxXnEg6KLq6DYiAxyiN44TpiRtqyW8NSq+Kx99mkEG8Qo6uu6cI5eMzMojW2muJxjmPnF8w== + version "19.8.1" + resolved "https://registry.yarnpkg.com/@commitlint/cli/-/cli-19.8.1.tgz#85f7d9f331344e1f0a2b9d8b24fd3695466e1158" + integrity sha512-LXUdNIkspyxrlV6VDHWBmCZRtkEVRpBKxi2Gtw3J54cGWhLCTouVD/Q6ZSaSvd2YaDObWK8mDjrz3TIKtaQMAA== dependencies: - "@commitlint/format" "^19.5.0" - "@commitlint/lint" "^19.6.0" - "@commitlint/load" "^19.5.0" - "@commitlint/read" "^19.5.0" - "@commitlint/types" "^19.5.0" - tinyexec "^0.3.0" + "@commitlint/format" "^19.8.1" + "@commitlint/lint" "^19.8.1" + "@commitlint/load" "^19.8.1" + "@commitlint/read" "^19.8.1" + "@commitlint/types" "^19.8.1" + tinyexec "^1.0.0" yargs "^17.0.0" "@commitlint/config-conventional@^19.6.0": - version "19.6.0" - resolved "https://registry.yarnpkg.com/@commitlint/config-conventional/-/config-conventional-19.6.0.tgz#badba72c8639ea79291e2941001bd7ea7fad3a2c" - integrity sha512-DJT40iMnTYtBtUfw9ApbsLZFke1zKh6llITVJ+x9mtpHD08gsNXaIRqHTmwTZL3dNX5+WoyK7pCN/5zswvkBCQ== + version "19.8.1" + resolved "https://registry.yarnpkg.com/@commitlint/config-conventional/-/config-conventional-19.8.1.tgz#eab42df58cda44f18410ae0cbd6785ece00f214b" + integrity sha512-/AZHJL6F6B/G959CsMAzrPKKZjeEiAVifRyEwXxcT6qtqbPwGw+iQxmNS+Bu+i09OCtdNRW6pNpBvgPrtMr9EQ== dependencies: - "@commitlint/types" "^19.5.0" + "@commitlint/types" "^19.8.1" conventional-changelog-conventionalcommits "^7.0.2" -"@commitlint/config-validator@^19.5.0": - version "19.5.0" - resolved "https://registry.yarnpkg.com/@commitlint/config-validator/-/config-validator-19.5.0.tgz#f0a4eda2109fc716ef01bb8831af9b02e3a1e568" - integrity sha512-CHtj92H5rdhKt17RmgALhfQt95VayrUo2tSqY9g2w+laAXyk7K/Ef6uPm9tn5qSIwSmrLjKaXK9eiNuxmQrDBw== +"@commitlint/config-validator@^19.8.1": + version "19.8.1" + resolved "https://registry.yarnpkg.com/@commitlint/config-validator/-/config-validator-19.8.1.tgz#29e9bb1360fa41b9439b23d8e25deaaf097306b5" + integrity sha512-0jvJ4u+eqGPBIzzSdqKNX1rvdbSU1lPNYlfQQRIFnBgLy26BtC0cFnr7c/AyuzExMxWsMOte6MkTi9I3SQ3iGQ== dependencies: - "@commitlint/types" "^19.5.0" + "@commitlint/types" "^19.8.1" ajv "^8.11.0" -"@commitlint/ensure@^19.5.0": - version "19.5.0" - resolved "https://registry.yarnpkg.com/@commitlint/ensure/-/ensure-19.5.0.tgz#b087374a6a0a0140e5925a82901d234885d9f6dd" - integrity sha512-Kv0pYZeMrdg48bHFEU5KKcccRfKmISSm9MvgIgkpI6m+ohFTB55qZlBW6eYqh/XDfRuIO0x4zSmvBjmOwWTwkg== +"@commitlint/ensure@^19.8.1": + version "19.8.1" + resolved "https://registry.yarnpkg.com/@commitlint/ensure/-/ensure-19.8.1.tgz#938c54d6f586bda600b5c8e8e842edb281546e14" + integrity sha512-mXDnlJdvDzSObafjYrOSvZBwkD01cqB4gbnnFuVyNpGUM5ijwU/r/6uqUmBXAAOKRfyEjpkGVZxaDsCVnHAgyw== dependencies: - "@commitlint/types" "^19.5.0" + "@commitlint/types" "^19.8.1" lodash.camelcase "^4.3.0" lodash.kebabcase "^4.1.1" lodash.snakecase "^4.1.1" lodash.startcase "^4.4.0" lodash.upperfirst "^4.3.1" -"@commitlint/execute-rule@^19.5.0": - version "19.5.0" - resolved "https://registry.yarnpkg.com/@commitlint/execute-rule/-/execute-rule-19.5.0.tgz#c13da8c03ea0379f30856111e27d57518e25b8a2" - integrity sha512-aqyGgytXhl2ejlk+/rfgtwpPexYyri4t8/n4ku6rRJoRhGZpLFMqrZ+YaubeGysCP6oz4mMA34YSTaSOKEeNrg== +"@commitlint/execute-rule@^19.8.1": + version "19.8.1" + resolved "https://registry.yarnpkg.com/@commitlint/execute-rule/-/execute-rule-19.8.1.tgz#53000363b737773e2d25e97c20f15eaa78742067" + integrity sha512-YfJyIqIKWI64Mgvn/sE7FXvVMQER/Cd+s3hZke6cI1xgNT/f6ZAz5heND0QtffH+KbcqAwXDEE1/5niYayYaQA== -"@commitlint/format@^19.5.0": - version "19.5.0" - resolved "https://registry.yarnpkg.com/@commitlint/format/-/format-19.5.0.tgz#d879db2d97d70ae622397839fb8603d56e85a250" - integrity sha512-yNy088miE52stCI3dhG/vvxFo9e4jFkU1Mj3xECfzp/bIS/JUay4491huAlVcffOoMK1cd296q0W92NlER6r3A== +"@commitlint/format@^19.8.1": + version "19.8.1" + resolved "https://registry.yarnpkg.com/@commitlint/format/-/format-19.8.1.tgz#3e09b1291b3e29092d7a86f0afbbcfc0d99d3ad4" + integrity sha512-kSJj34Rp10ItP+Eh9oCItiuN/HwGQMXBnIRk69jdOwEW9llW9FlyqcWYbHPSGofmjsqeoxa38UaEA5tsbm2JWw== dependencies: - "@commitlint/types" "^19.5.0" + "@commitlint/types" "^19.8.1" chalk "^5.3.0" -"@commitlint/is-ignored@^19.6.0": - version "19.6.0" - resolved "https://registry.yarnpkg.com/@commitlint/is-ignored/-/is-ignored-19.6.0.tgz#6adb9097d36b68e00b9c06a73d7a08e9f54c54dc" - integrity sha512-Ov6iBgxJQFR9koOupDPHvcHU9keFupDgtB3lObdEZDroiG4jj1rzky60fbQozFKVYRTUdrBGICHG0YVmRuAJmw== +"@commitlint/is-ignored@^19.8.1": + version "19.8.1" + resolved "https://registry.yarnpkg.com/@commitlint/is-ignored/-/is-ignored-19.8.1.tgz#fed0851360ea2d21799eaf8ec9ef6d98c15536e3" + integrity sha512-AceOhEhekBUQ5dzrVhDDsbMaY5LqtN8s1mqSnT2Kz1ERvVZkNihrs3Sfk1Je/rxRNbXYFzKZSHaPsEJJDJV8dg== dependencies: - "@commitlint/types" "^19.5.0" + "@commitlint/types" "^19.8.1" semver "^7.6.0" -"@commitlint/lint@^19.6.0": - version "19.6.0" - resolved "https://registry.yarnpkg.com/@commitlint/lint/-/lint-19.6.0.tgz#f9fc9b11b808c96bd3f85e882e056daabac40c36" - integrity sha512-LRo7zDkXtcIrpco9RnfhOKeg8PAnE3oDDoalnrVU/EVaKHYBWYL1DlRR7+3AWn0JiBqD8yKOfetVxJGdEtZ0tg== +"@commitlint/lint@^19.8.1": + version "19.8.1" + resolved "https://registry.yarnpkg.com/@commitlint/lint/-/lint-19.8.1.tgz#c21bf9000ca54e41c5b0139c98aaf12473c03bb0" + integrity sha512-52PFbsl+1EvMuokZXLRlOsdcLHf10isTPlWwoY1FQIidTsTvjKXVXYb7AvtpWkDzRO2ZsqIgPK7bI98x8LRUEw== dependencies: - "@commitlint/is-ignored" "^19.6.0" - "@commitlint/parse" "^19.5.0" - "@commitlint/rules" "^19.6.0" - "@commitlint/types" "^19.5.0" + "@commitlint/is-ignored" "^19.8.1" + "@commitlint/parse" "^19.8.1" + "@commitlint/rules" "^19.8.1" + "@commitlint/types" "^19.8.1" -"@commitlint/load@^19.5.0": - version "19.5.0" - resolved "https://registry.yarnpkg.com/@commitlint/load/-/load-19.5.0.tgz#67f90a294894d1f99b930b6152bed2df44a81794" - integrity sha512-INOUhkL/qaKqwcTUvCE8iIUf5XHsEPCLY9looJ/ipzi7jtGhgmtH7OOFiNvwYgH7mA8osUWOUDV8t4E2HAi4xA== +"@commitlint/load@^19.8.1": + version "19.8.1" + resolved "https://registry.yarnpkg.com/@commitlint/load/-/load-19.8.1.tgz#b997b1f65a961bf0a47189f15f6dc8786ceb4576" + integrity sha512-9V99EKG3u7z+FEoe4ikgq7YGRCSukAcvmKQuTtUyiYPnOd9a2/H9Ak1J9nJA1HChRQp9OA/sIKPugGS+FK/k1A== dependencies: - "@commitlint/config-validator" "^19.5.0" - "@commitlint/execute-rule" "^19.5.0" - "@commitlint/resolve-extends" "^19.5.0" - "@commitlint/types" "^19.5.0" + "@commitlint/config-validator" "^19.8.1" + "@commitlint/execute-rule" "^19.8.1" + "@commitlint/resolve-extends" "^19.8.1" + "@commitlint/types" "^19.8.1" chalk "^5.3.0" cosmiconfig "^9.0.0" - cosmiconfig-typescript-loader "^5.0.0" + cosmiconfig-typescript-loader "^6.1.0" lodash.isplainobject "^4.0.6" lodash.merge "^4.6.2" lodash.uniq "^4.5.0" -"@commitlint/message@^19.5.0": - version "19.5.0" - resolved "https://registry.yarnpkg.com/@commitlint/message/-/message-19.5.0.tgz#c062d9a1d2b3302c3a8cac25d6d1125ea9c019b2" - integrity sha512-R7AM4YnbxN1Joj1tMfCyBryOC5aNJBdxadTZkuqtWi3Xj0kMdutq16XQwuoGbIzL2Pk62TALV1fZDCv36+JhTQ== +"@commitlint/message@^19.8.1": + version "19.8.1" + resolved "https://registry.yarnpkg.com/@commitlint/message/-/message-19.8.1.tgz#d5d0d87837483d9f9b4559ffa06e1aaa26d266d6" + integrity sha512-+PMLQvjRXiU+Ae0Wc+p99EoGEutzSXFVwQfa3jRNUZLNW5odZAyseb92OSBTKCu+9gGZiJASt76Cj3dLTtcTdg== -"@commitlint/parse@^19.5.0": - version "19.5.0" - resolved "https://registry.yarnpkg.com/@commitlint/parse/-/parse-19.5.0.tgz#b450dad9b5a95ac5ba472d6d0fdab822dce946fc" - integrity sha512-cZ/IxfAlfWYhAQV0TwcbdR1Oc0/r0Ik1GEessDJ3Lbuma/MRO8FRQX76eurcXtmhJC//rj52ZSZuXUg0oIX0Fw== +"@commitlint/parse@^19.8.1": + version "19.8.1" + resolved "https://registry.yarnpkg.com/@commitlint/parse/-/parse-19.8.1.tgz#73125d04f07f11477cf563cbfe0cc9f6dc85a747" + integrity sha512-mmAHYcMBmAgJDKWdkjIGq50X4yB0pSGpxyOODwYmoexxxiUCy5JJT99t1+PEMK7KtsCtzuWYIAXYAiKR+k+/Jw== dependencies: - "@commitlint/types" "^19.5.0" + "@commitlint/types" "^19.8.1" conventional-changelog-angular "^7.0.0" conventional-commits-parser "^5.0.0" -"@commitlint/read@^19.5.0": - version "19.5.0" - resolved "https://registry.yarnpkg.com/@commitlint/read/-/read-19.5.0.tgz#601f9f1afe69852b0f28aa81cd455b40979fad6b" - integrity sha512-TjS3HLPsLsxFPQj6jou8/CZFAmOP2y+6V4PGYt3ihbQKTY1Jnv0QG28WRKl/d1ha6zLODPZqsxLEov52dhR9BQ== +"@commitlint/read@^19.8.1": + version "19.8.1" + resolved "https://registry.yarnpkg.com/@commitlint/read/-/read-19.8.1.tgz#812930fd0f616e796e122751cb983346e5454ec8" + integrity sha512-03Jbjb1MqluaVXKHKRuGhcKWtSgh3Jizqy2lJCRbRrnWpcM06MYm8th59Xcns8EqBYvo0Xqb+2DoZFlga97uXQ== dependencies: - "@commitlint/top-level" "^19.5.0" - "@commitlint/types" "^19.5.0" + "@commitlint/top-level" "^19.8.1" + "@commitlint/types" "^19.8.1" git-raw-commits "^4.0.0" minimist "^1.2.8" - tinyexec "^0.3.0" + tinyexec "^1.0.0" -"@commitlint/resolve-extends@^19.5.0": - version "19.5.0" - resolved "https://registry.yarnpkg.com/@commitlint/resolve-extends/-/resolve-extends-19.5.0.tgz#f3ec33e12d10df90cae0bfad8e593431fb61b18e" - integrity sha512-CU/GscZhCUsJwcKTJS9Ndh3AKGZTNFIOoQB2n8CmFnizE0VnEuJoum+COW+C1lNABEeqk6ssfc1Kkalm4bDklA== +"@commitlint/resolve-extends@^19.8.1": + version "19.8.1" + resolved "https://registry.yarnpkg.com/@commitlint/resolve-extends/-/resolve-extends-19.8.1.tgz#a44bb4c22e3e7d407cc9a3758fcf58f5c360b694" + integrity sha512-GM0mAhFk49I+T/5UCYns5ayGStkTt4XFFrjjf0L4S26xoMTSkdCf9ZRO8en1kuopC4isDFuEm7ZOm/WRVeElVg== dependencies: - "@commitlint/config-validator" "^19.5.0" - "@commitlint/types" "^19.5.0" + "@commitlint/config-validator" "^19.8.1" + "@commitlint/types" "^19.8.1" global-directory "^4.0.1" import-meta-resolve "^4.0.0" lodash.mergewith "^4.6.2" resolve-from "^5.0.0" -"@commitlint/rules@^19.6.0": - version "19.6.0" - resolved "https://registry.yarnpkg.com/@commitlint/rules/-/rules-19.6.0.tgz#2436da7974c3cf2a7236257f3ef5dd40c4d91312" - integrity sha512-1f2reW7lbrI0X0ozZMesS/WZxgPa4/wi56vFuJENBmed6mWq5KsheN/nxqnl/C23ioxpPO/PL6tXpiiFy5Bhjw== +"@commitlint/rules@^19.8.1": + version "19.8.1" + resolved "https://registry.yarnpkg.com/@commitlint/rules/-/rules-19.8.1.tgz#1cea53d5bf970ce56dc105e1da5e6655a2fe7a5f" + integrity sha512-Hnlhd9DyvGiGwjfjfToMi1dsnw1EXKGJNLTcsuGORHz6SS9swRgkBsou33MQ2n51/boIDrbsg4tIBbRpEWK2kw== dependencies: - "@commitlint/ensure" "^19.5.0" - "@commitlint/message" "^19.5.0" - "@commitlint/to-lines" "^19.5.0" - "@commitlint/types" "^19.5.0" + "@commitlint/ensure" "^19.8.1" + "@commitlint/message" "^19.8.1" + "@commitlint/to-lines" "^19.8.1" + "@commitlint/types" "^19.8.1" -"@commitlint/to-lines@^19.5.0": - version "19.5.0" - resolved "https://registry.yarnpkg.com/@commitlint/to-lines/-/to-lines-19.5.0.tgz#e4b7f34f09064568c96a74de4f1fc9f466c4d472" - integrity sha512-R772oj3NHPkodOSRZ9bBVNq224DOxQtNef5Pl8l2M8ZnkkzQfeSTr4uxawV2Sd3ui05dUVzvLNnzenDBO1KBeQ== +"@commitlint/to-lines@^19.8.1": + version "19.8.1" + resolved "https://registry.yarnpkg.com/@commitlint/to-lines/-/to-lines-19.8.1.tgz#c1a28a84542c7ba321c1c11178b83ae024257b47" + integrity sha512-98Mm5inzbWTKuZQr2aW4SReY6WUukdWXuZhrqf1QdKPZBCCsXuG87c+iP0bwtD6DBnmVVQjgp4whoHRVixyPBg== -"@commitlint/top-level@^19.5.0": - version "19.5.0" - resolved "https://registry.yarnpkg.com/@commitlint/top-level/-/top-level-19.5.0.tgz#0017ffe39b5ba3611a1debd62efe28803601a14f" - integrity sha512-IP1YLmGAk0yWrImPRRc578I3dDUI5A2UBJx9FbSOjxe9sTlzFiwVJ+zeMLgAtHMtGZsC8LUnzmW1qRemkFU4ng== +"@commitlint/top-level@^19.8.1": + version "19.8.1" + resolved "https://registry.yarnpkg.com/@commitlint/top-level/-/top-level-19.8.1.tgz#2c942189d83a29b21ff7ba6e91607301efdf5916" + integrity sha512-Ph8IN1IOHPSDhURCSXBz44+CIu+60duFwRsg6HqaISFHQHbmBtxVw4ZrFNIYUzEP7WwrNPxa2/5qJ//NK1FGcw== dependencies: find-up "^7.0.0" -"@commitlint/types@^19.5.0": - version "19.5.0" - resolved "https://registry.yarnpkg.com/@commitlint/types/-/types-19.5.0.tgz#c5084d1231d4dd50e40bdb656ee7601f691400b3" - integrity sha512-DSHae2obMSMkAtTBSOulg5X7/z+rGLxcXQIkg3OmWvY6wifojge5uVMydfhUvs7yQj+V7jNmRZ2Xzl8GJyqRgg== +"@commitlint/types@^19.8.1": + version "19.8.1" + resolved "https://registry.yarnpkg.com/@commitlint/types/-/types-19.8.1.tgz#7971fbd56b0cfb31692a4e1941b74ac8217c44e5" + integrity sha512-/yCrWGCoA1SVKOks25EGadP9Pnj0oAIHGpl2wH2M2Y46dPM2ueb8wyCVOD7O3WCTkaJ0IkKvzhl1JY7+uCT2Dw== dependencies: "@types/conventional-commits-parser" "^5.0.0" chalk "^5.3.0" @@ -1192,9 +938,37 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" +"@csstools/color-helpers@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@csstools/color-helpers/-/color-helpers-5.1.0.tgz#106c54c808cabfd1ab4c602d8505ee584c2996ef" + integrity sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA== + +"@csstools/css-calc@^2.1.3", "@csstools/css-calc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@csstools/css-calc/-/css-calc-2.1.4.tgz#8473f63e2fcd6e459838dd412401d5948f224c65" + integrity sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ== + +"@csstools/css-color-parser@^3.0.9": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz#4e386af3a99dd36c46fef013cfe4c1c341eed6f0" + integrity sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA== + dependencies: + "@csstools/color-helpers" "^5.1.0" + "@csstools/css-calc" "^2.1.4" + +"@csstools/css-parser-algorithms@^3.0.4": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz#5755370a9a29abaec5515b43c8b3f2cf9c2e3076" + integrity sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ== + +"@csstools/css-tokenizer@^3.0.3": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz#333fedabc3fd1a8e5d0100013731cf19e6a8c5d3" + integrity sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw== + "@develar/schema-utils@~2.6.5": version "2.6.5" - resolved "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz" + resolved "https://registry.yarnpkg.com/@develar/schema-utils/-/schema-utils-2.6.5.tgz#3ece22c5838402419a6e0425f85742b961d9b6c6" integrity sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig== dependencies: ajv "^6.12.0" @@ -1202,7 +976,7 @@ "@electron-toolkit/eslint-config-prettier@^2.0.0": version "2.0.0" - resolved "https://registry.npmjs.org/@electron-toolkit/eslint-config-prettier/-/eslint-config-prettier-2.0.0.tgz" + resolved "https://registry.yarnpkg.com/@electron-toolkit/eslint-config-prettier/-/eslint-config-prettier-2.0.0.tgz#a258a28fc0a09b7f910fd71e0aa18ea6ee440735" integrity sha512-L+uG1FvJcAZkPZpSi6B1pmdpyJFyOxWDTjr1Vs47vSryxv/EX1Ch6o4HVsachlDq3fMEkDgojuP2F3ZvVZMoLw== dependencies: eslint-config-prettier "^9.1.0" @@ -1223,7 +997,7 @@ "@electron-toolkit/tsconfig@^1.0.1": version "1.0.1" - resolved "https://registry.npmjs.org/@electron-toolkit/tsconfig/-/tsconfig-1.0.1.tgz" + resolved "https://registry.yarnpkg.com/@electron-toolkit/tsconfig/-/tsconfig-1.0.1.tgz#78048d3178dd7a6a573590e23240f0764b0c14af" integrity sha512-M0Mol3odspvtCuheyujLNAW7bXq7KFNYVMRtpjFa4ZfES4MuklXBC7Nli/omvc+PRKlrklgAGx3l4VakjNo8jg== "@electron-toolkit/utils@^4.0.0": @@ -1241,11 +1015,10 @@ minimatch "^3.0.4" "@electron/asar@^3.2.7": - version "3.2.13" - resolved "https://registry.yarnpkg.com/@electron/asar/-/asar-3.2.13.tgz#56565ea423ead184465adfa72663b2c70d9835f2" - integrity sha512-pY5z2qQSwbFzJsBdgfJIzXf5ElHTVMutC2dxh0FD60njknMu3n1NnTABOcQwbb5/v5soqE79m9UjaJryBf3epg== + version "3.4.1" + resolved "https://registry.yarnpkg.com/@electron/asar/-/asar-3.4.1.tgz#4e9196a4b54fba18c56cd8d5cac67c5bdc588065" + integrity sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA== dependencies: - "@types/glob" "^7.1.0" commander "^5.0.0" glob "^7.1.6" minimatch "^3.0.4" @@ -1261,7 +1034,7 @@ "@electron/get@^2.0.0": version "2.0.3" - resolved "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz" + resolved "https://registry.yarnpkg.com/@electron/get/-/get-2.0.3.tgz#fba552683d387aebd9f3fcadbcafc8e12ee4f960" integrity sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ== dependencies: debug "^4.1.1" @@ -1274,9 +1047,9 @@ optionalDependencies: global-agent "^3.0.0" -"@electron/node-gyp@https://github.com/electron/node-gyp#06b29aafb7708acef8b3669835c8a7857ebc92d2": +"@electron/node-gyp@git+https://github.com/electron/node-gyp.git#06b29aafb7708acef8b3669835c8a7857ebc92d2": version "10.2.0-electron.1" - resolved "https://github.com/electron/node-gyp#06b29aafb7708acef8b3669835c8a7857ebc92d2" + resolved "git+https://github.com/electron/node-gyp.git#06b29aafb7708acef8b3669835c8a7857ebc92d2" dependencies: env-paths "^2.2.0" exponential-backoff "^3.1.1" @@ -1589,25 +1362,20 @@ integrity sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw== "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": - version "4.4.0" - resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz" - integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + version "4.9.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz#7308df158e064f0dd8b8fdb58aa14fa2a7f913b3" + integrity sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g== dependencies: - eslint-visitor-keys "^3.3.0" + eslint-visitor-keys "^3.4.3" -"@eslint-community/regexpp@^4.10.0": +"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.6.1": version "4.12.1" resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== -"@eslint-community/regexpp@^4.6.1": - version "4.10.0" - resolved "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz" - integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== - "@eslint/eslintrc@^2.1.4": version "2.1.4" - resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== dependencies: ajv "^6.12.4" @@ -1625,45 +1393,32 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2" integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q== -"@floating-ui/core@^1.6.0": - version "1.6.8" - resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.8.tgz#aa43561be075815879305965020f492cdb43da12" - integrity sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA== +"@floating-ui/core@^1.7.3": + version "1.7.3" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.7.3.tgz#462d722f001e23e46d86fd2bd0d21b7693ccb8b7" + integrity sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w== dependencies: - "@floating-ui/utils" "^0.2.8" + "@floating-ui/utils" "^0.2.10" -"@floating-ui/dom@^1.0.0": - version "1.6.12" - resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.12.tgz#6333dcb5a8ead3b2bf82f33d6bc410e95f54e556" - integrity sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w== +"@floating-ui/dom@^1.0.0", "@floating-ui/dom@^1.6.1", "@floating-ui/dom@^1.7.4": + version "1.7.4" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.7.4.tgz#ee667549998745c9c3e3e84683b909c31d6c9a77" + integrity sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA== dependencies: - "@floating-ui/core" "^1.6.0" - "@floating-ui/utils" "^0.2.8" - -"@floating-ui/dom@^1.6.1": - version "1.6.13" - resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.13.tgz#a8a938532aea27a95121ec16e667a7cbe8c59e34" - integrity sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w== - dependencies: - "@floating-ui/core" "^1.6.0" - "@floating-ui/utils" "^0.2.9" + "@floating-ui/core" "^1.7.3" + "@floating-ui/utils" "^0.2.10" "@floating-ui/react-dom@^2.0.0": - version "2.1.2" - resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.2.tgz#a1349bbf6a0e5cb5ded55d023766f20a4d439a31" - integrity sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A== + version "2.1.6" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.6.tgz#189f681043c1400561f62972f461b93f01bf2231" + integrity sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw== dependencies: - "@floating-ui/dom" "^1.0.0" + "@floating-ui/dom" "^1.7.4" -"@floating-ui/utils@^0.2.8": - version "0.2.8" - resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.8.tgz#21a907684723bbbaa5f0974cf7730bd797eb8e62" - integrity sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig== - -"@floating-ui/utils@^0.2.9": - version "0.2.9" - resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.9.tgz#50dea3616bc8191fb8e112283b49eaff03e78429" - integrity sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg== +"@floating-ui/utils@^0.2.10": + version "0.2.10" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.10.tgz#a2a1e3812d14525f725d011a73eceb41fef5bc1c" + integrity sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ== "@fontsource/noto-sans@^5.2.10": version "5.2.10" @@ -1693,7 +1448,7 @@ "@humanwhocodes/module-importer@^1.0.1": version "1.0.1" - resolved "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== "@humanwhocodes/object-schema@^2.0.3": @@ -1701,9 +1456,21 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== +"@isaacs/balanced-match@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz#3081dadbc3460661b751e7591d7faea5df39dd29" + integrity sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ== + +"@isaacs/brace-expansion@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz#4b3dabab7d8e75a429414a96bd67bf4c1d13e0f3" + integrity sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA== + dependencies: + "@isaacs/balanced-match" "^4.0.1" + "@isaacs/cliui@^8.0.2": version "8.0.2" - resolved "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== dependencies: string-width "^5.1.2" @@ -1720,7 +1487,7 @@ dependencies: minipass "^7.0.4" -"@jridgewell/gen-mapping@^0.3.12": +"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5": version "0.3.13" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== @@ -1728,15 +1495,6 @@ "@jridgewell/sourcemap-codec" "^1.5.0" "@jridgewell/trace-mapping" "^0.3.24" -"@jridgewell/gen-mapping@^0.3.5": - version "0.3.5" - resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz" - integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== - dependencies: - "@jridgewell/set-array" "^1.2.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.24" - "@jridgewell/remapping@^2.3.5": version "2.3.5" resolved "https://registry.yarnpkg.com/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1" @@ -1747,23 +1505,13 @@ "@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": version "3.1.2" - resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== -"@jridgewell/set-array@^1.2.1": - version "1.2.1" - resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz" - integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== - -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": - version "1.4.15" - resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz" - integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== - -"@jridgewell/sourcemap-codec@^1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" - integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0", "@jridgewell/sourcemap-codec@^1.5.5": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== "@jridgewell/trace-mapping@0.3.9": version "0.3.9" @@ -1773,15 +1521,7 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": - version "0.3.25" - resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz" - integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - -"@jridgewell/trace-mapping@^0.3.28": +"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.28": version "0.3.31" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== @@ -1798,7 +1538,7 @@ "@malept/flatpak-bundler@^0.4.0": version "0.4.0" - resolved "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz" + resolved "https://registry.yarnpkg.com/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz#e8a32c30a95d20c2b1bb635cc580981a06389858" integrity sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q== dependencies: debug "^4.1.1" @@ -1806,23 +1546,23 @@ lodash "^4.17.15" tmp-promise "^3.0.2" -"@monaco-editor/loader@^1.4.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@monaco-editor/loader/-/loader-1.4.0.tgz#f08227057331ec890fa1e903912a5b711a2ad558" - integrity sha512-00ioBig0x642hytVspPl7DbQyaSWRaolYie/UFNjoTdvoKPzo6xrXLhTk9ixgIKcLH5b5vDOjVNiGyY+uDCUlg== +"@monaco-editor/loader@^1.5.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@monaco-editor/loader/-/loader-1.6.0.tgz#020a7ea717a63ff2a28940d7aa589a178a83356e" + integrity sha512-PdWBZkPb2Amc1kxRsIEyQFkAya6PzUvSf4waXMPt7I05Azm4e2hQuViBVjfw1Mmck98GG2TqecLzWtg1tbyriw== dependencies: state-local "^1.0.6" "@monaco-editor/react@^4.6.0": - version "4.6.0" - resolved "https://registry.yarnpkg.com/@monaco-editor/react/-/react-4.6.0.tgz#bcc68671e358a21c3814566b865a54b191e24119" - integrity sha512-RFkU9/i7cN2bsq/iTkurMWOEErmYcY6JiQI3Jn+WeR/FGISH8JbHERjpS9oRuSOPvDMJI0Z8nJeKkbOs9sBYQw== + version "4.7.0" + resolved "https://registry.yarnpkg.com/@monaco-editor/react/-/react-4.7.0.tgz#35a1ec01bfe729f38bfc025df7b7bac145602a60" + integrity sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA== dependencies: - "@monaco-editor/loader" "^1.4.0" + "@monaco-editor/loader" "^1.5.0" "@nodelib/fs.scandir@2.1.5": version "2.1.5" - resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== dependencies: "@nodelib/fs.stat" "2.0.5" @@ -1830,12 +1570,12 @@ "@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": version "2.0.5" - resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== "@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": version "1.2.8" - resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== dependencies: "@nodelib/fs.scandir" "2.1.5" @@ -1857,56 +1597,138 @@ mkdirp "^1.0.4" rimraf "^3.0.2" +"@parcel/watcher-android-arm64@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz#507f836d7e2042f798c7d07ad19c3546f9848ac1" + integrity sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA== + +"@parcel/watcher-darwin-arm64@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz#3d26dce38de6590ef79c47ec2c55793c06ad4f67" + integrity sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw== + +"@parcel/watcher-darwin-x64@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz#99f3af3869069ccf774e4ddfccf7e64fd2311ef8" + integrity sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg== + +"@parcel/watcher-freebsd-x64@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz#14d6857741a9f51dfe51d5b08b7c8afdbc73ad9b" + integrity sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ== + +"@parcel/watcher-linux-arm-glibc@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz#43c3246d6892381db473bb4f663229ad20b609a1" + integrity sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA== + +"@parcel/watcher-linux-arm-musl@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz#663750f7090bb6278d2210de643eb8a3f780d08e" + integrity sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q== + +"@parcel/watcher-linux-arm64-glibc@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz#ba60e1f56977f7e47cd7e31ad65d15fdcbd07e30" + integrity sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w== + +"@parcel/watcher-linux-arm64-musl@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz#f7fbcdff2f04c526f96eac01f97419a6a99855d2" + integrity sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg== + +"@parcel/watcher-linux-x64-glibc@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz#4d2ea0f633eb1917d83d483392ce6181b6a92e4e" + integrity sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A== + +"@parcel/watcher-linux-x64-musl@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz#277b346b05db54f55657301dd77bdf99d63606ee" + integrity sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg== + +"@parcel/watcher-win32-arm64@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz#7e9e02a26784d47503de1d10e8eab6cceb524243" + integrity sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw== + +"@parcel/watcher-win32-ia32@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz#2d0f94fa59a873cdc584bf7f6b1dc628ddf976e6" + integrity sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ== + +"@parcel/watcher-win32-x64@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz#ae52693259664ba6f2228fa61d7ee44b64ea0947" + integrity sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA== + +"@parcel/watcher@^2.4.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.5.1.tgz#342507a9cfaaf172479a882309def1e991fb1200" + integrity sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg== + dependencies: + detect-libc "^1.0.3" + is-glob "^4.0.3" + micromatch "^4.0.5" + node-addon-api "^7.0.0" + optionalDependencies: + "@parcel/watcher-android-arm64" "2.5.1" + "@parcel/watcher-darwin-arm64" "2.5.1" + "@parcel/watcher-darwin-x64" "2.5.1" + "@parcel/watcher-freebsd-x64" "2.5.1" + "@parcel/watcher-linux-arm-glibc" "2.5.1" + "@parcel/watcher-linux-arm-musl" "2.5.1" + "@parcel/watcher-linux-arm64-glibc" "2.5.1" + "@parcel/watcher-linux-arm64-musl" "2.5.1" + "@parcel/watcher-linux-x64-glibc" "2.5.1" + "@parcel/watcher-linux-x64-musl" "2.5.1" + "@parcel/watcher-win32-arm64" "2.5.1" + "@parcel/watcher-win32-ia32" "2.5.1" + "@parcel/watcher-win32-x64" "2.5.1" + "@pkgjs/parseargs@^0.11.0": version "0.11.0" - resolved "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz" + resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== -"@pkgr/core@^0.1.0": - version "0.1.1" - resolved "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz" - integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA== +"@pkgr/core@^0.2.9": + version "0.2.9" + resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.2.9.tgz#d229a7b7f9dac167a156992ef23c7f023653f53b" + integrity sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA== "@primer/octicons-react@^19.9.0": - version "19.9.0" - resolved "https://registry.npmjs.org/@primer/octicons-react/-/octicons-react-19.9.0.tgz" - integrity sha512-Uk4XrHyfylyfzZN9d8VFjF8FpfYHEyT4sabw+9+oP+GWAJHhPvNPTz6gXvUzJZmoblAvgcTrDslIPjz8zMh76w== - -"@protobuf-ts/plugin-framework@^2.10.0": - version "2.10.0" - resolved "https://registry.yarnpkg.com/@protobuf-ts/plugin-framework/-/plugin-framework-2.10.0.tgz#e716a4b7fdb8710527101d145a000e254731abbb" - integrity sha512-EuW9irbt+w7Ml1CaAxK6xyl7pSuWVbNy0rsChxJEthMrAVTN5EPdJ3whNWvsRBa+HwRImEl8KHNnRoq/vGOHbg== - dependencies: - "@protobuf-ts/runtime" "^2.10.0" - typescript "^3.9" + version "19.19.0" + resolved "https://registry.yarnpkg.com/@primer/octicons-react/-/octicons-react-19.19.0.tgz#e4e575004d9fb52b1391952fa8475af7b54becb3" + integrity sha512-dTO3khy50yS7XC0FB5L7Wwg+aEjI7mrdiZ+FeZGKiNSpkpcRDn7HTidLdtKgo0cJp6QKpqtUHGHRRpa+wrc6Bg== "@protobuf-ts/plugin@^2.10.0": - version "2.10.0" - resolved "https://registry.yarnpkg.com/@protobuf-ts/plugin/-/plugin-2.10.0.tgz#dc91af30aeecf2a9debe2d0abf5b2a07a25156c5" - integrity sha512-iMX4C4TVfMNRLn2msK0cVg5jmizjtu5FYiy8EK5Lg6EgyR9TVHeK2rzmufWKYM2Pcg1jSwC0cFcXHQnCoeFxUg== + version "2.11.1" + resolved "https://registry.yarnpkg.com/@protobuf-ts/plugin/-/plugin-2.11.1.tgz#c819ff9aca102209d2096414b3d2240dd240cbdb" + integrity sha512-HyuprDcw0bEEJqkOWe1rnXUP0gwYLij8YhPuZyZk6cJbIgc/Q0IFgoHQxOXNIXAcXM4Sbehh6kjVnCzasElw1A== dependencies: - "@protobuf-ts/plugin-framework" "^2.10.0" - "@protobuf-ts/protoc" "^2.10.0" - "@protobuf-ts/runtime" "^2.10.0" - "@protobuf-ts/runtime-rpc" "^2.10.0" + "@bufbuild/protobuf" "^2.4.0" + "@bufbuild/protoplugin" "^2.4.0" + "@protobuf-ts/protoc" "^2.11.1" + "@protobuf-ts/runtime" "^2.11.1" + "@protobuf-ts/runtime-rpc" "^2.11.1" typescript "^3.9" -"@protobuf-ts/protoc@^2.10.0": - version "2.10.0" - resolved "https://registry.yarnpkg.com/@protobuf-ts/protoc/-/protoc-2.10.0.tgz#4a280ead9d5b143b6ef863339980050184bcac49" - integrity sha512-S4BtGBh22+uL5E6qLVxV0QNY6tiLVB8QL7RIkvo+KYknipZfSNwubdKy5CPkrwVXzJn4s3cx7bKx1w6BxkBIPg== +"@protobuf-ts/protoc@^2.11.1": + version "2.11.1" + resolved "https://registry.yarnpkg.com/@protobuf-ts/protoc/-/protoc-2.11.1.tgz#5ae86a777cacdc58ddc7e38ee107a74342a86e30" + integrity sha512-mUZJaV0daGO6HUX90o/atzQ6A7bbN2RSuHtdwo8SSF2Qoe3zHwa4IHyCN1evftTeHfLmdz+45qo47sL+5P8nyg== -"@protobuf-ts/runtime-rpc@^2.10.0": - version "2.10.0" - resolved "https://registry.yarnpkg.com/@protobuf-ts/runtime-rpc/-/runtime-rpc-2.10.0.tgz#68de8dcc369e56579569a4deafd394cf4683dc66" - integrity sha512-8CS/XPv3+pMK4v8UKhtCdvbS4h9l7aqlteKdRt0/UbIKZ8n0qHj6hX8cBhz2ngvohxCOS0N08zPr9aCLBNhW3Q== +"@protobuf-ts/runtime-rpc@^2.11.1": + version "2.11.1" + resolved "https://registry.yarnpkg.com/@protobuf-ts/runtime-rpc/-/runtime-rpc-2.11.1.tgz#a6eb2f384bceae8d23a01d0b0e37faf0af36c179" + integrity sha512-4CqqUmNA+/uMz00+d3CYKgElXO9VrEbucjnBFEjqI4GuDrEQ32MaI3q+9qPBvIGOlL4PmHXrzM32vBPWRhQKWQ== dependencies: - "@protobuf-ts/runtime" "^2.10.0" + "@protobuf-ts/runtime" "^2.11.1" -"@protobuf-ts/runtime@^2.10.0": - version "2.10.0" - resolved "https://registry.yarnpkg.com/@protobuf-ts/runtime/-/runtime-2.10.0.tgz#bc90f632647ff2ae72887546ddf3d193f3f43d98" - integrity sha512-ypYwGg9Pn3W/2lZ7/HW60hONGuSdzphvOY8Dq7LeNttymDe0y3LaTUUMRpuGqOT6FfrWEMnfQbyqU8AAreo8wA== +"@protobuf-ts/runtime@^2.11.1": + version "2.11.1" + resolved "https://registry.yarnpkg.com/@protobuf-ts/runtime/-/runtime-2.11.1.tgz#ee2bf2fac6e2d8deac0ca63471a77481548e5553" + integrity sha512-KuDaT1IfHkugM2pyz+FwiY80ejWrkH1pAtOBOZFuR6SXEFTsnb/jiQWQ1rCIrcKx2BtyxnxW6BWwsVSA/Ie+WQ== "@radix-ui/primitive@1.1.3": version "1.1.3" @@ -2142,10 +1964,12 @@ integrity sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA== "@reduxjs/toolkit@^2.2.3": - version "2.2.5" - resolved "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.2.5.tgz" - integrity sha512-aeFA/s5NCG7NoJe/MhmwREJxRkDs0ZaSqt0MxhWUrwCf1UQXpwR87RROJEql0uAkLI6U7snBOYOcKw83ew3FPg== + version "2.9.0" + resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-2.9.0.tgz#d4b12b90c27716a6a507193f8c3b2880729b97d5" + integrity sha512-fSfQlSRu9Z5yBkvsNhYF2rPS8cGXn/TZVrlwN1948QyZ8xMZ0JvP50S2acZNaf+o63u6aEeMjipFyksjIcWrog== dependencies: + "@standard-schema/spec" "^1.0.0" + "@standard-schema/utils" "^0.3.0" immer "^10.0.3" redux "^5.0.1" redux-thunk "^3.1.0" @@ -2156,10 +1980,15 @@ resolved "https://registry.yarnpkg.com/@remirror/core-constants/-/core-constants-3.0.0.tgz#96fdb89d25c62e7b6a5d08caf0ce5114370e3b8f" integrity sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg== -"@remix-run/router@1.19.2": - version "1.19.2" - resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.19.2.tgz#0c896535473291cb41f152c180bedd5680a3b273" - integrity sha512-baiMx18+IMuD1yyvOGaHM9QrVUPGGG0jC+z+IPHnRJWUAUvaKuWKyE8gjDj2rzv3sz9zOGoRSPgeBVHRhZnBlA== +"@remix-run/router@1.23.0": + version "1.23.0" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.23.0.tgz#35390d0e7779626c026b11376da6789eb8389242" + integrity sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA== + +"@rolldown/pluginutils@1.0.0-beta.27": + version "1.0.0-beta.27" + resolved "https://registry.yarnpkg.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz#47d2bf4cef6d470b22f5831b420f8964e0bf755f" + integrity sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA== "@rollup/pluginutils@^5.2.0": version "5.3.0" @@ -2282,159 +2111,161 @@ "@sindresorhus/is@^4.0.0": version "4.6.0" - resolved "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== -"@smithy/abort-controller@^3.1.9": - version "3.1.9" - resolved "https://registry.yarnpkg.com/@smithy/abort-controller/-/abort-controller-3.1.9.tgz#47d323f754136a489e972d7fd465d534d72fcbff" - integrity sha512-yiW0WI30zj8ZKoSYNx90no7ugVn3khlyH/z5W8qtKBtVE6awRALbhSG+2SAHA1r6bO/6M9utxYKVZ3PCJ1rWxw== +"@smithy/abort-controller@^4.2.1": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@smithy/abort-controller/-/abort-controller-4.2.1.tgz#6e14bd7f30023dfd904758b552e5b88f687d0dd8" + integrity sha512-OvVe992TXYHR7QpYebmtw+/MF5AP9vU0fjfyfW1VmNYeA/dfibLhN13xrzIj+EO0HYMPur5lUIB9hRZ7IhjLDQ== dependencies: - "@smithy/types" "^3.7.2" + "@smithy/types" "^4.7.0" tslib "^2.6.2" -"@smithy/chunked-blob-reader-native@^3.0.1": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-3.0.1.tgz#39045ed278ee1b6f4c12715c7565678557274c29" - integrity sha512-VEYtPvh5rs/xlyqpm5NRnfYLZn+q0SRPELbvBV+C/G7IQ+ouTuo+NKKa3ShG5OaFR8NYVMXls9hPYLTvIKKDrQ== +"@smithy/chunked-blob-reader-native@^4.2.1": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.2.1.tgz#380266951d746b522b4ab2b16bfea6b451147b41" + integrity sha512-lX9Ay+6LisTfpLid2zZtIhSEjHMZoAR5hHCR4H7tBz/Zkfr5ea8RcQ7Tk4mi0P76p4cN+Btz16Ffno7YHpKXnQ== dependencies: - "@smithy/util-base64" "^3.0.0" + "@smithy/util-base64" "^4.3.0" tslib "^2.6.2" -"@smithy/chunked-blob-reader@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@smithy/chunked-blob-reader/-/chunked-blob-reader-4.0.0.tgz#754099909957fb1986c16eb88afad75919d7129d" - integrity sha512-jSqRnZvkT4egkq/7b6/QRCNXmmYVcHwnJldqJ3IhVpQE2atObVJ137xmGeuGFhjFUr8gCEVAOKwSY79OvpbDaQ== +"@smithy/chunked-blob-reader@^5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.2.0.tgz#776fec5eaa5ab5fa70d0d0174b7402420b24559c" + integrity sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA== dependencies: tslib "^2.6.2" -"@smithy/config-resolver@^3.0.13": - version "3.0.13" - resolved "https://registry.yarnpkg.com/@smithy/config-resolver/-/config-resolver-3.0.13.tgz#653643a77a33d0f5907a5e7582353886b07ba752" - integrity sha512-Gr/qwzyPaTL1tZcq8WQyHhTZREER5R1Wytmz4WnVGL4onA3dNk6Btll55c8Vr58pLdvWZmtG8oZxJTw3t3q7Jg== +"@smithy/config-resolver@^4.3.0", "@smithy/config-resolver@^4.3.1": + version "4.3.1" + resolved "https://registry.yarnpkg.com/@smithy/config-resolver/-/config-resolver-4.3.1.tgz#f1a0ed6faa52377909440002e1632be9fc901840" + integrity sha512-tWDwrWy37CDVGeaP8AIGZPFL2RoFtmd5Y+nTzLw5qroXNedT2S66EY2d+XzB1zxulCd6nfDXnAQu4auq90aj5Q== dependencies: - "@smithy/node-config-provider" "^3.1.12" - "@smithy/types" "^3.7.2" - "@smithy/util-config-provider" "^3.0.0" - "@smithy/util-middleware" "^3.0.11" + "@smithy/node-config-provider" "^4.3.1" + "@smithy/types" "^4.7.0" + "@smithy/util-config-provider" "^4.2.0" + "@smithy/util-middleware" "^4.2.1" tslib "^2.6.2" -"@smithy/core@^2.5.5": - version "2.5.5" - resolved "https://registry.yarnpkg.com/@smithy/core/-/core-2.5.5.tgz#c75b15caee9e58c800db3e6b99e9e373532d394a" - integrity sha512-G8G/sDDhXA7o0bOvkc7bgai6POuSld/+XhNnWAbpQTpLv2OZPvyqQ58tLPPlz0bSNsXktldDDREIv1LczFeNEw== +"@smithy/core@^3.15.0", "@smithy/core@^3.16.0": + version "3.16.0" + resolved "https://registry.yarnpkg.com/@smithy/core/-/core-3.16.0.tgz#43718e4df99059e5ad5cecc21b6287bf7932ae6d" + integrity sha512-T6eJ+yhnCP5plm6aEaenUpxkHTd5zVCKpyWAbP4ekJ7R5wSmKQjmvQIA58CXB1sgrwaYZJXOJMeRtpghxP7n1g== dependencies: - "@smithy/middleware-serde" "^3.0.11" - "@smithy/protocol-http" "^4.1.8" - "@smithy/types" "^3.7.2" - "@smithy/util-body-length-browser" "^3.0.0" - "@smithy/util-middleware" "^3.0.11" - "@smithy/util-stream" "^3.3.2" - "@smithy/util-utf8" "^3.0.0" + "@smithy/middleware-serde" "^4.2.1" + "@smithy/protocol-http" "^5.3.1" + "@smithy/types" "^4.7.0" + "@smithy/util-base64" "^4.3.0" + "@smithy/util-body-length-browser" "^4.2.0" + "@smithy/util-middleware" "^4.2.1" + "@smithy/util-stream" "^4.5.1" + "@smithy/util-utf8" "^4.2.0" + "@smithy/uuid" "^1.1.0" tslib "^2.6.2" -"@smithy/credential-provider-imds@^3.2.8": - version "3.2.8" - resolved "https://registry.yarnpkg.com/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.8.tgz#27ed2747074c86a7d627a98e56f324a65cba88de" - integrity sha512-ZCY2yD0BY+K9iMXkkbnjo+08T2h8/34oHd0Jmh6BZUSZwaaGlGCyBT/3wnS7u7Xl33/EEfN4B6nQr3Gx5bYxgw== +"@smithy/credential-provider-imds@^4.2.0", "@smithy/credential-provider-imds@^4.2.1": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.1.tgz#9cfe81812bc48b1d7b457efeb9b82f037adfcee8" + integrity sha512-Y7Gq6xZvAUJOf60prfpknyKIJoIU89q/t6Cr4AWLYZBaaIhEdWJRIWvLqiqL5Hb6iK8btorKHI8jT6ZuQB+BVg== dependencies: - "@smithy/node-config-provider" "^3.1.12" - "@smithy/property-provider" "^3.1.11" - "@smithy/types" "^3.7.2" - "@smithy/url-parser" "^3.0.11" + "@smithy/node-config-provider" "^4.3.1" + "@smithy/property-provider" "^4.2.1" + "@smithy/types" "^4.7.0" + "@smithy/url-parser" "^4.2.1" tslib "^2.6.2" -"@smithy/eventstream-codec@^3.1.10": - version "3.1.10" - resolved "https://registry.yarnpkg.com/@smithy/eventstream-codec/-/eventstream-codec-3.1.10.tgz#0c1a3457e7a23b71cd71525ceb668f8569a84dad" - integrity sha512-323B8YckSbUH0nMIpXn7HZsAVKHYHFUODa8gG9cHo0ySvA1fr5iWaNT+iIL0UCqUzG6QPHA3BSsBtRQou4mMqQ== +"@smithy/eventstream-codec@^4.2.1": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@smithy/eventstream-codec/-/eventstream-codec-4.2.1.tgz#97abdd85be768ff95b889de55141c4605db8dfef" + integrity sha512-CAxQlKfWIPLT/hF6GfocaYPbc8ZYC93cqTlu8173olp6K+LGa/32w3E+DG0EmP9TqWSUAOGe6i2SqWBtt+Rnrg== dependencies: "@aws-crypto/crc32" "5.2.0" - "@smithy/types" "^3.7.2" - "@smithy/util-hex-encoding" "^3.0.0" + "@smithy/types" "^4.7.0" + "@smithy/util-hex-encoding" "^4.2.0" tslib "^2.6.2" -"@smithy/eventstream-serde-browser@^3.0.14": - version "3.0.14" - resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-3.0.14.tgz#0c3584c7cde2e210aacdfbbd2b57c1d7e2ca3b95" - integrity sha512-kbrt0vjOIihW3V7Cqj1SXQvAI5BR8SnyQYsandva0AOR307cXAc+IhPngxIPslxTLfxwDpNu0HzCAq6g42kCPg== +"@smithy/eventstream-serde-browser@^4.2.0": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.1.tgz#e52300806619a5ac611188b5a3c2263359790efb" + integrity sha512-8TE6Kwlo+mbvnJdQz1dNGueT+GNtE//Wos8G3Eiz+ebf7Fz1l0ekxnn71XeipIkisgXSKvvaAlDBSVQ2aRYmZQ== dependencies: - "@smithy/eventstream-serde-universal" "^3.0.13" - "@smithy/types" "^3.7.2" + "@smithy/eventstream-serde-universal" "^4.2.1" + "@smithy/types" "^4.7.0" tslib "^2.6.2" -"@smithy/eventstream-serde-config-resolver@^3.0.11": - version "3.0.11" - resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.0.11.tgz#5edceba836debea165ea93145231036f6286d67c" - integrity sha512-P2pnEp4n75O+QHjyO7cbw/vsw5l93K/8EWyjNCAAybYwUmj3M+hjSQZ9P5TVdUgEG08ueMAP5R4FkuSkElZ5tQ== +"@smithy/eventstream-serde-config-resolver@^4.3.0": + version "4.3.1" + resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.1.tgz#2f408779a78716e6a7ecb312d4dd5d86f370e3f2" + integrity sha512-/2auO4BugZ64ynwvN3+IJx2CrlKNHRyH6Jw8idzyUqtgsqqpQbIKN5DTHwuUtYWM/s6SbXfgJSgnEmt0HCq0Qw== dependencies: - "@smithy/types" "^3.7.2" + "@smithy/types" "^4.7.0" tslib "^2.6.2" -"@smithy/eventstream-serde-node@^3.0.13": - version "3.0.13" - resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.13.tgz#5aebd7b553becee277e411a2b69f6af8c9d7b3a6" - integrity sha512-zqy/9iwbj8Wysmvi7Lq7XFLeDgjRpTbCfwBhJa8WbrylTAHiAu6oQTwdY7iu2lxigbc9YYr9vPv5SzYny5tCXQ== +"@smithy/eventstream-serde-node@^4.2.0": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.1.tgz#3e15afc05952e64ddb234aff1dbd6b5e9e84bd36" + integrity sha512-SKCkhJytWD2l/PlftMnHoZ3Sd6CLoIL/9Ws/ErWIFWVmSeZfImBA/wCZ4oPLhyqTsFPkNdFuL/FlrJfIUnZVPg== dependencies: - "@smithy/eventstream-serde-universal" "^3.0.13" - "@smithy/types" "^3.7.2" + "@smithy/eventstream-serde-universal" "^4.2.1" + "@smithy/types" "^4.7.0" tslib "^2.6.2" -"@smithy/eventstream-serde-universal@^3.0.13": - version "3.0.13" - resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.13.tgz#609c922ea14a0a3eed23a28ac110344c935704eb" - integrity sha512-L1Ib66+gg9uTnqp/18Gz4MDpJPKRE44geOjOQ2SVc0eiaO5l255ADziATZgjQjqumC7yPtp1XnjHlF1srcwjKw== +"@smithy/eventstream-serde-universal@^4.2.1": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.1.tgz#6c83efea2cb68b50393a79ef9b39fb261ac6d082" + integrity sha512-q/k55ZXavQVIAkHFaonz5vUkOHe4isy5114fMv+FJ6HXZESaYN6DZfadjbHMQoMVyqBg5togfk+AxD2rbCop/A== dependencies: - "@smithy/eventstream-codec" "^3.1.10" - "@smithy/types" "^3.7.2" + "@smithy/eventstream-codec" "^4.2.1" + "@smithy/types" "^4.7.0" tslib "^2.6.2" -"@smithy/fetch-http-handler@^4.1.2": - version "4.1.2" - resolved "https://registry.yarnpkg.com/@smithy/fetch-http-handler/-/fetch-http-handler-4.1.2.tgz#f034ff16416b37d92908a1381ef5fddbf4ef1879" - integrity sha512-R7rU7Ae3ItU4rC0c5mB2sP5mJNbCfoDc8I5XlYjIZnquyUwec7fEo78F6DA3SmgJgkU1qTMcZJuGblxZsl10ZA== +"@smithy/fetch-http-handler@^5.3.1", "@smithy/fetch-http-handler@^5.3.2": + version "5.3.2" + resolved "https://registry.yarnpkg.com/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.2.tgz#8dfd9917dbff7e9079d8e7b50d5bcdae6855e996" + integrity sha512-3CXDhyjl6nz0na+te37f+aGqmDwJeyeo9GK7ThPStoa/ruZcUm17UPRC4xJvbm8Z4JCvbnh54mRCFtiR/IzXjw== dependencies: - "@smithy/protocol-http" "^4.1.8" - "@smithy/querystring-builder" "^3.0.11" - "@smithy/types" "^3.7.2" - "@smithy/util-base64" "^3.0.0" + "@smithy/protocol-http" "^5.3.1" + "@smithy/querystring-builder" "^4.2.1" + "@smithy/types" "^4.7.0" + "@smithy/util-base64" "^4.3.0" tslib "^2.6.2" -"@smithy/hash-blob-browser@^3.1.10": - version "3.1.10" - resolved "https://registry.yarnpkg.com/@smithy/hash-blob-browser/-/hash-blob-browser-3.1.10.tgz#985e308189c2687a15004152b97506882ffb2b13" - integrity sha512-elwslXOoNunmfS0fh55jHggyhccobFkexLYC1ZeZ1xP2BTSrcIBaHV2b4xUQOdctrSNOpMqOZH1r2XzWTEhyfA== +"@smithy/hash-blob-browser@^4.2.1": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@smithy/hash-blob-browser/-/hash-blob-browser-4.2.2.tgz#85abb0a810a0f70af656d21dde8e184dd2ac4b71" + integrity sha512-I7oRNdOVWV5psHPvlatTLGNXi1rVfLafsK7RY0etWMop2WpqcnYMzmRCfJLzmIQBb5zB3KfmZBITUhxlCn1PCw== dependencies: - "@smithy/chunked-blob-reader" "^4.0.0" - "@smithy/chunked-blob-reader-native" "^3.0.1" - "@smithy/types" "^3.7.2" + "@smithy/chunked-blob-reader" "^5.2.0" + "@smithy/chunked-blob-reader-native" "^4.2.1" + "@smithy/types" "^4.7.0" tslib "^2.6.2" -"@smithy/hash-node@^3.0.11": - version "3.0.11" - resolved "https://registry.yarnpkg.com/@smithy/hash-node/-/hash-node-3.0.11.tgz#99e09ead3fc99c8cd7ca0f254ea0e35714f2a0d3" - integrity sha512-emP23rwYyZhQBvklqTtwetkQlqbNYirDiEEwXl2v0GYWMnCzxst7ZaRAnWuy28njp5kAH54lvkdG37MblZzaHA== +"@smithy/hash-node@^4.2.0": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@smithy/hash-node/-/hash-node-4.2.1.tgz#d8666428535f3587cdf0119496300c628e05d1be" + integrity sha512-eqyR+zua9LI8K0NhYMUEh8HDy7zaT1gRuB3d1kNIKeSG9nc2JxNbKXYNRdmIvAWG3wJyl9uUWPs+H3k8uDes1Q== dependencies: - "@smithy/types" "^3.7.2" - "@smithy/util-buffer-from" "^3.0.0" - "@smithy/util-utf8" "^3.0.0" + "@smithy/types" "^4.7.0" + "@smithy/util-buffer-from" "^4.2.0" + "@smithy/util-utf8" "^4.2.0" tslib "^2.6.2" -"@smithy/hash-stream-node@^3.1.10": - version "3.1.10" - resolved "https://registry.yarnpkg.com/@smithy/hash-stream-node/-/hash-stream-node-3.1.10.tgz#94716b4556f4ccf2807e605f47bb5b018ed7dfb0" - integrity sha512-olomK/jZQ93OMayW1zfTHwcbwBdhcZOHsyWyiZ9h9IXvc1mCD/VuvzbLb3Gy/qNJwI4MANPLctTp2BucV2oU/Q== +"@smithy/hash-stream-node@^4.2.0": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@smithy/hash-stream-node/-/hash-stream-node-4.2.1.tgz#43489664a3b7ea4997f9d3c7be80b93a009adce2" + integrity sha512-Ege/rLl4srtUMSsJJIdWJNcwIXf45Y191IegtBgdX/XP54guFGhKpVRPg7yG0J4pazOGb8GNGl/rbTN7DHbXiw== dependencies: - "@smithy/types" "^3.7.2" - "@smithy/util-utf8" "^3.0.0" + "@smithy/types" "^4.7.0" + "@smithy/util-utf8" "^4.2.0" tslib "^2.6.2" -"@smithy/invalid-dependency@^3.0.11": - version "3.0.11" - resolved "https://registry.yarnpkg.com/@smithy/invalid-dependency/-/invalid-dependency-3.0.11.tgz#8144d7b0af9d34ab5f672e1f674f97f8740bb9ae" - integrity sha512-NuQmVPEJjUX6c+UELyVz8kUx8Q539EDeNwbRyu4IIF8MeV7hUtq1FB3SHVyki2u++5XLMFqngeMKk7ccspnNyQ== +"@smithy/invalid-dependency@^4.2.0": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@smithy/invalid-dependency/-/invalid-dependency-4.2.1.tgz#1a2830c72f9593c38f654b92f55a244b4cc3ab76" + integrity sha512-mGH4fyQwVun9jtAbNQjU5Dt2pItOM1ULQrceaISyyu8pEjreBjyC0T5BN+zU2ltqKF3NefjQ+ApfoAk1w1UplQ== dependencies: - "@smithy/types" "^3.7.2" + "@smithy/types" "^4.7.0" tslib "^2.6.2" "@smithy/is-array-buffer@^2.2.0": @@ -2444,208 +2275,209 @@ dependencies: tslib "^2.6.2" -"@smithy/is-array-buffer@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz#9a95c2d46b8768946a9eec7f935feaddcffa5e7a" - integrity sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ== +"@smithy/is-array-buffer@^4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz#b0f874c43887d3ad44f472a0f3f961bcce0550c2" + integrity sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ== dependencies: tslib "^2.6.2" -"@smithy/md5-js@^3.0.11": - version "3.0.11" - resolved "https://registry.yarnpkg.com/@smithy/md5-js/-/md5-js-3.0.11.tgz#27e4dab616348ff94aed24dc75e4017c582df40f" - integrity sha512-3NM0L3i2Zm4bbgG6Ymi9NBcxXhryi3uE8fIfHJZIOfZVxOkGdjdgjR9A06SFIZCfnEIWKXZdm6Yq5/aPXFFhsQ== +"@smithy/md5-js@^4.2.0": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@smithy/md5-js/-/md5-js-4.2.1.tgz#bdf8b104809c183f965584c4126bd21b66162316" + integrity sha512-6iFIH++OqA8QP7axnP/9NE5DNwq4t05VKx5izFsHQZUQDjfqqmKsLdzEPNtuspV/6AgPe23tYHJU+2XQhjpBxA== dependencies: - "@smithy/types" "^3.7.2" - "@smithy/util-utf8" "^3.0.0" + "@smithy/types" "^4.7.0" + "@smithy/util-utf8" "^4.2.0" tslib "^2.6.2" -"@smithy/middleware-content-length@^3.0.13": - version "3.0.13" - resolved "https://registry.yarnpkg.com/@smithy/middleware-content-length/-/middleware-content-length-3.0.13.tgz#6e08fe52739ac8fb3996088e0f8837e4b2ea187f" - integrity sha512-zfMhzojhFpIX3P5ug7jxTjfUcIPcGjcQYzB9t+rv0g1TX7B0QdwONW+ATouaLoD7h7LOw/ZlXfkq4xJ/g2TrIw== +"@smithy/middleware-content-length@^4.2.0": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@smithy/middleware-content-length/-/middleware-content-length-4.2.1.tgz#2922b6b81e89c7ab1fe944f452e812221e39cc50" + integrity sha512-+V6TdTAcS/dGILfe4hZP5lVnCuUvcX05yj+GihbOpy/ylGzUYhE/oYmv4vU33vMj5rfpdcfuyuESHkJTTRDXGw== dependencies: - "@smithy/protocol-http" "^4.1.8" - "@smithy/types" "^3.7.2" + "@smithy/protocol-http" "^5.3.1" + "@smithy/types" "^4.7.0" tslib "^2.6.2" -"@smithy/middleware-endpoint@^3.2.5": - version "3.2.5" - resolved "https://registry.yarnpkg.com/@smithy/middleware-endpoint/-/middleware-endpoint-3.2.5.tgz#bdcfdf1f342cf933b0b8a709996f9a8fbb8148f4" - integrity sha512-VhJNs/s/lyx4weiZdXSloBgoLoS8osV0dKIain8nGmx7of3QFKu5BSdEuk1z/U8x9iwes1i+XCiNusEvuK1ijg== +"@smithy/middleware-endpoint@^4.3.1", "@smithy/middleware-endpoint@^4.3.2": + version "4.3.2" + resolved "https://registry.yarnpkg.com/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.2.tgz#95e44d3188a31db7f53578c1477395b682d27364" + integrity sha512-3UP7E5SD0rF6cQEWVMxfbMvpC0fv9fTbusMQfKAXlff5g7L2tn2kspiiGX+nqyK78FV2kP/O2WS7rbIvhfw6/Q== dependencies: - "@smithy/core" "^2.5.5" - "@smithy/middleware-serde" "^3.0.11" - "@smithy/node-config-provider" "^3.1.12" - "@smithy/shared-ini-file-loader" "^3.1.12" - "@smithy/types" "^3.7.2" - "@smithy/url-parser" "^3.0.11" - "@smithy/util-middleware" "^3.0.11" + "@smithy/core" "^3.16.0" + "@smithy/middleware-serde" "^4.2.1" + "@smithy/node-config-provider" "^4.3.1" + "@smithy/shared-ini-file-loader" "^4.3.1" + "@smithy/types" "^4.7.0" + "@smithy/url-parser" "^4.2.1" + "@smithy/util-middleware" "^4.2.1" tslib "^2.6.2" -"@smithy/middleware-retry@^3.0.30": - version "3.0.30" - resolved "https://registry.yarnpkg.com/@smithy/middleware-retry/-/middleware-retry-3.0.30.tgz#2580322d0d28ad782b5b8c07c150b14efdc3b2f9" - integrity sha512-6323RL2BvAR3VQpTjHpa52kH/iSHyxd/G9ohb2MkBk2Ucu+oMtRXT8yi7KTSIS9nb58aupG6nO0OlXnQOAcvmQ== +"@smithy/middleware-retry@^4.4.1": + version "4.4.2" + resolved "https://registry.yarnpkg.com/@smithy/middleware-retry/-/middleware-retry-4.4.2.tgz#5adf9f577e9dd881f8c2fd0457e9fa77ba5d41d4" + integrity sha512-cuPmDJi7AE7PkdfeqJaHKBR33mXCl1MPxrboQDR/zZUo9u947m0gnYRd25NTSRER5LZpNDCvVTSedeAC9dHckA== dependencies: - "@smithy/node-config-provider" "^3.1.12" - "@smithy/protocol-http" "^4.1.8" - "@smithy/service-error-classification" "^3.0.11" - "@smithy/smithy-client" "^3.5.0" - "@smithy/types" "^3.7.2" - "@smithy/util-middleware" "^3.0.11" - "@smithy/util-retry" "^3.0.11" - tslib "^2.6.2" - uuid "^9.0.1" - -"@smithy/middleware-serde@^3.0.11": - version "3.0.11" - resolved "https://registry.yarnpkg.com/@smithy/middleware-serde/-/middleware-serde-3.0.11.tgz#c7d54e0add4f83e05c6878a011fc664e21022f12" - integrity sha512-KzPAeySp/fOoQA82TpnwItvX8BBURecpx6ZMu75EZDkAcnPtO6vf7q4aH5QHs/F1s3/snQaSFbbUMcFFZ086Mw== - dependencies: - "@smithy/types" "^3.7.2" + "@smithy/node-config-provider" "^4.3.1" + "@smithy/protocol-http" "^5.3.1" + "@smithy/service-error-classification" "^4.2.1" + "@smithy/smithy-client" "^4.8.0" + "@smithy/types" "^4.7.0" + "@smithy/util-middleware" "^4.2.1" + "@smithy/util-retry" "^4.2.1" + "@smithy/uuid" "^1.1.0" tslib "^2.6.2" -"@smithy/middleware-stack@^3.0.11": - version "3.0.11" - resolved "https://registry.yarnpkg.com/@smithy/middleware-stack/-/middleware-stack-3.0.11.tgz#453af2096924e4064d9da4e053cfdf65d9a36acc" - integrity sha512-1HGo9a6/ikgOMrTrWL/WiN9N8GSVYpuRQO5kjstAq4CvV59bjqnh7TbdXGQ4vxLD3xlSjfBjq5t1SOELePsLnA== +"@smithy/middleware-serde@^4.2.0", "@smithy/middleware-serde@^4.2.1": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@smithy/middleware-serde/-/middleware-serde-4.2.1.tgz#674736c4f7514878ad708ae663f390f6cddef03e" + integrity sha512-0J1EDeGGBNz0h0R/UGKudF7gBMS+UMJEWuNPY1hDV/RTyyKgBfsKH87nKCeCSB81EgjnBDFsnfXD2ZMRCfIPWA== dependencies: - "@smithy/types" "^3.7.2" + "@smithy/protocol-http" "^5.3.1" + "@smithy/types" "^4.7.0" tslib "^2.6.2" -"@smithy/node-config-provider@^3.1.12": - version "3.1.12" - resolved "https://registry.yarnpkg.com/@smithy/node-config-provider/-/node-config-provider-3.1.12.tgz#1b1d674fc83f943dc7b3017e37f16f374e878a6c" - integrity sha512-O9LVEu5J/u/FuNlZs+L7Ikn3lz7VB9hb0GtPT9MQeiBmtK8RSY3ULmsZgXhe6VAlgTw0YO+paQx4p8xdbs43vQ== +"@smithy/middleware-stack@^4.2.0", "@smithy/middleware-stack@^4.2.1": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@smithy/middleware-stack/-/middleware-stack-4.2.1.tgz#d620fa159c6d444a3b03ecd8082923663c538bc7" + integrity sha512-gWKgBqYYrcdtkEMzN8hEtypab7zgU4VVZHSwURAR5YGrvGJxbBh5mC9RPmVWS7TZxr/vB4yMKfxEQTrYRKRQ3Q== dependencies: - "@smithy/property-provider" "^3.1.11" - "@smithy/shared-ini-file-loader" "^3.1.12" - "@smithy/types" "^3.7.2" + "@smithy/types" "^4.7.0" tslib "^2.6.2" -"@smithy/node-http-handler@^3.3.2": - version "3.3.2" - resolved "https://registry.yarnpkg.com/@smithy/node-http-handler/-/node-http-handler-3.3.2.tgz#b34685863b74dabdaf7860aa81b42d0d5437c7e0" - integrity sha512-t4ng1DAd527vlxvOfKFYEe6/QFBcsj7WpNlWTyjorwXXcKw3XlltBGbyHfSJ24QT84nF+agDha9tNYpzmSRZPA== +"@smithy/node-config-provider@^4.3.0", "@smithy/node-config-provider@^4.3.1": + version "4.3.1" + resolved "https://registry.yarnpkg.com/@smithy/node-config-provider/-/node-config-provider-4.3.1.tgz#95e576a5608c3e6e512f70bf729358d55f566db4" + integrity sha512-Ap8Wd95HCrWRktMAZNc0AVzdPdUSPHsG59+DMe+4aH74FLDnVTo/7XDcRhSkSZCHeDjaDtzAh5OvnHOE0VHwUg== dependencies: - "@smithy/abort-controller" "^3.1.9" - "@smithy/protocol-http" "^4.1.8" - "@smithy/querystring-builder" "^3.0.11" - "@smithy/types" "^3.7.2" + "@smithy/property-provider" "^4.2.1" + "@smithy/shared-ini-file-loader" "^4.3.1" + "@smithy/types" "^4.7.0" tslib "^2.6.2" -"@smithy/property-provider@^3.1.11": - version "3.1.11" - resolved "https://registry.yarnpkg.com/@smithy/property-provider/-/property-provider-3.1.11.tgz#161cf1c2a2ada361e417382c57f5ba6fbca8acad" - integrity sha512-I/+TMc4XTQ3QAjXfOcUWbSS073oOEAxgx4aZy8jHaf8JQnRkq2SZWw8+PfDtBvLUjcGMdxl+YwtzWe6i5uhL/A== +"@smithy/node-http-handler@^4.3.0", "@smithy/node-http-handler@^4.4.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@smithy/node-http-handler/-/node-http-handler-4.4.0.tgz#e1f6ae4a90cd7257699263bf8e06e653ff0e5f83" + integrity sha512-E00fuesARqnmdc1vR4qurQjQH+QWcsKjmM6kYoJBWjxgqNfp1WHc1SwfC18EdVaYamgctxyXV6kWhHmanhYgCg== dependencies: - "@smithy/types" "^3.7.2" + "@smithy/abort-controller" "^4.2.1" + "@smithy/protocol-http" "^5.3.1" + "@smithy/querystring-builder" "^4.2.1" + "@smithy/types" "^4.7.0" tslib "^2.6.2" -"@smithy/protocol-http@^4.1.8": - version "4.1.8" - resolved "https://registry.yarnpkg.com/@smithy/protocol-http/-/protocol-http-4.1.8.tgz#0461758671335f65e8ff3fc0885ab7ed253819c9" - integrity sha512-hmgIAVyxw1LySOwkgMIUN0kjN8TG9Nc85LJeEmEE/cNEe2rkHDUWhnJf2gxcSRFLWsyqWsrZGw40ROjUogg+Iw== +"@smithy/property-provider@^4.2.0", "@smithy/property-provider@^4.2.1": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@smithy/property-provider/-/property-provider-4.2.1.tgz#c175cae05d229ed13371b97795a75f5b36aad218" + integrity sha512-2zthf6j/u4XV3nRvulJgQsZdAs9xNf7dJPE5+Wvrx4yAsNrmtchadydASqRLXEw67ovl8c+HFa58QEXD/jUMSg== dependencies: - "@smithy/types" "^3.7.2" + "@smithy/types" "^4.7.0" tslib "^2.6.2" -"@smithy/querystring-builder@^3.0.11": - version "3.0.11" - resolved "https://registry.yarnpkg.com/@smithy/querystring-builder/-/querystring-builder-3.0.11.tgz#2ed04adbe725671824c5613d0d6f9376d791a909" - integrity sha512-u+5HV/9uJaeLj5XTb6+IEF/dokWWkEqJ0XiaRRogyREmKGUgZnNecLucADLdauWFKUNbQfulHFEZEdjwEBjXRg== +"@smithy/protocol-http@^5.3.0", "@smithy/protocol-http@^5.3.1": + version "5.3.1" + resolved "https://registry.yarnpkg.com/@smithy/protocol-http/-/protocol-http-5.3.1.tgz#add01f73290f1e8fd49d7102b63e3fe53a5e6e18" + integrity sha512-DqbfSgeZC0qo3/3fLgr5UEdOE7/o/VlVOt6LtpShwVcw3PIoqQMRCUTzMpJ0keAVb86Cl1w5YtW7uDUzeNMMLA== dependencies: - "@smithy/types" "^3.7.2" - "@smithy/util-uri-escape" "^3.0.0" + "@smithy/types" "^4.7.0" tslib "^2.6.2" -"@smithy/querystring-parser@^3.0.11": - version "3.0.11" - resolved "https://registry.yarnpkg.com/@smithy/querystring-parser/-/querystring-parser-3.0.11.tgz#9d3177ea19ce8462f18d9712b395239e1ca1f969" - integrity sha512-Je3kFvCsFMnso1ilPwA7GtlbPaTixa3WwC+K21kmMZHsBEOZYQaqxcMqeFFoU7/slFjKDIpiiPydvdJm8Q/MCw== +"@smithy/querystring-builder@^4.2.1": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@smithy/querystring-builder/-/querystring-builder-4.2.1.tgz#1ef454874cb9da2295369ded8433d1de66498104" + integrity sha512-2Qf5x7Afn6ofV3XLYL9+oaOwWK2FUC/LLTarex0SaXEKctVdzCdOOzEfaAZJSwSSiYqFWF6e2r0m7PFDzA44fA== dependencies: - "@smithy/types" "^3.7.2" + "@smithy/types" "^4.7.0" + "@smithy/util-uri-escape" "^4.2.0" tslib "^2.6.2" -"@smithy/service-error-classification@^3.0.11": - version "3.0.11" - resolved "https://registry.yarnpkg.com/@smithy/service-error-classification/-/service-error-classification-3.0.11.tgz#d3d7fc0aacd2e60d022507367e55c7939e5bcb8a" - integrity sha512-QnYDPkyewrJzCyaeI2Rmp7pDwbUETe+hU8ADkXmgNusO1bgHBH7ovXJiYmba8t0fNfJx75fE8dlM6SEmZxheog== +"@smithy/querystring-parser@^4.2.1": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@smithy/querystring-parser/-/querystring-parser-4.2.1.tgz#996358b90c35ecf357d5b32048414e9c17551f6d" + integrity sha512-y1DmifEgOF5J1MmrLP2arzI17tEaVqD+NUnfE+sVcpPcEHmAUL0TF9gQzAi5s6GGHUyDurO+zHvZQOeo7LuJnQ== dependencies: - "@smithy/types" "^3.7.2" - -"@smithy/shared-ini-file-loader@^3.1.12": - version "3.1.12" - resolved "https://registry.yarnpkg.com/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.12.tgz#d98b1b663eb18935ce2cbc79024631d34f54042a" - integrity sha512-1xKSGI+U9KKdbG2qDvIR9dGrw3CNx+baqJfyr0igKEpjbHL5stsqAesYBzHChYHlelWtb87VnLWlhvfCz13H8Q== - dependencies: - "@smithy/types" "^3.7.2" + "@smithy/types" "^4.7.0" tslib "^2.6.2" -"@smithy/signature-v4@^4.2.4": - version "4.2.4" - resolved "https://registry.yarnpkg.com/@smithy/signature-v4/-/signature-v4-4.2.4.tgz#3501d3d09fd82768867bfc00a7be4bad62f62f4d" - integrity sha512-5JWeMQYg81TgU4cG+OexAWdvDTs5JDdbEZx+Qr1iPbvo91QFGzjy0IkXAKaXUHqmKUJgSHK0ZxnCkgZpzkeNTA== +"@smithy/service-error-classification@^4.2.1": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@smithy/service-error-classification/-/service-error-classification-4.2.1.tgz#efc6772fd47152ca64b6c7b95e672bc1e21b9292" + integrity sha512-NEcg3bGL9MddDd0GtH1+6bLg+e9SpbNEAVV8vEM4uWgqixECItz6wf0sYcq+N0lQjeRljdwaG3wxd2YgJ7JfbQ== dependencies: - "@smithy/is-array-buffer" "^3.0.0" - "@smithy/protocol-http" "^4.1.8" - "@smithy/types" "^3.7.2" - "@smithy/util-hex-encoding" "^3.0.0" - "@smithy/util-middleware" "^3.0.11" - "@smithy/util-uri-escape" "^3.0.0" - "@smithy/util-utf8" "^3.0.0" + "@smithy/types" "^4.7.0" + +"@smithy/shared-ini-file-loader@^4.3.0", "@smithy/shared-ini-file-loader@^4.3.1": + version "4.3.1" + resolved "https://registry.yarnpkg.com/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.3.1.tgz#efc591bf620599a8473c9adfefcfb9775a21b0d3" + integrity sha512-V4XVUUCsuVeSNkjeXLR4Y5doyNkTx29Cp8NfKoklgpSsWawyxmJbVvJ1kFHRulOmdBlLuHoqDrAirN8ZoduUCA== + dependencies: + "@smithy/types" "^4.7.0" tslib "^2.6.2" -"@smithy/smithy-client@^3.5.0": - version "3.5.0" - resolved "https://registry.yarnpkg.com/@smithy/smithy-client/-/smithy-client-3.5.0.tgz#65cff262801b009998c1196764ee69929ee06f8a" - integrity sha512-Y8FeOa7gbDfCWf7njrkoRATPa5eNLUEjlJS5z5rXatYuGkCb80LbHcu8AQR8qgAZZaNHCLyo2N+pxPsV7l+ivg== +"@smithy/signature-v4@^5.3.0": + version "5.3.1" + resolved "https://registry.yarnpkg.com/@smithy/signature-v4/-/signature-v4-5.3.1.tgz#c3d711c29d37f3db4daf51750eea75204c4f51d4" + integrity sha512-7jimpk6X2jzV3UmesOFFV675N/4D8QqNg6NdZFNa/RmWAco+jyX/TbX2mHFImNm+DoafpwEfcDNsPxDSYF0Pxw== dependencies: - "@smithy/core" "^2.5.5" - "@smithy/middleware-endpoint" "^3.2.5" - "@smithy/middleware-stack" "^3.0.11" - "@smithy/protocol-http" "^4.1.8" - "@smithy/types" "^3.7.2" - "@smithy/util-stream" "^3.3.2" + "@smithy/is-array-buffer" "^4.2.0" + "@smithy/protocol-http" "^5.3.1" + "@smithy/types" "^4.7.0" + "@smithy/util-hex-encoding" "^4.2.0" + "@smithy/util-middleware" "^4.2.1" + "@smithy/util-uri-escape" "^4.2.0" + "@smithy/util-utf8" "^4.2.0" tslib "^2.6.2" -"@smithy/types@^3.7.2": - version "3.7.2" - resolved "https://registry.yarnpkg.com/@smithy/types/-/types-3.7.2.tgz#05cb14840ada6f966de1bf9a9c7dd86027343e10" - integrity sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg== +"@smithy/smithy-client@^4.7.1", "@smithy/smithy-client@^4.8.0": + version "4.8.0" + resolved "https://registry.yarnpkg.com/@smithy/smithy-client/-/smithy-client-4.8.0.tgz#a602a8513977b1d411e3bf5629a93f0f41a304a8" + integrity sha512-gbpNLnuDnguDcXQvbeIAd05F9EDK4HasFtiRzJoM5NbsvXGnW2dGd4mHaShR+ZNveoP9KaWlwF8Hj4ZtipaM3Q== + dependencies: + "@smithy/core" "^3.16.0" + "@smithy/middleware-endpoint" "^4.3.2" + "@smithy/middleware-stack" "^4.2.1" + "@smithy/protocol-http" "^5.3.1" + "@smithy/types" "^4.7.0" + "@smithy/util-stream" "^4.5.1" + tslib "^2.6.2" + +"@smithy/types@^4.6.0", "@smithy/types@^4.7.0": + version "4.7.0" + resolved "https://registry.yarnpkg.com/@smithy/types/-/types-4.7.0.tgz#42d707276d9184aef705f04e04615cd1979d044f" + integrity sha512-KM8Or+jCDCrUI3wYYhj7ehrC7aATB1NdJ1aFEE/YLKNLVH257k9RNeOqKdg0JOxjyEpVD7KKsmmob9mRy1Ho2g== dependencies: tslib "^2.6.2" -"@smithy/url-parser@^3.0.11": - version "3.0.11" - resolved "https://registry.yarnpkg.com/@smithy/url-parser/-/url-parser-3.0.11.tgz#e5f5ffabfb6230159167cf4cc970705fca6b8b2d" - integrity sha512-TmlqXkSk8ZPhfc+SQutjmFr5FjC0av3GZP4B/10caK1SbRwe/v+Wzu/R6xEKxoNqL+8nY18s1byiy6HqPG37Aw== +"@smithy/url-parser@^4.2.0", "@smithy/url-parser@^4.2.1": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@smithy/url-parser/-/url-parser-4.2.1.tgz#0c00510684f832cd2fc8f188766267db04e6115d" + integrity sha512-dHm6hDcl79Ededl0oKgpSq3mM5b7Xdw+jic8bq1G7Z2spVpm7HpHJuLCV9PUJLjMbDbZfRUf5GEOnnOIvgfYgQ== dependencies: - "@smithy/querystring-parser" "^3.0.11" - "@smithy/types" "^3.7.2" + "@smithy/querystring-parser" "^4.2.1" + "@smithy/types" "^4.7.0" tslib "^2.6.2" -"@smithy/util-base64@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@smithy/util-base64/-/util-base64-3.0.0.tgz#f7a9a82adf34e27a72d0719395713edf0e493017" - integrity sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ== +"@smithy/util-base64@^4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@smithy/util-base64/-/util-base64-4.3.0.tgz#5e287b528793aa7363877c1a02cd880d2e76241d" + integrity sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ== dependencies: - "@smithy/util-buffer-from" "^3.0.0" - "@smithy/util-utf8" "^3.0.0" + "@smithy/util-buffer-from" "^4.2.0" + "@smithy/util-utf8" "^4.2.0" tslib "^2.6.2" -"@smithy/util-body-length-browser@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@smithy/util-body-length-browser/-/util-body-length-browser-3.0.0.tgz#86ec2f6256310b4845a2f064e2f571c1ca164ded" - integrity sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ== +"@smithy/util-body-length-browser@^4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz#04e9fc51ee7a3e7f648a4b4bcdf96c350cfa4d61" + integrity sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg== dependencies: tslib "^2.6.2" -"@smithy/util-body-length-node@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@smithy/util-body-length-node/-/util-body-length-node-3.0.0.tgz#99a291bae40d8932166907fe981d6a1f54298a6d" - integrity sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA== +"@smithy/util-body-length-node@^4.2.1": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz#79c8a5d18e010cce6c42d5cbaf6c1958523e6fec" + integrity sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA== dependencies: tslib "^2.6.2" @@ -2657,96 +2489,95 @@ "@smithy/is-array-buffer" "^2.2.0" tslib "^2.6.2" -"@smithy/util-buffer-from@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz#559fc1c86138a89b2edaefc1e6677780c24594e3" - integrity sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA== +"@smithy/util-buffer-from@^4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz#7abd12c4991b546e7cee24d1e8b4bfaa35c68a9d" + integrity sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew== dependencies: - "@smithy/is-array-buffer" "^3.0.0" + "@smithy/is-array-buffer" "^4.2.0" tslib "^2.6.2" -"@smithy/util-config-provider@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@smithy/util-config-provider/-/util-config-provider-3.0.0.tgz#62c6b73b22a430e84888a8f8da4b6029dd5b8efe" - integrity sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ== +"@smithy/util-config-provider@^4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz#2e4722937f8feda4dcb09672c59925a4e6286cfc" + integrity sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q== dependencies: tslib "^2.6.2" -"@smithy/util-defaults-mode-browser@^3.0.30": - version "3.0.30" - resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.30.tgz#6c0d95af3f15bef8f1fe3f6217cc4f5ba8df5554" - integrity sha512-nLuGmgfcr0gzm64pqF2UT4SGWVG8UGviAdayDlVzJPNa6Z4lqvpDzdRXmLxtOdEjVlTOEdpZ9dd3ZMMu488mzg== +"@smithy/util-defaults-mode-browser@^4.3.0": + version "4.3.1" + resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.1.tgz#62c406c8e4ce731ac223b50cc7bbee674d38cfd0" + integrity sha512-B3kaaqtc11rIc7SN3g6TYGdUrQfCkoHvpqbhd9kdfRUQZG7M7dcc0oLcCjMuBhCSUdtorkK7OA5uGq9BB+isaA== dependencies: - "@smithy/property-provider" "^3.1.11" - "@smithy/smithy-client" "^3.5.0" - "@smithy/types" "^3.7.2" - bowser "^2.11.0" + "@smithy/property-provider" "^4.2.1" + "@smithy/smithy-client" "^4.8.0" + "@smithy/types" "^4.7.0" tslib "^2.6.2" -"@smithy/util-defaults-mode-node@^3.0.30": - version "3.0.30" - resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.30.tgz#33cdb02f90944b9ff221e2f8e0904a63ac1e335f" - integrity sha512-OD63eWoH68vp75mYcfYyuVH+p7Li/mY4sYOROnauDrtObo1cS4uWfsy/zhOTW8F8ZPxQC1ZXZKVxoxvMGUv2Ow== +"@smithy/util-defaults-mode-node@^4.2.1": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.2.tgz#b561cd3192e6cda6a0a3ee3c1b050248eb8ffaed" + integrity sha512-cneOHBPi/DGbjz65oV8wID+uUbtzrFAQ8w3a7uS3C1jjrInSrinAitup8SouDpmi8jr5GVOAck1/hsR3n/WvaQ== dependencies: - "@smithy/config-resolver" "^3.0.13" - "@smithy/credential-provider-imds" "^3.2.8" - "@smithy/node-config-provider" "^3.1.12" - "@smithy/property-provider" "^3.1.11" - "@smithy/smithy-client" "^3.5.0" - "@smithy/types" "^3.7.2" + "@smithy/config-resolver" "^4.3.1" + "@smithy/credential-provider-imds" "^4.2.1" + "@smithy/node-config-provider" "^4.3.1" + "@smithy/property-provider" "^4.2.1" + "@smithy/smithy-client" "^4.8.0" + "@smithy/types" "^4.7.0" tslib "^2.6.2" -"@smithy/util-endpoints@^2.1.7": - version "2.1.7" - resolved "https://registry.yarnpkg.com/@smithy/util-endpoints/-/util-endpoints-2.1.7.tgz#a088ebfab946a7219dd4763bfced82709894b82d" - integrity sha512-tSfcqKcN/Oo2STEYCABVuKgJ76nyyr6skGl9t15hs+YaiU06sgMkN7QYjo0BbVw+KT26zok3IzbdSOksQ4YzVw== +"@smithy/util-endpoints@^3.2.0": + version "3.2.1" + resolved "https://registry.yarnpkg.com/@smithy/util-endpoints/-/util-endpoints-3.2.1.tgz#da67eb006997c4806ceafd30237c92b48e87a624" + integrity sha512-lJudabG/ll+BD22i8IgxZgxS+1hEdUfFqtC1tNubC9vlGwInUktcXodTe5CvM+xDiqGZfqYLY7mKFdabCIrkYw== dependencies: - "@smithy/node-config-provider" "^3.1.12" - "@smithy/types" "^3.7.2" + "@smithy/node-config-provider" "^4.3.1" + "@smithy/types" "^4.7.0" tslib "^2.6.2" -"@smithy/util-hex-encoding@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz#32938b33d5bf2a15796cd3f178a55b4155c535e6" - integrity sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ== +"@smithy/util-hex-encoding@^4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz#1c22ea3d1e2c3a81ff81c0a4f9c056a175068a7b" + integrity sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw== dependencies: tslib "^2.6.2" -"@smithy/util-middleware@^3.0.11": - version "3.0.11" - resolved "https://registry.yarnpkg.com/@smithy/util-middleware/-/util-middleware-3.0.11.tgz#2ab5c17266b42c225e62befcffb048afa682b5bf" - integrity sha512-dWpyc1e1R6VoXrwLoLDd57U1z6CwNSdkM69Ie4+6uYh2GC7Vg51Qtan7ITzczuVpqezdDTKJGJB95fFvvjU/ow== +"@smithy/util-middleware@^4.2.0", "@smithy/util-middleware@^4.2.1": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@smithy/util-middleware/-/util-middleware-4.2.1.tgz#126aaeb6b434748b81058b0ea6c1a7c1656d7040" + integrity sha512-4rf5Ma0e0uuKmtzMihsvs3jnb9iGMRDWrUe6mfdZBWm52PW1xVHdEeP4+swhheF+YAXhVH/O+taKJuqOrVsG3w== dependencies: - "@smithy/types" "^3.7.2" + "@smithy/types" "^4.7.0" tslib "^2.6.2" -"@smithy/util-retry@^3.0.11": - version "3.0.11" - resolved "https://registry.yarnpkg.com/@smithy/util-retry/-/util-retry-3.0.11.tgz#d267e5ccb290165cee69732547fea17b695a7425" - integrity sha512-hJUC6W7A3DQgaee3Hp9ZFcOxVDZzmBIRBPlUAk8/fSOEl7pE/aX7Dci0JycNOnm9Mfr0KV2XjIlUOcGWXQUdVQ== +"@smithy/util-retry@^4.2.0", "@smithy/util-retry@^4.2.1": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@smithy/util-retry/-/util-retry-4.2.1.tgz#8336368586a458cdce86fc92d6fb11fd1db41521" + integrity sha512-0DQqQtZ9brT/QCMts9ssPnsU6CmQAgzkAvTIGcTHoMbntQa7v5VPxxpiyyiTK/BIl8y0vCZSXcOS+kOMXAYRpg== dependencies: - "@smithy/service-error-classification" "^3.0.11" - "@smithy/types" "^3.7.2" + "@smithy/service-error-classification" "^4.2.1" + "@smithy/types" "^4.7.0" tslib "^2.6.2" -"@smithy/util-stream@^3.3.2": - version "3.3.2" - resolved "https://registry.yarnpkg.com/@smithy/util-stream/-/util-stream-3.3.2.tgz#daeea26397e8541cf2499ce65bf0b8d528cba421" - integrity sha512-sInAqdiVeisUGYAv/FrXpmJ0b4WTFmciTRqzhb7wVuem9BHvhIG7tpiYHLDWrl2stOokNZpTTGqz3mzB2qFwXg== +"@smithy/util-stream@^4.5.0", "@smithy/util-stream@^4.5.1": + version "4.5.1" + resolved "https://registry.yarnpkg.com/@smithy/util-stream/-/util-stream-4.5.1.tgz#07b330af9263762a42ea0b40578c67716b080b84" + integrity sha512-kVnOiYDDb84ZUGwpQBiVQROWR7epNXikxMGw971Mww3+eufKl2NHYyao2Gg4Wd3iG+D9hF/d9VrmMBxBcVprXw== dependencies: - "@smithy/fetch-http-handler" "^4.1.2" - "@smithy/node-http-handler" "^3.3.2" - "@smithy/types" "^3.7.2" - "@smithy/util-base64" "^3.0.0" - "@smithy/util-buffer-from" "^3.0.0" - "@smithy/util-hex-encoding" "^3.0.0" - "@smithy/util-utf8" "^3.0.0" + "@smithy/fetch-http-handler" "^5.3.2" + "@smithy/node-http-handler" "^4.4.0" + "@smithy/types" "^4.7.0" + "@smithy/util-base64" "^4.3.0" + "@smithy/util-buffer-from" "^4.2.0" + "@smithy/util-hex-encoding" "^4.2.0" + "@smithy/util-utf8" "^4.2.0" tslib "^2.6.2" -"@smithy/util-uri-escape@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz#e43358a78bf45d50bb736770077f0f09195b6f54" - integrity sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg== +"@smithy/util-uri-escape@^4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz#096a4cec537d108ac24a68a9c60bee73fc7e3a9e" + integrity sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA== dependencies: tslib "^2.6.2" @@ -2758,23 +2589,35 @@ "@smithy/util-buffer-from" "^2.2.0" tslib "^2.6.2" -"@smithy/util-utf8@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@smithy/util-utf8/-/util-utf8-3.0.0.tgz#1a6a823d47cbec1fd6933e5fc87df975286d9d6a" - integrity sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA== +"@smithy/util-utf8@^4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@smithy/util-utf8/-/util-utf8-4.2.0.tgz#8b19d1514f621c44a3a68151f3d43e51087fed9d" + integrity sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw== dependencies: - "@smithy/util-buffer-from" "^3.0.0" + "@smithy/util-buffer-from" "^4.2.0" tslib "^2.6.2" -"@smithy/util-waiter@^3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@smithy/util-waiter/-/util-waiter-3.2.0.tgz#1e52f870e77d2e5572025f7606053e6ff00df93d" - integrity sha512-PpjSboaDUE6yl+1qlg3Si57++e84oXdWGbuFUSAciXsVfEZJJJupR2Nb0QuXHiunt2vGR+1PTizOMvnUPaG2Qg== +"@smithy/util-waiter@^4.2.0": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@smithy/util-waiter/-/util-waiter-4.2.1.tgz#c849ef1096863314d762944ead830e20db56783c" + integrity sha512-c/tJR24Zf7TZFiq/cmVY9WH9j6j3WBDVvhfgCE9mtSb1u93A1P+qf4c7r6g8hcJOKm25Jp12uqojnB0T1//OmA== dependencies: - "@smithy/abort-controller" "^3.1.9" - "@smithy/types" "^3.7.2" + "@smithy/abort-controller" "^4.2.1" + "@smithy/types" "^4.7.0" tslib "^2.6.2" +"@smithy/uuid@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@smithy/uuid/-/uuid-1.1.0.tgz#9fd09d3f91375eab94f478858123387df1cda987" + integrity sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw== + dependencies: + tslib "^2.6.2" + +"@standard-schema/spec@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@standard-schema/spec/-/spec-1.0.0.tgz#f193b73dc316c4170f2e82a881da0f550d551b9c" + integrity sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA== + "@standard-schema/utils@^0.3.0": version "0.3.0" resolved "https://registry.yarnpkg.com/@standard-schema/utils/-/utils-0.3.0.tgz#3d5e608f16c2390c10528e98e59aef6bf73cae7b" @@ -2782,47 +2625,47 @@ "@svgr/babel-plugin-add-jsx-attribute@8.0.0": version "8.0.0" - resolved "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz#4001f5d5dd87fa13303e36ee106e3ff3a7eb8b22" integrity sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g== "@svgr/babel-plugin-remove-jsx-attribute@8.0.0": version "8.0.0" - resolved "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz#69177f7937233caca3a1afb051906698f2f59186" integrity sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA== "@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0": version "8.0.0" - resolved "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz#c2c48104cfd7dcd557f373b70a56e9e3bdae1d44" integrity sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA== "@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0": version "8.0.0" - resolved "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz#8fbb6b2e91fa26ac5d4aa25c6b6e4f20f9c0ae27" integrity sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ== "@svgr/babel-plugin-svg-dynamic-title@8.0.0": version "8.0.0" - resolved "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz#1d5ba1d281363fc0f2f29a60d6d936f9bbc657b0" integrity sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og== "@svgr/babel-plugin-svg-em-dimensions@8.0.0": version "8.0.0" - resolved "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz#35e08df300ea8b1d41cb8f62309c241b0369e501" integrity sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g== "@svgr/babel-plugin-transform-react-native-svg@8.1.0": version "8.1.0" - resolved "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz#90a8b63998b688b284f255c6a5248abd5b28d754" integrity sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q== "@svgr/babel-plugin-transform-svg-component@8.0.0": version "8.0.0" - resolved "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz#013b4bfca88779711f0ed2739f3f7efcefcf4f7e" integrity sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw== "@svgr/babel-preset@8.1.0": version "8.1.0" - resolved "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz" + resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-8.1.0.tgz#0e87119aecdf1c424840b9d4565b7137cabf9ece" integrity sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug== dependencies: "@svgr/babel-plugin-add-jsx-attribute" "8.0.0" @@ -2836,7 +2679,7 @@ "@svgr/core@^8.1.0": version "8.1.0" - resolved "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz" + resolved "https://registry.yarnpkg.com/@svgr/core/-/core-8.1.0.tgz#41146f9b40b1a10beaf5cc4f361a16a3c1885e88" integrity sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA== dependencies: "@babel/core" "^7.21.3" @@ -2847,7 +2690,7 @@ "@svgr/hast-util-to-babel-ast@8.0.0": version "8.0.0" - resolved "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz" + resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz#6952fd9ce0f470e1aded293b792a2705faf4ffd4" integrity sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q== dependencies: "@babel/types" "^7.21.3" @@ -2855,7 +2698,7 @@ "@svgr/plugin-jsx@^8.1.0": version "8.1.0" - resolved "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz" + resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz#96969f04a24b58b174ee4cd974c60475acbd6928" integrity sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA== dependencies: "@babel/core" "^7.21.3" @@ -2863,90 +2706,90 @@ "@svgr/hast-util-to-babel-ast" "8.0.0" svg-parser "^2.0.4" -"@swc/core-darwin-arm64@1.5.7": - version "1.5.7" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.5.7.tgz#2b5cdbd34e4162e50de6147dd1a5cb12d23b08e8" - integrity sha512-bZLVHPTpH3h6yhwVl395k0Mtx8v6CGhq5r4KQdAoPbADU974Mauz1b6ViHAJ74O0IVE5vyy7tD3OpkQxL/vMDQ== +"@swc/core-darwin-arm64@1.13.5": + version "1.13.5" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.13.5.tgz#7638c073946f9297753ed9a2eb198d07b2336a24" + integrity sha512-lKNv7SujeXvKn16gvQqUQI5DdyY8v7xcoO3k06/FJbHJS90zEwZdQiMNRiqpYw/orU543tPaWgz7cIYWhbopiQ== -"@swc/core-darwin-x64@1.5.7": - version "1.5.7" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.5.7.tgz#6aa7e3c01ab8e5e41597f8a24ff24c4e50936a46" - integrity sha512-RpUyu2GsviwTc2qVajPL0l8nf2vKj5wzO3WkLSHAHEJbiUZk83NJrZd1RVbEknIMO7+Uyjh54hEh8R26jSByaw== +"@swc/core-darwin-x64@1.13.5": + version "1.13.5" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.13.5.tgz#18061167378f0fb285e17818494bc6c89dd07551" + integrity sha512-ILd38Fg/w23vHb0yVjlWvQBoE37ZJTdlLHa8LRCFDdX4WKfnVBiblsCU9ar4QTMNdeTBEX9iUF4IrbNWhaF1Ng== -"@swc/core-linux-arm-gnueabihf@1.5.7": - version "1.5.7" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.5.7.tgz#160108633b9e1d1ad05f815bedc7e9eb5d59fc2a" - integrity sha512-cTZWTnCXLABOuvWiv6nQQM0hP6ZWEkzdgDvztgHI/+u/MvtzJBN5lBQ2lue/9sSFYLMqzqff5EHKlFtrJCA9dQ== +"@swc/core-linux-arm-gnueabihf@1.13.5": + version "1.13.5" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.13.5.tgz#4c8062bd598049b5b9b0beb762e075e76b4c23c3" + integrity sha512-Q6eS3Pt8GLkXxqz9TAw+AUk9HpVJt8Uzm54MvPsqp2yuGmY0/sNaPPNVqctCX9fu/Nu8eaWUen0si6iEiCsazQ== -"@swc/core-linux-arm64-gnu@1.5.7": - version "1.5.7" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.5.7.tgz#cbfa512683c73227ad25552f3b3e722b0e7fbd1d" - integrity sha512-hoeTJFBiE/IJP30Be7djWF8Q5KVgkbDtjySmvYLg9P94bHg9TJPSQoC72tXx/oXOgXvElDe/GMybru0UxhKx4g== +"@swc/core-linux-arm64-gnu@1.13.5": + version "1.13.5" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.13.5.tgz#7222d321197ea9304e387933e87d775849fc1ae6" + integrity sha512-aNDfeN+9af+y+M2MYfxCzCy/VDq7Z5YIbMqRI739o8Ganz6ST+27kjQFd8Y/57JN/hcnUEa9xqdS3XY7WaVtSw== -"@swc/core-linux-arm64-musl@1.5.7": - version "1.5.7" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.5.7.tgz#80239cb58fe57f3c86b44617fe784530ec55ee2b" - integrity sha512-+NDhK+IFTiVK1/o7EXdCeF2hEzCiaRSrb9zD7X2Z7inwWlxAntcSuzZW7Y6BRqGQH89KA91qYgwbnjgTQ22PiQ== +"@swc/core-linux-arm64-musl@1.13.5": + version "1.13.5" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.13.5.tgz#51e7958deaf37edc212bd9dc0ea1476f151d2bea" + integrity sha512-9+ZxFN5GJag4CnYnq6apKTnnezpfJhCumyz0504/JbHLo+Ue+ZtJnf3RhyA9W9TINtLE0bC4hKpWi8ZKoETyOQ== -"@swc/core-linux-x64-gnu@1.5.7": - version "1.5.7" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.5.7.tgz#a699c1632de60b6a63b7fdb7abcb4fef317e57ca" - integrity sha512-25GXpJmeFxKB+7pbY7YQLhWWjkYlR+kHz5I3j9WRl3Lp4v4UD67OGXwPe+DIcHqcouA1fhLhsgHJWtsaNOMBNg== +"@swc/core-linux-x64-gnu@1.13.5": + version "1.13.5" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.13.5.tgz#3476beab93ab03e92844d955ca9d9289aa4a5993" + integrity sha512-WD530qvHrki8Ywt/PloKUjaRKgstQqNGvmZl54g06kA+hqtSE2FTG9gngXr3UJxYu/cNAjJYiBifm7+w4nbHbA== -"@swc/core-linux-x64-musl@1.5.7": - version "1.5.7" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.5.7.tgz#8e4c203d6bc41e7f85d7d34d0fdf4ef751fa626c" - integrity sha512-0VN9Y5EAPBESmSPPsCJzplZHV26akC0sIgd3Hc/7S/1GkSMoeuVL+V9vt+F/cCuzr4VidzSkqftdP3qEIsXSpg== +"@swc/core-linux-x64-musl@1.13.5": + version "1.13.5" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.13.5.tgz#f4934b1e77e2a297909bb3ab977836205c36e5e0" + integrity sha512-Luj8y4OFYx4DHNQTWjdIuKTq2f5k6uSXICqx+FSabnXptaOBAbJHNbHT/06JZh6NRUouaf0mYXN0mcsqvkhd7Q== -"@swc/core-win32-arm64-msvc@1.5.7": - version "1.5.7" - resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.5.7.tgz#31e3d42b8c0aa79f0ea1a980c0dd1a999d378ed7" - integrity sha512-RtoNnstBwy5VloNCvmvYNApkTmuCe4sNcoYWpmY7C1+bPR+6SOo8im1G6/FpNem8AR5fcZCmXHWQ+EUmRWJyuA== +"@swc/core-win32-arm64-msvc@1.13.5": + version "1.13.5" + resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.13.5.tgz#5084c107435cfc82d4d901bfb388dc319d38a236" + integrity sha512-cZ6UpumhF9SDJvv4DA2fo9WIzlNFuKSkZpZmPG1c+4PFSEMy5DFOjBSllCvnqihCabzXzpn6ykCwBmHpy31vQw== -"@swc/core-win32-ia32-msvc@1.5.7": - version "1.5.7" - resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.5.7.tgz#a235285f9f62850aefcf9abb03420f2c54f63638" - integrity sha512-Xm0TfvcmmspvQg1s4+USL3x8D+YPAfX2JHygvxAnCJ0EHun8cm2zvfNBcsTlnwYb0ybFWXXY129aq1wgFC9TpQ== +"@swc/core-win32-ia32-msvc@1.13.5": + version "1.13.5" + resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.13.5.tgz#f8b2e28bc51b30467e316ed736a130c1324b9880" + integrity sha512-C5Yi/xIikrFUzZcyGj9L3RpKljFvKiDMtyDzPKzlsDrKIw2EYY+bF88gB6oGY5RGmv4DAX8dbnpRAqgFD0FMEw== -"@swc/core-win32-x64-msvc@1.5.7": - version "1.5.7" - resolved "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.5.7.tgz" - integrity sha512-tp43WfJLCsKLQKBmjmY/0vv1slVywR5Q4qKjF5OIY8QijaEW7/8VwPyUyVoJZEnDgv9jKtUTG5PzqtIYPZGnyg== +"@swc/core-win32-x64-msvc@1.13.5": + version "1.13.5" + resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.13.5.tgz#13883cf3c63bf11b787e28dcdf75ca0cc49efa83" + integrity sha512-YrKdMVxbYmlfybCSbRtrilc6UA8GF5aPmGKBdPvjrarvsmf4i7ZHGCEnLtfOMd3Lwbs2WUZq3WdMbozYeLU93Q== "@swc/core@^1.4.16": - version "1.5.7" - resolved "https://registry.npmjs.org/@swc/core/-/core-1.5.7.tgz" - integrity sha512-U4qJRBefIJNJDRCCiVtkfa/hpiZ7w0R6kASea+/KLp+vkus3zcLSB8Ub8SvKgTIxjWpwsKcZlPf5nrv4ls46SQ== + version "1.13.5" + resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.13.5.tgz#93874b831d3bd121560e6fcd688972b7fc7baa26" + integrity sha512-WezcBo8a0Dg2rnR82zhwoR6aRNxeTGfK5QCD6TQ+kg3xx/zNT02s/0o+81h/3zhvFSB24NtqEr8FTw88O5W/JQ== dependencies: - "@swc/counter" "^0.1.2" - "@swc/types" "0.1.7" + "@swc/counter" "^0.1.3" + "@swc/types" "^0.1.24" optionalDependencies: - "@swc/core-darwin-arm64" "1.5.7" - "@swc/core-darwin-x64" "1.5.7" - "@swc/core-linux-arm-gnueabihf" "1.5.7" - "@swc/core-linux-arm64-gnu" "1.5.7" - "@swc/core-linux-arm64-musl" "1.5.7" - "@swc/core-linux-x64-gnu" "1.5.7" - "@swc/core-linux-x64-musl" "1.5.7" - "@swc/core-win32-arm64-msvc" "1.5.7" - "@swc/core-win32-ia32-msvc" "1.5.7" - "@swc/core-win32-x64-msvc" "1.5.7" + "@swc/core-darwin-arm64" "1.13.5" + "@swc/core-darwin-x64" "1.13.5" + "@swc/core-linux-arm-gnueabihf" "1.13.5" + "@swc/core-linux-arm64-gnu" "1.13.5" + "@swc/core-linux-arm64-musl" "1.13.5" + "@swc/core-linux-x64-gnu" "1.13.5" + "@swc/core-linux-x64-musl" "1.13.5" + "@swc/core-win32-arm64-msvc" "1.13.5" + "@swc/core-win32-ia32-msvc" "1.13.5" + "@swc/core-win32-x64-msvc" "1.13.5" -"@swc/counter@^0.1.2", "@swc/counter@^0.1.3": +"@swc/counter@^0.1.3": version "0.1.3" - resolved "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz" + resolved "https://registry.yarnpkg.com/@swc/counter/-/counter-0.1.3.tgz#cc7463bd02949611c6329596fccd2b0ec782b0e9" integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ== -"@swc/types@0.1.7": - version "0.1.7" - resolved "https://registry.npmjs.org/@swc/types/-/types-0.1.7.tgz" - integrity sha512-scHWahbHF0eyj3JsxG9CFJgFdFNaVQCNAimBlT6PzS3n/HptxqREjsm4OH6AN3lYcffZYSPxXW8ua2BEHp0lJQ== +"@swc/types@^0.1.24": + version "0.1.25" + resolved "https://registry.yarnpkg.com/@swc/types/-/types-0.1.25.tgz#b517b2a60feb37dd933e542d93093719e4cf1078" + integrity sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g== dependencies: "@swc/counter" "^0.1.3" "@szmarczak/http-timer@^4.0.5": version "4.0.6" - resolved "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807" integrity sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w== dependencies: defer-to-connect "^2.0.0" @@ -2958,139 +2801,139 @@ dependencies: uint8-util "^2.2.5" -"@tiptap/core@^3.6.2": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-3.6.2.tgz#abda4116e4a39779fca7070e316b9ed9fdcded7e" - integrity sha512-XKZYrCVFsyQGF6dXQR73YR222l/76wkKfZ+2/4LCrem5qtcOarmv5pYxjUBG8mRuBPskTTBImSFTeQltJIUNCg== +"@tiptap/core@^3.6.7": + version "3.6.7" + resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-3.6.7.tgz#77ececdbe3379c02c594fda7fe4d3732dd79af28" + integrity sha512-Br3huqItnCkxjrt7EiIRMVd+dPfGRvy+ZTekRCofRzNioVtyRWRLH1KYM7cZ2z04wkDKA47E+j9Dr1z1aq474g== -"@tiptap/extension-blockquote@^3.6.2": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-3.6.2.tgz#01b589565c87a691e586e189ddcbcdc5f35618fc" - integrity sha512-TSl41UZhi3ugJMDaf91CA4F5NeFylgTSm6GqnZAHOE6IREdCpAK3qej2zaW3EzfpzxW7sRGLlytkZRvpeyjgJA== +"@tiptap/extension-blockquote@^3.6.7": + version "3.6.7" + resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-3.6.7.tgz#79ddf3796433de54f61297d69b0461c5ab82fec5" + integrity sha512-8IdRy7ixrUVVbiaaJBx7p6KRyJQDRezKL+mklkKEd2srK0ApCdQSjqnBbYQ20lkDMC6o35Jw+ZhHy/TdnmLscg== -"@tiptap/extension-bold@^3.6.2": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-3.6.2.tgz#ed721961daf3210c7ba4433a5aeae981043c2d77" - integrity sha512-Q9KO8CCPCAXYqHzIw8b/ookVmrfqfCg2cyh9h9Hvw6nhO4LOOnJMcGVmWsrpFItbwCGMafI5iY9SbSj7RpCyuw== +"@tiptap/extension-bold@^3.6.2", "@tiptap/extension-bold@^3.6.7": + version "3.6.7" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-3.6.7.tgz#7d2f3763b259bfe18df318e90f4e61e0a670aefe" + integrity sha512-K07qJY9uwcmjnh56l3Qj9ma0R8OffigMKXn0PKMtQ5exaRDuZFiHirCOw85u8iqLjVpx0hsZJ3gumP4RO+a31Q== -"@tiptap/extension-bubble-menu@^3.6.2": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-3.6.2.tgz#237d84f217c8da52c0bc5265a36557fb27d64eaf" - integrity sha512-OF5CxCmYExcXZjcectwAeujSeDZ4IltPy+SsqBZLbQRDts9PQhzv5azGDvYdL2eMMkT3yhO2gWkXxSHMxI3O6w== +"@tiptap/extension-bubble-menu@^3.6.7": + version "3.6.7" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-3.6.7.tgz#88ea4aa1c8f8a9c5d118ae5143b2cec978e2509b" + integrity sha512-5hn++5eZYUKhoEfAhpzq/0qQDThD/WZfdEUnual+xAgfYG6MXPeJhHfmNDjOlCpM2ld5p7ap38N273rnr4yulQ== dependencies: "@floating-ui/dom" "^1.0.0" -"@tiptap/extension-bullet-list@^3.6.2": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-3.6.2.tgz#be20b6c795c53bc0d199bdc4dd9f01b6270a1bee" - integrity sha512-Y5Uhir+za7xMm6RAe592aNNlLvCayVSQt2HfSckOr+c/v/Zd2bFUHv0ef6l/nUzUhDBs32Bg9SvfWx/yyMyNEw== +"@tiptap/extension-bullet-list@^3.6.7": + version "3.6.7" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-3.6.7.tgz#3926ae219b214cc3327e439fdb06797a29b7e3e7" + integrity sha512-ISuKxSNEJsTVglvbolBbrG8OvAAg/xC1EYtaDArDMcRdhi1UR9uy+u5ZkJBgoTIWPp2cvCnqnaCL8VH7vk4JAg== -"@tiptap/extension-code-block@^3.6.2": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-3.6.2.tgz#cb3f6f607dcfb36e3eff25255fdcfdedfb3940a7" - integrity sha512-5jfoiQ/3AUrIyuVU1NmEXar6sZFnY7wDFf3ZU2zpcBUG++yg/CmpOe5bXpoolczhl58cM/jyBG5gumQjyOxLNg== +"@tiptap/extension-code-block@^3.6.7": + version "3.6.7" + resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-3.6.7.tgz#769ea88fcba8be9feabc3087861dc761fef18045" + integrity sha512-BRNAxkkv6YNUnfpovR3RfLahqmvXqvRVvIIhgdMDt7/ifIY8CCdf+y/Fh3hVMi/QB71uZ1IMvrm1ikKm+w6rQg== -"@tiptap/extension-code@^3.6.2": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-3.6.2.tgz#5c6500d748fd4f52ddbe01ff114d4933c7a09e8f" - integrity sha512-U6jilbcpCxtLZAgJrTapXzzVJTXnS78kJITFSOLyGCTyGSm6PXatQ4hnaxVGmNet66GySONGjhwAVZ8+l94Rwg== +"@tiptap/extension-code@^3.6.7": + version "3.6.7" + resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-3.6.7.tgz#160ee044f12c182f773ae728ac8a7a5f97dcdea6" + integrity sha512-wCzd5NxxuRUGZteevy2R4Y1frBVMVmYwHl+kmTwOJw8tZ6+tuKQn/6+KpDiLfia4tQXfgH3YxuV0o/ArlD5yOQ== -"@tiptap/extension-document@^3.6.2": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-3.6.2.tgz#5c3f3a85d12868f5d4e6d6d258b8fa0b8000b778" - integrity sha512-4qg3KWL3aO1M7hfDpZR6/vSo7Cfqr3McyGUfqb/BXqYDW1DwT8jJkDTcHrGU7WUKRlWgoyPyzM8pZiGlP0uQHg== +"@tiptap/extension-document@^3.6.7": + version "3.6.7" + resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-3.6.7.tgz#d9ee2e40e3d3b107bd9a8e44f5681421e38f006a" + integrity sha512-Tlxf8tbViD57nlqzDyE/QwS4HGpwlqCFS1qD0tayoqpJqaVO5DvBS8XWFCYIQx8bQqaNsfPvTzjICXIPFOr0Rw== -"@tiptap/extension-dropcursor@^3.6.2": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@tiptap/extension-dropcursor/-/extension-dropcursor-3.6.2.tgz#22a64a4da25ac17cf0cd33e1e924762000152817" - integrity sha512-6R5sma/i2TKd5h9OpIcy3a0wOGp5BNT/zIgnE/1HTmKi40eNcCAVe8sxd6+iWA5ETONP1E48kDy4hqA5ZzZCiQ== +"@tiptap/extension-dropcursor@^3.6.7": + version "3.6.7" + resolved "https://registry.yarnpkg.com/@tiptap/extension-dropcursor/-/extension-dropcursor-3.6.7.tgz#937d1adffdf3782c61ff941e0b2182929fb7dc44" + integrity sha512-m3OF468rQCFq0kPIsKJl/nLgJjGTK0Sb6jLzVb692KOWnvWsGcREiRCuYnDHoym0TrOem/kyFqtrB4kCq8Xchw== -"@tiptap/extension-floating-menu@^3.6.2": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-3.6.2.tgz#cc9c97cdd5fa55407631d3135e00ca8051516444" - integrity sha512-ym7YMKGY3QhFUKUS6JYOwtdi8s2PeGmOhu7TwI9/U0LmGbELeKJBJl2BP1yB+Sjpv25pVL++CwJQ6dsrjDlZ8g== +"@tiptap/extension-floating-menu@^3.6.7": + version "3.6.7" + resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-3.6.7.tgz#9892c04b9e9f530dd5ddc095a2c4ef6fd4921be1" + integrity sha512-e/CM70p4Xld6uRJY5ke+VhezDLF8rU7Dvi5IHMblAF6NiupC9wCgWYZUsQDVfoGo4Ky9b+c54Ybnan2XGBkIRg== -"@tiptap/extension-gapcursor@^3.6.2": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-3.6.2.tgz#790c94d20a5b8ded4c0d38960254d24704a2bc08" - integrity sha512-gXg+EvUKlv3ZO1GxKkRmAsi/V4yyA8AzLW6ppOcYrM2CKf6epmPaVRgAjdwHCA6cm3QuCBJyWeGTCAjhjNakhw== +"@tiptap/extension-gapcursor@^3.6.7": + version "3.6.7" + resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-3.6.7.tgz#be296c794b0375cbe01a8033217c13ab49774e77" + integrity sha512-n8dtV7wmmrtMCdOww/ZJZX+nzQexDONIlxz3lckKn0HJ7OoZz9YKvcx8tcEbbKcGAPkl6j9dsRDxxVqXiKGhlw== -"@tiptap/extension-hard-break@^3.6.2": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-3.6.2.tgz#3c379d9104cd7d9e942277f22ba62c57fae267ad" - integrity sha512-ncuPBHhGY58QjluJvEH6vXotaa1QZ/vphXBGAr55kiATZwMIEHgwh2Hgc6AiFTcw057gabGn6jNFDfRB+HjbmA== +"@tiptap/extension-hard-break@^3.6.7": + version "3.6.7" + resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-3.6.7.tgz#300d3e7341a5737492ab354589f2503b5425263c" + integrity sha512-Jlzi5KPCylrSIrlYRxLC33OLlu/8p9rv0EsKPB04OXcr/Oyy9Z0X2/yTWzoNUma6cMpqikLAGQhQZ1rpGDKygw== -"@tiptap/extension-heading@^3.6.2": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-3.6.2.tgz#3884c309de60c9d61f1bb60c521410b3a0d88ed7" - integrity sha512-JQ2yjwXGAiwGc+MhS1mULBr354MHfmWqVDQLRg8ey6LkdXggTDDJ1Ni3GrUS7B5YcA/ICdhr4krXaQpNkT5Syw== +"@tiptap/extension-heading@^3.6.7": + version "3.6.7" + resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-3.6.7.tgz#ca02310eb07b4cb093bdd2754b3bb1ee5263ceb1" + integrity sha512-uuAkZ+WXr6jZ7x75Vim20V9o5RbaZmFAj/HjKjw6xgSUcMDL7dWdKNGO2v20iv6o8+MyZtD4ZCW0qfq401nA/Q== -"@tiptap/extension-horizontal-rule@^3.6.2": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.6.2.tgz#f5680b3209bc48bf8635f3674355bd3d47f15622" - integrity sha512-3TlPqedPDM9QkRTUPhOTxNxQVPSsBwlsuLrAZOgyM1y871Xi7M1DFX0h9LLXuqzPndYzUY16NjrfBGFJX+O56w== +"@tiptap/extension-horizontal-rule@^3.6.7": + version "3.6.7" + resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.6.7.tgz#26ea71e9d754403097eb43eb24b427159fae551c" + integrity sha512-ISUlP+OhhFiITNPwRP2MJPXtI0yobCeXnyMQDmI5of9j79N+ckq8CuEt/Ab3KS4hMM74dkBbsN0AGzyI60lLSQ== -"@tiptap/extension-italic@^3.6.2": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-3.6.2.tgz#ea314f5e723499c9e7a1021ad7836693db9c653c" - integrity sha512-46zYKqM3o9w1A2G9hWr0ERGbJpqIncoH45XIfLdAI6ZldZVVf+NeXMGwjOPf4+03cZ5/emk3MRTnVp9vF4ToIg== +"@tiptap/extension-italic@^3.6.2", "@tiptap/extension-italic@^3.6.7": + version "3.6.7" + resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-3.6.7.tgz#bef77575431f39c69ab55ca44a7ac582371ea421" + integrity sha512-aLLMLHyDN1ZD5dyR7NyDArbDllF4YLPLosq0+fqFT0UW5TpEG+d8pcRkvt8bA38Ih9AUQKTX9gzutfmOhX+xOw== -"@tiptap/extension-link@^3.6.2": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-3.6.2.tgz#5577d100cd3b735247db327b15d91de025cc76b6" - integrity sha512-3yiRDWa187h30e6iUOJeejZLsbzbJthLfBwTeJGx7pHh7RngsEW82npBRuqLoI3udhJGTkXbzwAFZ9qOGOjl1Q== +"@tiptap/extension-link@^3.6.2", "@tiptap/extension-link@^3.6.7": + version "3.6.7" + resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-3.6.7.tgz#01e2e70a6a18990dad8f38c95969cb609b154db2" + integrity sha512-Zu3tAkyd82efPGxyyBhx+J2Fl214lUu2pFD9683PagyCeoQY+xBx8vRAKMvu2zerRnN60IYuKzSM4OX4kGNgUQ== dependencies: linkifyjs "^4.3.2" -"@tiptap/extension-list-item@^3.6.2": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-3.6.2.tgz#705f782a872e4bbb6f0e125fe277c45aeefe8161" - integrity sha512-ma/D2GKylpNB04FfNI3tDMY+C9nz7Yk85H21YTIGv8QL5KlDK97L6orydmx6IVRc2nNMZQVitBIEKDOXcczX9w== +"@tiptap/extension-list-item@^3.6.7": + version "3.6.7" + resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-3.6.7.tgz#d1e2aa272a6b1d1f3fd8eb1fd352175cb8029625" + integrity sha512-eT88D37qRJz3GFD+6jc6OxTRfo+ovdx6X1mRJ+JQnCnYO8FCczG/4uQgiW/0rYUJH/RS9kAaTJHDIXTgxdfMyA== -"@tiptap/extension-list-keymap@^3.6.2": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@tiptap/extension-list-keymap/-/extension-list-keymap-3.6.2.tgz#f14e173325b443a89dbbca7f418b76ec3d5c9a21" - integrity sha512-1kl/lggH+LL/FUwcSx8p761ebk9L5ZGK06mGyDDU9XiGLS310CktZYLnpEuFgn/oMPbRHo26oNl9SXLn1/U53A== +"@tiptap/extension-list-keymap@^3.6.7": + version "3.6.7" + resolved "https://registry.yarnpkg.com/@tiptap/extension-list-keymap/-/extension-list-keymap-3.6.7.tgz#bc0dffecac8896fc6cd6b7c59414628d0413b362" + integrity sha512-UE9FLi6lnp+qgRy2lAdu71i6YRdXWWm0DVUoDvcHxDgpCBLZqJ4ucXeJfV3vpqYUC452LwMAzxV809akyeFlsg== -"@tiptap/extension-list@^3.6.2": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@tiptap/extension-list/-/extension-list-3.6.2.tgz#beb4d965f48085fa7f69197e10109cde8c175046" - integrity sha512-ZLaEHGVq4eL26hZZFE9e7RArk2rEjcVstN/YTRTKElTnLaf58kLTKN3nlgy1PWGwzfWGUuXURBuEBLaq5l6djg== +"@tiptap/extension-list@^3.6.7": + version "3.6.7" + resolved "https://registry.yarnpkg.com/@tiptap/extension-list/-/extension-list-3.6.7.tgz#5baa2dad49174d2e9b882866e77da62ce787b601" + integrity sha512-hF6/cTc6qKqm92Y9B8Cu2jUKzt6RSVqkPS9LFVOjTn0v/5FpzTpFsEDAs67i+PoOxKRnN6kZAs4b+QSWppgmMQ== -"@tiptap/extension-ordered-list@^3.6.2": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-3.6.2.tgz#43b83757f67264ff0050c03825e780da43680c1d" - integrity sha512-KdJ5MLIw19N+XiqQ2COXGtaq9TzUbtlLE5dgYCJQ2EumeZKIGELvUnHjrnIB9gH/gRlMs+hprLTh23xVUDJovg== +"@tiptap/extension-ordered-list@^3.6.7": + version "3.6.7" + resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-3.6.7.tgz#497cfc94b258a383dc96391dcc99fe91ed54439c" + integrity sha512-TmoT6HpwhVVSSnv0fq9ppqeJM4WEe2QvDSJ1a17KVrsm3Gc94obL79RG84dlfCgq8QTk6gKcZGPwer5YokfS/A== -"@tiptap/extension-paragraph@^3.6.2": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-3.6.2.tgz#d6cc89cdc369e463dd7dd4eb9121718441c984a0" - integrity sha512-jeJWj2xKib3392iHQEcB7wYZ30dUgXuwqpCTwtN9eANor+Zvv6CpDKBs1R2al6BYFbIJCgKeTulqxce0yoC80g== +"@tiptap/extension-paragraph@^3.6.7": + version "3.6.7" + resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-3.6.7.tgz#3b6738b72f1ed21f8eb46817e4c08bc1ec0589d4" + integrity sha512-ZfM0s719Ht4sDv1UdXiTxbElfiLFbdjyEBUQ60t2Gog2XOe6C9MJiUOF0QFqphr6eLdx29PHQh9SJjrsJ8Rgog== -"@tiptap/extension-strike@^3.6.2": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-3.6.2.tgz#2dab3f253a4ecfd525c5609ab5edb9325a6364c2" - integrity sha512-976u5WaioIN/0xCjl/UIEypmzACzxgVz6OGgfIsYyreMUiPjhhgzXb0A/2Po5p3nZpKcaMcxifOdhqdw+lDpIQ== +"@tiptap/extension-strike@^3.6.7": + version "3.6.7" + resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-3.6.7.tgz#49a0e0ad29cc0c247f1856562a9ad817195572bf" + integrity sha512-WPaaSVroPqNvxZ3KgA3jz1DI/GcGOwU2ra+gjamge+YVWJAS4vxmvWsoqs4z6m8RFFOXg5cv+VCSjTFDNd12tw== -"@tiptap/extension-text@^3.6.2": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-3.6.2.tgz#77313173a9f91208e40d298bc2d40b39371b8fca" - integrity sha512-fFSUEv1H3lM92yr6jZdELk0gog8rPTK5hTf08kP8RsY8pA80Br1ADVenejrMV4UNTmT1JWTXGBGhMqfQFHUvAQ== +"@tiptap/extension-text@^3.6.7": + version "3.6.7" + resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-3.6.7.tgz#4475c8aefc893a22fff27bbe2dad3d03ce532305" + integrity sha512-xkEduL3icFW2Wk0FUMLS9BCMatinEit/ALJ1XY/5J3QjCIH4pPHjiF8sXB+xv1numnxDSoU+8iVuTf4n2oDygg== -"@tiptap/extension-underline@^3.6.2": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@tiptap/extension-underline/-/extension-underline-3.6.2.tgz#9f0dfb9722bd3d0cd144fc955bcb94a3fcf5eac2" - integrity sha512-IrG6vjxTMI2EeyhZCtx0sNTEu83PsAvzIh4vxmG1fUi/RYokks+sFbgGMuq0jtO96iVNEszlpAC/vaqfxFJwew== +"@tiptap/extension-underline@^3.6.2", "@tiptap/extension-underline@^3.6.7": + version "3.6.7" + resolved "https://registry.yarnpkg.com/@tiptap/extension-underline/-/extension-underline-3.6.7.tgz#ffc0bda5f80fda3162d875cb66ce08e769c94daf" + integrity sha512-D3gJl7oPSDPgNa/QspPhw/fd9SUde1jh5bVPnsI/Fywmd68oGrGhAXZxErBfxfQwjfmRrY8aB82g8CXIC8JEeg== -"@tiptap/extensions@^3.6.2": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@tiptap/extensions/-/extensions-3.6.2.tgz#591fbd5b9fa41f98f69dbd7d21d5d38a2241d94b" - integrity sha512-tg7/DgaI6SpkeawryapUtNoBxsJUMJl3+nSjTfTvsaNXed+BHzLPsvmPbzlF9ScrAbVEx8nj6CCkneECYIQ4CQ== +"@tiptap/extensions@^3.6.7": + version "3.6.7" + resolved "https://registry.yarnpkg.com/@tiptap/extensions/-/extensions-3.6.7.tgz#05ecf20b63af8d00b50167c8399e82538acfe808" + integrity sha512-078CbHYbTdPt7cPf2dJpoR2rEDs3NXivAtaV+U5YF/17sUKNdrJ8kEaD3P3RT7vVKKJlWaTfttz5X68C0M7iBg== -"@tiptap/pm@^3.6.2": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@tiptap/pm/-/pm-3.6.2.tgz#2121d4917f92d11229529a26955a7033aa8a8843" - integrity sha512-g+NXjqjbj6NfHOMl22uNWVYIu8oCq7RFfbnpohPMsSKJLaHYE8mJR++7T6P5R9FoqhIFdwizg1jTpwRU5CHqXQ== +"@tiptap/pm@^3.6.7": + version "3.6.7" + resolved "https://registry.yarnpkg.com/@tiptap/pm/-/pm-3.6.7.tgz#2014aabae3518cf91cff0cb30dc6fc99bf8168bb" + integrity sha512-M657tCIM8R86aNhslgTa1Dt46IECdRh0fWdopUY4GgeQNHFb/UXfBUqJI33IEZhspWYF30x7h0W+mrJ4Jr15WA== dependencies: prosemirror-changeset "^2.3.0" prosemirror-collab "^1.3.1" @@ -3112,46 +2955,46 @@ prosemirror-view "^1.38.1" "@tiptap/react@^3.6.2": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@tiptap/react/-/react-3.6.2.tgz#5495776c9051a60ece7522da176c9f211a67c7df" - integrity sha512-jgG+bM/GDvI6jnqW3YyLtr/vOR6iO2ta9PYVzoWqNYIxISsMOJeRfinsIqB8l6hkiGZApn9bQji6oUXTc59fgA== + version "3.6.7" + resolved "https://registry.yarnpkg.com/@tiptap/react/-/react-3.6.7.tgz#62bfdc07787737dca20fd27a95904186ece52c69" + integrity sha512-PbSPjbPb1kWhr6VeCuk4Q2kQ6hlk9AN2mZK8cyT3O2ztHLVbcMW2hXrHTtNulN5JWUZlzZ4N/o1UVKo3iR+9dA== dependencies: "@types/use-sync-external-store" "^0.0.6" fast-deep-equal "^3.1.3" use-sync-external-store "^1.4.0" optionalDependencies: - "@tiptap/extension-bubble-menu" "^3.6.2" - "@tiptap/extension-floating-menu" "^3.6.2" + "@tiptap/extension-bubble-menu" "^3.6.7" + "@tiptap/extension-floating-menu" "^3.6.7" "@tiptap/starter-kit@^3.6.2": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@tiptap/starter-kit/-/starter-kit-3.6.2.tgz#ddd5612d4836a87082254779c9f152bb51e757bc" - integrity sha512-nPzraIx/f1cOUNqG1LSC0OTnEu3mudcN3jQVuyGh3dvdOnik7FUciJEVfHKnloAyeoijidEeiLpiGHInp2uREg== + version "3.6.7" + resolved "https://registry.yarnpkg.com/@tiptap/starter-kit/-/starter-kit-3.6.7.tgz#0f1d2e69e1b3650a82375d4133d94fde67bb3c96" + integrity sha512-ROpQPMDJzEyNTJGUXML9VQzuhz0u7ojNi72mEXk0WkNgfAD0RnKVvTb40pI+oyjfbuSfNFfxEQCUWrJJyuFGFA== dependencies: - "@tiptap/core" "^3.6.2" - "@tiptap/extension-blockquote" "^3.6.2" - "@tiptap/extension-bold" "^3.6.2" - "@tiptap/extension-bullet-list" "^3.6.2" - "@tiptap/extension-code" "^3.6.2" - "@tiptap/extension-code-block" "^3.6.2" - "@tiptap/extension-document" "^3.6.2" - "@tiptap/extension-dropcursor" "^3.6.2" - "@tiptap/extension-gapcursor" "^3.6.2" - "@tiptap/extension-hard-break" "^3.6.2" - "@tiptap/extension-heading" "^3.6.2" - "@tiptap/extension-horizontal-rule" "^3.6.2" - "@tiptap/extension-italic" "^3.6.2" - "@tiptap/extension-link" "^3.6.2" - "@tiptap/extension-list" "^3.6.2" - "@tiptap/extension-list-item" "^3.6.2" - "@tiptap/extension-list-keymap" "^3.6.2" - "@tiptap/extension-ordered-list" "^3.6.2" - "@tiptap/extension-paragraph" "^3.6.2" - "@tiptap/extension-strike" "^3.6.2" - "@tiptap/extension-text" "^3.6.2" - "@tiptap/extension-underline" "^3.6.2" - "@tiptap/extensions" "^3.6.2" - "@tiptap/pm" "^3.6.2" + "@tiptap/core" "^3.6.7" + "@tiptap/extension-blockquote" "^3.6.7" + "@tiptap/extension-bold" "^3.6.7" + "@tiptap/extension-bullet-list" "^3.6.7" + "@tiptap/extension-code" "^3.6.7" + "@tiptap/extension-code-block" "^3.6.7" + "@tiptap/extension-document" "^3.6.7" + "@tiptap/extension-dropcursor" "^3.6.7" + "@tiptap/extension-gapcursor" "^3.6.7" + "@tiptap/extension-hard-break" "^3.6.7" + "@tiptap/extension-heading" "^3.6.7" + "@tiptap/extension-horizontal-rule" "^3.6.7" + "@tiptap/extension-italic" "^3.6.7" + "@tiptap/extension-link" "^3.6.7" + "@tiptap/extension-list" "^3.6.7" + "@tiptap/extension-list-item" "^3.6.7" + "@tiptap/extension-list-keymap" "^3.6.7" + "@tiptap/extension-ordered-list" "^3.6.7" + "@tiptap/extension-paragraph" "^3.6.7" + "@tiptap/extension-strike" "^3.6.7" + "@tiptap/extension-text" "^3.6.7" + "@tiptap/extension-underline" "^3.6.7" + "@tiptap/extensions" "^3.6.7" + "@tiptap/pm" "^3.6.7" "@tokenizer/inflate@^0.2.6": version "0.2.7" @@ -3169,7 +3012,7 @@ "@tootallnate/once@2": version "2.0.0" - resolved "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== "@tsconfig/node10@^1.0.7": @@ -3194,12 +3037,12 @@ "@types/auto-launch@^5.0.5": version "5.0.5" - resolved "https://registry.npmjs.org/@types/auto-launch/-/auto-launch-5.0.5.tgz" + resolved "https://registry.yarnpkg.com/@types/auto-launch/-/auto-launch-5.0.5.tgz#439ed36aaaea501e2e2cfbddd8a20c366c34863b" integrity sha512-/nGvQZSzM/pvCMCh4Gt2kIeiUmOP/cKGJbjlInI+A+5MoV/7XmT56DJ6EU8bqc3+ItxEe4UC2GVspmPzcCc8cg== "@types/babel__core@^7.20.5": version "7.20.5" - resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== dependencies: "@babel/parser" "^7.20.7" @@ -3209,30 +3052,30 @@ "@types/babel__traverse" "*" "@types/babel__generator@*": - version "7.6.8" - resolved "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz" - integrity sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw== + version "7.27.0" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.27.0.tgz#b5819294c51179957afaec341442f9341e4108a9" + integrity sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg== dependencies: "@babel/types" "^7.0.0" "@types/babel__template@*": version "7.4.4" - resolved "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== dependencies: "@babel/parser" "^7.1.0" "@babel/types" "^7.0.0" "@types/babel__traverse@*": - version "7.20.5" - resolved "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz" - integrity sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ== + version "7.28.0" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz#07d713d6cce0d265c9849db0cbe62d3f61f36f74" + integrity sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q== dependencies: - "@babel/types" "^7.20.7" + "@babel/types" "^7.28.2" "@types/cacheable-request@^6.0.1": version "6.0.3" - resolved "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz" + resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.3.tgz#a430b3260466ca7b5ca5bfd735693b36e7a9d183" integrity sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw== dependencies: "@types/http-cache-semantics" "*" @@ -3241,66 +3084,53 @@ "@types/responselike" "^1.0.0" "@types/color-convert@*": - version "2.0.3" - resolved "https://registry.npmjs.org/@types/color-convert/-/color-convert-2.0.3.tgz" - integrity sha512-2Q6wzrNiuEvYxVQqhh7sXM2mhIhvZR/Paq4FdsQkOMgWsCIkKvSGj8Le1/XalulrmgOzPMqNa0ix+ePY4hTrfg== + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/color-convert/-/color-convert-2.0.4.tgz#843398ae71e951dc5415d202dfd5e43108823eeb" + integrity sha512-Ub1MmDdyZ7mX//g25uBAoH/mWGd9swVbt8BseymnaE18SU4po/PjmCrHxqIIRjBo3hV/vh1KGr0eMxUhp+t+dQ== dependencies: - "@types/color-name" "*" + "@types/color-name" "^1.1.0" -"@types/color-name@*": - version "1.1.4" - resolved "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.4.tgz" - integrity sha512-hulKeREDdLFesGQjl96+4aoJSHY5b2GRjagzzcqCfIrWhe5vkCqIvrLbqzBaI1q94Vg8DNJZZqTR5ocdWmWclg== +"@types/color-name@^1.1.0": + version "1.1.5" + resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.5.tgz#3a3510c4e3661f7707c5ae9c67d726986e6e147d" + integrity sha512-j2K5UJqGTxeesj6oQuGpMgifpT5k9HprgQd8D1Y0lOFqKHl3PJu5GMeS4Y5EgjS55AE6OQxf8mPED9uaGbf4Cg== "@types/color@^3.0.6": version "3.0.6" - resolved "https://registry.npmjs.org/@types/color/-/color-3.0.6.tgz" + resolved "https://registry.yarnpkg.com/@types/color/-/color-3.0.6.tgz#29c27a99d4de2975e1676712679a0bd7f646a3fb" integrity sha512-NMiNcZFRUAiUUCCf7zkAelY8eV3aKqfbzyFQlXpPIEeoNDbsEHGpb854V3gzTsGKYj830I5zPuOwU/TP5/cW6A== dependencies: "@types/color-convert" "*" "@types/conventional-commits-parser@^5.0.0": - version "5.0.0" - resolved "https://registry.npmjs.org/@types/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz" - integrity sha512-loB369iXNmAZglwWATL+WRe+CRMmmBPtpolYzIebFaX4YA3x+BEfLqhUAV9WanycKI3TG1IMr5bMJDajDKLlUQ== + version "5.0.1" + resolved "https://registry.yarnpkg.com/@types/conventional-commits-parser/-/conventional-commits-parser-5.0.1.tgz#8cb81cf170853496cbc501a3b32dcf5e46ffb61a" + integrity sha512-7uz5EHdzz2TqoMfV7ee61Egf5y6NkcO4FB/1iCCQnbeiI1F3xzv3vK5dBCXUCLQgGYS+mUeigK1iKQzvED+QnQ== dependencies: "@types/node" "*" "@types/debug@^4.1.6": version "4.1.12" - resolved "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz" + resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917" integrity sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ== dependencies: "@types/ms" "*" -"@types/estree@1.0.8": +"@types/estree@1.0.8", "@types/estree@^1.0.0": version "1.0.8" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== -"@types/estree@^1.0.0": - version "1.0.5" - resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz" - integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== - "@types/fs-extra@9.0.13", "@types/fs-extra@^9.0.11": version "9.0.13" - resolved "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz" + resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-9.0.13.tgz#7594fbae04fe7f1918ce8b3d213f74ff44ac1f45" integrity sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA== dependencies: "@types/node" "*" -"@types/glob@^7.1.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" - integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA== - dependencies: - "@types/minimatch" "*" - "@types/node" "*" - "@types/http-cache-semantics@*": version "4.0.4" - resolved "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz" + resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz#b979ebad3919799c979b17c72621c0bc0a31c6c4" integrity sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA== "@types/jsdom@^21.1.7": @@ -3313,16 +3143,16 @@ parse5 "^7.0.0" "@types/jsonwebtoken@^9.0.8": - version "9.0.8" - resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.8.tgz#313490052801edfb031bb32b6bbd77cc9f230852" - integrity sha512-7fx54m60nLFUVYlxAB1xpe9CBWX2vSrk50Y6ogRJ1v5xxtba7qXTg5BgYDN5dq+yuQQ9HaVlHJyAAt1/mxryFg== + version "9.0.10" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz#a7932a47177dcd4283b6146f3bd5c26d82647f09" + integrity sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA== dependencies: "@types/ms" "*" "@types/node" "*" "@types/keyv@^3.1.4": version "3.1.4" - resolved "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz" + resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.4.tgz#3ccdb1c6751b0c7e52300bcdacd5bcbf8faa75b6" integrity sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg== dependencies: "@types/node" "*" @@ -3334,19 +3164,19 @@ "@types/lodash-es@^4.17.12": version "4.17.12" - resolved "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz" + resolved "https://registry.yarnpkg.com/@types/lodash-es/-/lodash-es-4.17.12.tgz#65f6d1e5f80539aa7cfbfc962de5def0cf4f341b" integrity sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ== dependencies: "@types/lodash" "*" "@types/lodash@*": - version "4.17.3" - resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.3.tgz" - integrity sha512-zmNrEJaBvNskZXQWaUQq6bktF4IDGVfDS78M+YEk5aCn9M/b94/mB/6WCyfH2/MjwBdc6QuOor95CIlKWYRL3A== + version "4.17.20" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.20.tgz#1ca77361d7363432d29f5e55950d9ec1e1c6ea93" + integrity sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA== "@types/magnet-uri@*": version "5.1.5" - resolved "https://registry.npmjs.org/@types/magnet-uri/-/magnet-uri-5.1.5.tgz" + resolved "https://registry.yarnpkg.com/@types/magnet-uri/-/magnet-uri-5.1.5.tgz#8034da138edb41f007729ff18c76e0454df42438" integrity sha512-SbBjlb1KGe38VfjRR+mwqztJd/4skhdKkRbIzPDhTy7IAeEAPZWIVSEkZw00Qr4ZZOGR3/ATJ20WWPBfrKHGdA== dependencies: "@types/node" "*" @@ -3364,24 +3194,19 @@ resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-2.0.0.tgz#d43878b5b20222682163ae6f897b20447233bdfd" integrity sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg== -"@types/minimatch@*": - version "5.1.2" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca" - integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== - "@types/ms@*": - version "0.7.34" - resolved "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz" - integrity sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g== + version "2.1.0" + resolved "https://registry.yarnpkg.com/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78" + integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== -"@types/node@*", "@types/node@^20.12.7": - version "20.12.12" - resolved "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz" - integrity sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw== +"@types/node@*": + version "24.7.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-24.7.2.tgz#5adf66b6e2ac5cab1d10a2ad3682e359cb652f4a" + integrity sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA== dependencies: - undici-types "~5.26.4" + undici-types "~7.14.0" -"@types/node@^20.9.0": +"@types/node@^20.12.7", "@types/node@^20.9.0": version "20.19.21" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.21.tgz#6e5378e04993c40395473b13baf94a09875157b8" integrity sha512-CsGG2P3I5y48RPMfprQGfy4JPRZ6csfC3ltBZSRItG3ngggmNY/qs2uZKp4p9VbrpqNNSMzUZNFZKzgOGnd/VA== @@ -3390,14 +3215,14 @@ "@types/parse-torrent-file@*": version "4.0.6" - resolved "https://registry.npmjs.org/@types/parse-torrent-file/-/parse-torrent-file-4.0.6.tgz" + resolved "https://registry.yarnpkg.com/@types/parse-torrent-file/-/parse-torrent-file-4.0.6.tgz#11801dfd5b0a017302a164b72c8869f2bcba15b1" integrity sha512-SxqVth0Iv0WuEkqWS5MaY4S4Tlyi+QHkElQREvsUPw2xHcPgKyQ2dkJRRv5vAxmLzH+tnMdOj1Nws/wsenbzUw== dependencies: "@types/node" "*" "@types/parse-torrent@^5.8.7": version "5.8.7" - resolved "https://registry.npmjs.org/@types/parse-torrent/-/parse-torrent-5.8.7.tgz" + resolved "https://registry.yarnpkg.com/@types/parse-torrent/-/parse-torrent-5.8.7.tgz#68bca7701201a67568fdc7f194bdf3f2a49c2c11" integrity sha512-vZtYe450hO+KL7B5fejM8CHWg1LPZKeVXlolphPsWf6n4H0ZUlI6ICbqHoaFmH7JQmU2yRbGgyvqqizdFuGPFQ== dependencies: "@types/magnet-uri" "*" @@ -3413,28 +3238,26 @@ xmlbuilder ">=11.0.1" "@types/prop-types@*": - version "15.7.12" - resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz" - integrity sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q== + version "15.7.15" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.15.tgz#e6e5a86d602beaca71ce5163fadf5f95d70931c7" + integrity sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw== "@types/react-dom@^18.2.18": - version "18.3.0" - resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz" - integrity sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg== - dependencies: - "@types/react" "*" + version "18.3.7" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.7.tgz#b89ddf2cd83b4feafcc4e2ea41afdfb95a0d194f" + integrity sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ== -"@types/react@*", "@types/react@^18.2.48": - version "18.3.2" - resolved "https://registry.npmjs.org/@types/react/-/react-18.3.2.tgz" - integrity sha512-Btgg89dAnqD4vV7R3hlwOxgqobUQKgx3MmrQRi0yYbs/P0ym8XozIAlkqVilPqHQwXs4e9Tf63rrCgl58BcO4w== +"@types/react@^18.2.48": + version "18.3.26" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.26.tgz#4c5970878d30db3d2a0bca1e4eb5f258e391bbeb" + integrity sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA== dependencies: "@types/prop-types" "*" csstype "^3.0.2" "@types/responselike@^1.0.0": version "1.0.3" - resolved "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz" + resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.3.tgz#cc29706f0a397cfe6df89debfe4bf5cea159db50" integrity sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw== dependencies: "@types/node" "*" @@ -3448,14 +3271,9 @@ "@types/tough-cookie@*": version "4.0.5" - resolved "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz" + resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.5.tgz#cb6e2a691b70cb177c6e3ae9c1d2e8b2ea8cd304" integrity sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA== -"@types/use-sync-external-store@^0.0.3": - version "0.0.3" - resolved "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz" - integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA== - "@types/use-sync-external-store@^0.0.6": version "0.0.6" resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz#60be8d21baab8c305132eb9cb912ed497852aadc" @@ -3463,13 +3281,13 @@ "@types/user-agents@^1.0.4": version "1.0.4" - resolved "https://registry.npmjs.org/@types/user-agents/-/user-agents-1.0.4.tgz" + resolved "https://registry.yarnpkg.com/@types/user-agents/-/user-agents-1.0.4.tgz#49bf6760d9ca3858d91e6258835bf2c329b87cf0" integrity sha512-AjeFc4oX5WPPflgKfRWWJfkEk7Wu82fnj1rROPsiqFt6yElpdGFg8Srtm/4PU4rA9UiDUZlruGPgcwTMQlwq4w== "@types/verror@^1.10.3": - version "1.10.10" - resolved "https://registry.yarnpkg.com/@types/verror/-/verror-1.10.10.tgz#d5a4b56abac169bfbc8b23d291363a682e6fa087" - integrity sha512-l4MM0Jppn18hb9xmM6wwD1uTdShpf9Pn80aXTStnK1C94gtPvJcV2FrDmbOQUAQfJ1cKZHktkQUDwEqaAKXMMg== + version "1.10.11" + resolved "https://registry.yarnpkg.com/@types/verror/-/verror-1.10.11.tgz#d3d6b418978c8aa202d41e5bb3483227b6ecc1bb" + integrity sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg== "@types/winreg@^1.2.36": version "1.2.36" @@ -3485,7 +3303,7 @@ "@types/yauzl@^2.9.1": version "2.10.3" - resolved "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz" + resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.3.tgz#e9b2808b4f109504a03cda958259876f61017999" integrity sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q== dependencies: "@types/node" "*" @@ -3571,30 +3389,38 @@ "@typescript-eslint/types" "7.18.0" eslint-visitor-keys "^3.4.3" +"@typescript/vfs@^1.5.2": + version "1.6.1" + resolved "https://registry.yarnpkg.com/@typescript/vfs/-/vfs-1.6.1.tgz#fe7087d5a43715754f7ea9bf6e0b905176c9eebd" + integrity sha512-JwoxboBh7Oz1v38tPbkrZ62ZXNHAk9bJ7c9x0eI5zBfBnBYGhURdbnh7Z4smN/MV48Y5OCcZb58n972UtbazsA== + dependencies: + debug "^4.1.1" + "@ungap/structured-clone@^1.2.0": - version "1.2.0" - resolved "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz" - integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + version "1.3.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" + integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== "@vitejs/plugin-react@^4.2.1": - version "4.2.1" - resolved "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.2.1.tgz" - integrity sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ== + version "4.7.0" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz#647af4e7bb75ad3add578e762ad984b90f4a24b9" + integrity sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA== dependencies: - "@babel/core" "^7.23.5" - "@babel/plugin-transform-react-jsx-self" "^7.23.3" - "@babel/plugin-transform-react-jsx-source" "^7.23.3" + "@babel/core" "^7.28.0" + "@babel/plugin-transform-react-jsx-self" "^7.27.1" + "@babel/plugin-transform-react-jsx-source" "^7.27.1" + "@rolldown/pluginutils" "1.0.0-beta.27" "@types/babel__core" "^7.20.5" - react-refresh "^0.14.0" + react-refresh "^0.17.0" "@xmldom/xmldom@^0.8.8": - version "0.8.10" - resolved "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz" - integrity sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw== + version "0.8.11" + resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.11.tgz#b79de2d67389734c57c52595f7a7305e30c2d608" + integrity sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw== JSONStream@^1.3.5: version "1.3.5" - resolved "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz" + resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== dependencies: jsonparse "^1.2.0" @@ -3607,7 +3433,7 @@ abbrev@^1.0.0: abort-controller@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== dependencies: event-target-shim "^5.0.0" @@ -3626,49 +3452,37 @@ abstract-level@^2.0.0: acorn-jsx@^5.3.2: version "5.3.2" - resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== acorn-walk@^8.1.1: - version "8.3.3" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.3.tgz#9caeac29eefaa0c41e3d4c65137de4d6f34df43e" - integrity sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw== + version "8.3.4" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" + integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== dependencies: acorn "^8.11.0" -acorn@^8.11.0, acorn@^8.4.1: - version "8.12.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" - integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== - -acorn@^8.9.0: - version "8.11.3" - resolved "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz" - integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== +acorn@^8.11.0, acorn@^8.4.1, acorn@^8.9.0: + version "8.15.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" + integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== agent-base@6, agent-base@^6.0.2: version "6.0.2" - resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== dependencies: debug "4" -agent-base@^7.0.2, agent-base@^7.1.0: - version "7.1.1" - resolved "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz" - integrity sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA== - dependencies: - debug "^4.3.4" - -agent-base@^7.1.3: - version "7.1.3" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.3.tgz#29435eb821bc4194633a5b89e5bc4703bafc25a1" - integrity sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw== +agent-base@^7.1.0, agent-base@^7.1.2, agent-base@^7.1.3: + version "7.1.4" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.4.tgz#e3cd76d4c548ee895d3c3fd8dc1f6c5b9032e7a8" + integrity sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ== agentkeepalive@^4.2.1: - version "4.5.0" - resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.5.0.tgz#2673ad1389b3c418c5a20c5d7364f93ca04be923" - integrity sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew== + version "4.6.0" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.6.0.tgz#35f73e94b3f40bf65f105219c623ad19c136ea6a" + integrity sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ== dependencies: humanize-ms "^1.2.1" @@ -3682,12 +3496,12 @@ aggregate-error@^3.0.0: ajv-keywords@^3.4.1: version "3.5.2" - resolved "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== ajv@^6.10.0, ajv@^6.12.0, ajv@^6.12.4: version "6.12.6" - resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== dependencies: fast-deep-equal "^3.1.1" @@ -3696,43 +3510,36 @@ ajv@^6.10.0, ajv@^6.12.0, ajv@^6.12.4: uri-js "^4.2.2" ajv@^8.11.0: - version "8.13.0" - resolved "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz" - integrity sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA== + version "8.17.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" + integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== dependencies: fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" json-schema-traverse "^1.0.0" require-from-string "^2.0.2" - uri-js "^4.4.1" ansi-regex@^5.0.1: version "5.0.1" - resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== ansi-regex@^6.0.1: - version "6.0.1" - resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz" - integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== - -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" + version "6.2.2" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.2.2.tgz#60216eea464d864597ce2832000738a0589650c1" + integrity sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg== ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== dependencies: color-convert "^2.0.1" ansi-styles@^6.1.0: - version "6.2.1" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz" - integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + version "6.2.3" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.3.tgz#c044d5dcc521a076413472597a1acb1f103c4041" + integrity sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg== app-builder-bin@5.0.0-alpha.12: version "5.0.0-alpha.12" @@ -3780,7 +3587,7 @@ app-builder-lib@26.0.12: applescript@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/applescript/-/applescript-1.0.0.tgz" + resolved "https://registry.yarnpkg.com/applescript/-/applescript-1.0.0.tgz#bb87af568cad034a4e48c4bdaf6067a3a2701317" integrity sha512-yvtNHdWvtbYEiIazXAdp/NY+BBb65/DAseqlNiJQjOx9DynuzOYDbVLBJvuc0ve0VL9x6B3OHF6eH52y9hCBtQ== arg@^4.1.0: @@ -3805,15 +3612,7 @@ aria-query@^5.3.2: resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.2.tgz#93f81a43480e33a338f19163a3d10a50c01dcd59" integrity sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw== -array-buffer-byte-length@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f" - integrity sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg== - dependencies: - call-bind "^1.0.5" - is-array-buffer "^3.0.4" - -array-buffer-byte-length@^1.0.2: +array-buffer-byte-length@^1.0.1, array-buffer-byte-length@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz#384d12a37295aec3769ab022ad323a18a51ccf8b" integrity sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw== @@ -3827,16 +3626,18 @@ array-ify@^1.0.0: integrity sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng== array-includes@^3.1.6, array-includes@^3.1.8: - version "3.1.8" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.8.tgz#5e370cbe172fdd5dd6530c1d4aadda25281ba97d" - integrity sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ== + version "3.1.9" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.9.tgz#1f0ccaa08e90cdbc3eb433210f903ad0f17c3f3a" + integrity sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ== dependencies: - call-bind "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.4" define-properties "^1.2.1" - es-abstract "^1.23.2" - es-object-atoms "^1.0.0" - get-intrinsic "^1.2.4" - is-string "^1.0.7" + es-abstract "^1.24.0" + es-object-atoms "^1.1.1" + get-intrinsic "^1.3.0" + is-string "^1.1.1" + math-intrinsics "^1.1.0" array-union@^2.1.0: version "2.1.0" @@ -3856,26 +3657,16 @@ array.prototype.findlast@^1.2.5: es-shim-unscopables "^1.0.2" array.prototype.flat@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz#1476217df8cff17d72ee8f3ba06738db5b387d18" - integrity sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA== + version "1.3.3" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz#534aaf9e6e8dd79fb6b9a9917f839ef1ec63afe5" + integrity sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg== dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - es-shim-unscopables "^1.0.0" + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-shim-unscopables "^1.0.2" -array.prototype.flatmap@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527" - integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - es-shim-unscopables "^1.0.0" - -array.prototype.flatmap@^1.3.3: +array.prototype.flatmap@^1.3.2, array.prototype.flatmap@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz#712cc792ae70370ae40586264629e33aab5dd38b" integrity sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg== @@ -3896,20 +3687,6 @@ array.prototype.tosorted@^1.1.4: es-errors "^1.3.0" es-shim-unscopables "^1.0.2" -arraybuffer.prototype.slice@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz#097972f4255e41bc3425e37dc3f6421cf9aefde6" - integrity sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A== - dependencies: - array-buffer-byte-length "^1.0.1" - call-bind "^1.0.5" - define-properties "^1.2.1" - es-abstract "^1.22.3" - es-errors "^1.2.1" - get-intrinsic "^1.2.3" - is-array-buffer "^3.0.4" - is-shared-array-buffer "^1.0.2" - arraybuffer.prototype.slice@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz#9d760d84dbdd06d0cbf92c8849615a1a7ab3183c" @@ -3943,7 +3720,12 @@ async-exit-hook@^2.0.1: resolved "https://registry.yarnpkg.com/async-exit-hook/-/async-exit-hook-2.0.1.tgz#8bd8b024b0ec9b1c01cccb9af9db29bd717dfaf3" integrity sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw== -async@^3.2.3: +async-function@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/async-function/-/async-function-1.0.0.tgz#509c9fca60eaf85034c6829838188e4e4c8ffb2b" + integrity sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA== + +async@^3.2.6: version "3.2.6" resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== @@ -3977,9 +3759,9 @@ available-typed-arrays@^1.0.7: possible-typed-array-names "^1.0.0" axe-core@^4.10.0: - version "4.10.0" - resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.10.0.tgz#d9e56ab0147278272739a000880196cdfe113b59" - integrity sha512-Mr2ZakwQ7XUAjp7pAwQWRhhK8mQQ6JAaNWSjmjxil0R8BPioMtQsTLOolGYkji1rcL++3dCqZA3zWqpT+9Ew6g== + version "4.11.0" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.11.0.tgz#16f74d6482e343ff263d4f4503829e9ee91a86b6" + integrity sha512-ilYanEU8vxxBexpJd8cWM4ElSQq4QctCLKih0TSfjIfCQTeyH/6zVrmIJfLPrKTKJRbiG+cfnZbQIjAlJmF1jQ== axios-cookiejar-support@^5.0.5: version "5.0.5" @@ -4017,6 +3799,11 @@ base64-js@^1.3.1, base64-js@^1.5.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +baseline-browser-mapping@^2.8.9: + version "2.8.16" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.8.16.tgz#e17789673e7f4b7654f81ab2ef25e96ab6a895f9" + integrity sha512-OMu3BGQ4E7P1ErFsIPpbJh0qvDudM/UuJeHgkAvfWe+0HFJCXh+t/l8L6fVLR55RI/UbKrVLnAXZSVwd9ysWYw== + bencode@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/bencode/-/bencode-4.0.0.tgz#36ca0bc366290dad002215fc52fc74edf4eb0625" @@ -4044,22 +3831,22 @@ boolean@^3.0.1: integrity sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw== bowser@^2.11.0: - version "2.11.0" - resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.11.0.tgz#5ca3c35757a7aa5771500c70a73a9f91ef420a8f" - integrity sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA== + version "2.12.1" + resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.12.1.tgz#f9ad78d7aebc472feb63dd9635e3ce2337e0e2c1" + integrity sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw== brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + version "1.1.12" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" + integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== dependencies: balanced-match "^1.0.0" concat-map "0.0.1" brace-expansion@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" - integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + version "2.0.2" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.2.tgz#54fc53237a613d854c7bd37463aad17df87214e7" + integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== dependencies: balanced-match "^1.0.0" @@ -4070,25 +3857,16 @@ braces@^3.0.3: dependencies: fill-range "^7.1.1" -browserslist@^4.22.2: - version "4.24.0" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.0.tgz#a1325fe4bc80b64fda169629fc01b3d6cecd38d4" - integrity sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A== - dependencies: - caniuse-lite "^1.0.30001663" - electron-to-chromium "^1.5.28" - node-releases "^2.0.18" - update-browserslist-db "^1.1.0" - browserslist@^4.24.0: - version "4.24.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.3.tgz#5fc2725ca8fb3c1432e13dac278c7cc103e026d2" - integrity sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA== + version "4.26.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.26.3.tgz#40fbfe2d1cd420281ce5b1caa8840049c79afb56" + integrity sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w== dependencies: - caniuse-lite "^1.0.30001688" - electron-to-chromium "^1.5.73" - node-releases "^2.0.19" - update-browserslist-db "^1.1.1" + baseline-browser-mapping "^2.8.9" + caniuse-lite "^1.0.30001746" + electron-to-chromium "^1.5.227" + node-releases "^2.0.21" + update-browserslist-db "^1.1.3" buffer-builder@^0.2.0: version "0.2.0" @@ -4100,7 +3878,7 @@ buffer-crc32@~0.2.3: resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== -buffer-equal-constant-time@1.0.1: +buffer-equal-constant-time@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== @@ -4204,26 +3982,15 @@ cacheable-request@^7.0.2: normalize-url "^6.0.1" responselike "^2.0.0" -call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz#32e5892e6361b29b0b545ba6f7763378daca2840" - integrity sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g== +call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== dependencies: es-errors "^1.3.0" function-bind "^1.1.2" -call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" - integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== - dependencies: - es-define-property "^1.0.0" - es-errors "^1.3.0" - function-bind "^1.1.2" - get-intrinsic "^1.2.4" - set-function-length "^1.2.1" - -call-bind@^1.0.8: +call-bind@^1.0.7, call-bind@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c" integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== @@ -4233,21 +4000,13 @@ call-bind@^1.0.8: get-intrinsic "^1.2.4" set-function-length "^1.2.2" -call-bound@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.2.tgz#9dbd4daf9f5f753bec3e4c8fbb8a2ecc4de6c39b" - integrity sha512-0lk0PHFe/uz0vl527fG9CgdE9WdafjDbCXvBbs+LUv000TVt2Jjhqbs4Jwm8gz070w8xXyEAxrPOMullsxXeGg== +call-bound@^1.0.2, call-bound@^1.0.3, call-bound@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" + integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== dependencies: - call-bind "^1.0.8" - get-intrinsic "^1.2.5" - -call-bound@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.3.tgz#41cfd032b593e39176a71533ab4f384aa04fd681" - integrity sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA== - dependencies: - call-bind-apply-helpers "^1.0.1" - get-intrinsic "^1.2.6" + call-bind-apply-helpers "^1.0.2" + get-intrinsic "^1.3.0" callsites@^3.0.0: version "3.1.0" @@ -4259,26 +4018,12 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001663: - version "1.0.30001664" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001664.tgz#d588d75c9682d3301956b05a3749652a80677df4" - integrity sha512-AmE7k4dXiNKQipgn7a2xg558IRqPN3jMQY/rOsbxDhrd0tyChwbITBfiwtnqz8bi2M5mIWbxAYBvk7W7QBUS2g== +caniuse-lite@^1.0.30001746: + version "1.0.30001750" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001750.tgz#c229f82930033abd1502c6f73035356cf528bfbc" + integrity sha512-cuom0g5sdX6rw00qOoLNSFCJ9/mYIsuSOA+yzpDw8eopiFqcVwQvZHqov0vmEighRxX++cfC0Vg1G+1Iy/mSpQ== -caniuse-lite@^1.0.30001688: - version "1.0.30001690" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz#f2d15e3aaf8e18f76b2b8c1481abde063b8104c8" - integrity sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w== - -chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -4287,15 +4032,22 @@ chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: supports-color "^7.1.0" chalk@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" - integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== + version "5.6.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.6.2.tgz#b1238b6e23ea337af71c7f8a295db5af0c158aea" + integrity sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA== check-disk-space@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/check-disk-space/-/check-disk-space-3.4.0.tgz#eb8e69eee7a378fd12e35281b8123a8b4c4a8ff7" integrity sha512-drVkSqfwA+TvuEhFipiR1OC9boEGZL5RrWvVsOthdcvQNXyCCuKkEiTOTXZ7qxSf/GLwq4GvzfrQD/Wz325hgw== +chokidar@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30" + integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== + dependencies: + readdirp "^4.0.1" + chownr@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" @@ -4377,13 +4129,6 @@ clone@^1.0.2: resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - color-convert@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" @@ -4391,11 +4136,6 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - color-name@^1.0.0, color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" @@ -4499,12 +4239,12 @@ core-util-is@1.0.2: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== -cosmiconfig-typescript-loader@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-5.0.0.tgz#0d3becfe022a871f7275ceb2397d692e06045dc8" - integrity sha512-+8cK7jRAReYkMwMiG+bxhcNKiHJDM6bR9FD/nGBXOWdMLuYawjF5cGrtLilJ+LGd3ZjCXnJjR5DkfWPoIVlqJA== +cosmiconfig-typescript-loader@^6.1.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-6.2.0.tgz#26399fa92e9569052062846afd038c94628f0f69" + integrity sha512-GEN39v7TgdxgIoNcdkRE3uiAzQt3UXLyHbRHD6YoL048XAeOomyxaP+Hh/+2C6C2wYjxJ2onhJcsQp+L4YEkVQ== dependencies: - jiti "^1.19.1" + jiti "^2.6.1" cosmiconfig@^8.1.3: version "8.3.6" @@ -4563,16 +4303,7 @@ cross-fetch-ponyfill@^1.0.3: abort-controller "^3.0.0" node-fetch "^3.3.0" -cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -cross-spawn@^7.0.6: +cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.6: version "7.0.6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== @@ -4582,11 +4313,12 @@ cross-spawn@^7.0.6: which "^2.0.1" cssstyle@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-4.1.0.tgz#161faee382af1bafadb6d3867a92a19bcb4aea70" - integrity sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA== + version "4.6.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-4.6.0.tgz#ea18007024e3167f4f105315f3ec2d982bf48ed9" + integrity sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg== dependencies: - rrweb-cssom "^0.7.1" + "@asamuzakjp/css-color" "^3.2.0" + rrweb-cssom "^0.8.0" csstype@^3.0.2: version "3.1.3" @@ -4616,15 +4348,6 @@ data-urls@^5.0.0: whatwg-mimetype "^4.0.0" whatwg-url "^14.0.0" -data-view-buffer@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2" - integrity sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA== - dependencies: - call-bind "^1.0.6" - es-errors "^1.3.0" - is-data-view "^1.0.1" - data-view-buffer@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.2.tgz#211a03ba95ecaf7798a8c7198d79536211f88570" @@ -4634,15 +4357,6 @@ data-view-buffer@^1.0.2: es-errors "^1.3.0" is-data-view "^1.0.2" -data-view-byte-length@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz#90721ca95ff280677eb793749fce1011347669e2" - integrity sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ== - dependencies: - call-bind "^1.0.7" - es-errors "^1.3.0" - is-data-view "^1.0.1" - data-view-byte-length@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz#9e80f7ca52453ce3e93d25a35318767ea7704735" @@ -4652,15 +4366,6 @@ data-view-byte-length@^1.0.2: es-errors "^1.3.0" is-data-view "^1.0.2" -data-view-byte-offset@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz#5e0bbfb4828ed2d1b9b400cd8a7d119bca0ff18a" - integrity sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA== - dependencies: - call-bind "^1.0.6" - es-errors "^1.3.0" - is-data-view "^1.0.1" - data-view-byte-offset@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz#068307f9b71ab76dbbe10291389e020856606191" @@ -4675,24 +4380,17 @@ date-fns@^3.6.0: resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-3.6.0.tgz#f20ca4fe94f8b754951b24240676e8618c0206bf" integrity sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww== -debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: - version "4.3.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" - integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== - dependencies: - ms "^2.1.3" - -debug@^4.4.0: - version "4.4.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b" - integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@^4.4.0: + version "4.4.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== dependencies: ms "^2.1.3" decimal.js@^10.4.3: - version "10.4.3" - resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" - integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== + version "10.6.0" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.6.0.tgz#e649a43e3ab953a72192ff5983865e509f37ed9a" + integrity sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg== decompress-response@^6.0.0: version "6.0.0" @@ -4727,7 +4425,7 @@ define-data-property@^1.0.1, define-data-property@^1.1.4: es-errors "^1.3.0" gopd "^1.0.1" -define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1: +define-properties@^1.1.3, define-properties@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== @@ -4741,10 +4439,15 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== +detect-libc@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg== + detect-libc@^2.0.1: - version "2.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700" - integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== + version "2.1.2" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.1.2.tgz#689c5dcdc1900ef5583a4cb9f6d7b473742074ad" + integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ== detect-node-es@^1.1.0: version "1.1.0" @@ -4756,11 +4459,6 @@ detect-node@^2.0.4: resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== -dexie@^4.0.10: - version "4.0.10" - resolved "https://registry.yarnpkg.com/dexie/-/dexie-4.0.10.tgz#979e3ee75993b44eea3852f97ceb198019d5b287" - integrity sha512-eM2RzuR3i+M046r2Q0Optl3pS31qTWf8aFuA7H9wnsHTwl8EPvroVLwvQene/6paAs39Tbk6fWZcn2aZaHkc/w== - diff@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" @@ -4848,27 +4546,18 @@ dot-prop@^5.1.0: is-obj "^2.0.0" dotenv-expand@^11.0.6: - version "11.0.6" - resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-11.0.6.tgz#f2c840fd924d7c77a94eff98f153331d876882d3" - integrity sha512-8NHi73otpWsZGBSZwwknTXS5pqMOrk9+Ssrna8xCaxkzEpU9OTf9R5ArQGVw03//Zmk9MOwLPng9WwndvpAJ5g== + version "11.0.7" + resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-11.0.7.tgz#af695aea007d6fdc84c86cd8d0ad7beb40a0bd08" + integrity sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA== dependencies: - dotenv "^16.4.4" + dotenv "^16.4.5" -dotenv@^16.4.4, dotenv@^16.4.5: - version "16.4.5" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" - integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== +dotenv@^16.4.5: + version "16.6.1" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.6.1.tgz#773f0e69527a8315c7285d5ee73c4459d20a8020" + integrity sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow== -dunder-proto@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.0.tgz#c2fce098b3c8f8899554905f4377b6d85dabaa80" - integrity sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A== - dependencies: - call-bind-apply-helpers "^1.0.0" - es-errors "^1.3.0" - gopd "^1.2.0" - -dunder-proto@^1.0.1: +dunder-proto@^1.0.0, dunder-proto@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== @@ -4931,15 +4620,10 @@ electron-publish@26.0.11: lazy-val "^1.0.5" mime "^2.5.2" -electron-to-chromium@^1.5.28: - version "1.5.30" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.30.tgz#5b264b489cfe0c3dd71097c164d795444834e7c7" - integrity sha512-sXI35EBN4lYxzc/pIGorlymYNzDBOqkSlVRe6MkgBsW/hW1tpC/HDJ2fjG7XnjakzfLEuvdmux0Mjs6jHq4UOA== - -electron-to-chromium@^1.5.73: - version "1.5.76" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.76.tgz#db20295c5061b68f07c8ea4dfcbd701485d94a3d" - integrity sha512-CjVQyG7n7Sr+eBXE86HIulnL5N8xZY1sgmOPGuq/F0Rr0FJq63lg0kEtOIDfZBk44FnDLf6FUJ+dsJcuiUDdDQ== +electron-to-chromium@^1.5.227: + version "1.5.235" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.235.tgz#778dac70537ed9e9e0abf25a49cc94b5a23aaa0e" + integrity sha512-i/7ntLFwOdoHY7sgjlTIDo4Sl8EdoTjWIaKinYOVfC6bOp71bmwenyZthWHcasxgHDNWbWxvG9M3Ia116zIaYQ== electron-updater@^6.6.2: version "6.6.2" @@ -5017,9 +4701,9 @@ encoding@^0.1.13: iconv-lite "^0.6.2" end-of-stream@^1.1.0: - version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + version "1.4.5" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.5.tgz#7344d711dea40e0b74abc2ed49778743ccedb08c" + integrity sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg== dependencies: once "^1.4.0" @@ -5028,6 +4712,11 @@ entities@^4.4.0: resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== +entities@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/entities/-/entities-6.0.1.tgz#c28c34a43379ca7f61d074130b2f5f7020a30694" + integrity sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g== + env-paths@^2.2.0, env-paths@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" @@ -5039,137 +4728,33 @@ err-code@^2.0.2: integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + version "1.3.4" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.4.tgz#b3a8d8bb6f92eecc1629e3e27d3c8607a8a32414" + integrity sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ== dependencies: is-arrayish "^0.2.1" -es-abstract@^1.17.5, es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.2, es-abstract@^1.23.3: - version "1.23.3" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.3.tgz#8f0c5a35cd215312573c5a27c87dfd6c881a0aa0" - integrity sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A== - dependencies: - array-buffer-byte-length "^1.0.1" - arraybuffer.prototype.slice "^1.0.3" - available-typed-arrays "^1.0.7" - call-bind "^1.0.7" - data-view-buffer "^1.0.1" - data-view-byte-length "^1.0.1" - data-view-byte-offset "^1.0.0" - es-define-property "^1.0.0" - es-errors "^1.3.0" - es-object-atoms "^1.0.0" - es-set-tostringtag "^2.0.3" - es-to-primitive "^1.2.1" - function.prototype.name "^1.1.6" - get-intrinsic "^1.2.4" - get-symbol-description "^1.0.2" - globalthis "^1.0.3" - gopd "^1.0.1" - has-property-descriptors "^1.0.2" - has-proto "^1.0.3" - has-symbols "^1.0.3" - hasown "^2.0.2" - internal-slot "^1.0.7" - is-array-buffer "^3.0.4" - is-callable "^1.2.7" - is-data-view "^1.0.1" - is-negative-zero "^2.0.3" - is-regex "^1.1.4" - is-shared-array-buffer "^1.0.3" - is-string "^1.0.7" - is-typed-array "^1.1.13" - is-weakref "^1.0.2" - object-inspect "^1.13.1" - object-keys "^1.1.1" - object.assign "^4.1.5" - regexp.prototype.flags "^1.5.2" - safe-array-concat "^1.1.2" - safe-regex-test "^1.0.3" - string.prototype.trim "^1.2.9" - string.prototype.trimend "^1.0.8" - string.prototype.trimstart "^1.0.8" - typed-array-buffer "^1.0.2" - typed-array-byte-length "^1.0.1" - typed-array-byte-offset "^1.0.2" - typed-array-length "^1.0.6" - unbox-primitive "^1.0.2" - which-typed-array "^1.1.15" - -es-abstract@^1.23.5: - version "1.23.5" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.5.tgz#f4599a4946d57ed467515ed10e4f157289cd52fb" - integrity sha512-vlmniQ0WNPwXqA0BnmwV3Ng7HxiGlh6r5U6JcTMNx8OilcAGqVJBHJcPjqOMaczU9fRuRK5Px2BdVyPRnKMMVQ== - dependencies: - array-buffer-byte-length "^1.0.1" - arraybuffer.prototype.slice "^1.0.3" - available-typed-arrays "^1.0.7" - call-bind "^1.0.7" - data-view-buffer "^1.0.1" - data-view-byte-length "^1.0.1" - data-view-byte-offset "^1.0.0" - es-define-property "^1.0.0" - es-errors "^1.3.0" - es-object-atoms "^1.0.0" - es-set-tostringtag "^2.0.3" - es-to-primitive "^1.2.1" - function.prototype.name "^1.1.6" - get-intrinsic "^1.2.4" - get-symbol-description "^1.0.2" - globalthis "^1.0.4" - gopd "^1.0.1" - has-property-descriptors "^1.0.2" - has-proto "^1.0.3" - has-symbols "^1.0.3" - hasown "^2.0.2" - internal-slot "^1.0.7" - is-array-buffer "^3.0.4" - is-callable "^1.2.7" - is-data-view "^1.0.1" - is-negative-zero "^2.0.3" - is-regex "^1.1.4" - is-shared-array-buffer "^1.0.3" - is-string "^1.0.7" - is-typed-array "^1.1.13" - is-weakref "^1.0.2" - object-inspect "^1.13.3" - object-keys "^1.1.1" - object.assign "^4.1.5" - regexp.prototype.flags "^1.5.3" - safe-array-concat "^1.1.2" - safe-regex-test "^1.0.3" - string.prototype.trim "^1.2.9" - string.prototype.trimend "^1.0.8" - string.prototype.trimstart "^1.0.8" - typed-array-buffer "^1.0.2" - typed-array-byte-length "^1.0.1" - typed-array-byte-offset "^1.0.2" - typed-array-length "^1.0.6" - unbox-primitive "^1.0.2" - which-typed-array "^1.1.15" - -es-abstract@^1.23.6, es-abstract@^1.23.9: - version "1.23.9" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.9.tgz#5b45994b7de78dada5c1bebf1379646b32b9d606" - integrity sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA== +es-abstract@^1.17.5, es-abstract@^1.23.2, es-abstract@^1.23.3, es-abstract@^1.23.5, es-abstract@^1.23.6, es-abstract@^1.23.9, es-abstract@^1.24.0: + version "1.24.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.24.0.tgz#c44732d2beb0acc1ed60df840869e3106e7af328" + integrity sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg== dependencies: array-buffer-byte-length "^1.0.2" arraybuffer.prototype.slice "^1.0.4" available-typed-arrays "^1.0.7" call-bind "^1.0.8" - call-bound "^1.0.3" + call-bound "^1.0.4" data-view-buffer "^1.0.2" data-view-byte-length "^1.0.2" data-view-byte-offset "^1.0.1" es-define-property "^1.0.1" es-errors "^1.3.0" - es-object-atoms "^1.0.0" + es-object-atoms "^1.1.1" es-set-tostringtag "^2.1.0" es-to-primitive "^1.3.0" function.prototype.name "^1.1.8" - get-intrinsic "^1.2.7" - get-proto "^1.0.0" + get-intrinsic "^1.3.0" + get-proto "^1.0.1" get-symbol-description "^1.1.0" globalthis "^1.0.4" gopd "^1.2.0" @@ -5181,21 +4766,24 @@ es-abstract@^1.23.6, es-abstract@^1.23.9: is-array-buffer "^3.0.5" is-callable "^1.2.7" is-data-view "^1.0.2" + is-negative-zero "^2.0.3" is-regex "^1.2.1" + is-set "^2.0.3" is-shared-array-buffer "^1.0.4" is-string "^1.1.1" is-typed-array "^1.1.15" - is-weakref "^1.1.0" + is-weakref "^1.1.1" math-intrinsics "^1.1.0" - object-inspect "^1.13.3" + object-inspect "^1.13.4" object-keys "^1.1.1" object.assign "^4.1.7" own-keys "^1.0.1" - regexp.prototype.flags "^1.5.3" + regexp.prototype.flags "^1.5.4" safe-array-concat "^1.1.3" safe-push-apply "^1.0.0" safe-regex-test "^1.1.0" set-proto "^1.0.0" + stop-iteration-iterator "^1.1.0" string.prototype.trim "^1.2.10" string.prototype.trimend "^1.0.9" string.prototype.trimstart "^1.0.8" @@ -5204,21 +4792,14 @@ es-abstract@^1.23.6, es-abstract@^1.23.9: typed-array-byte-offset "^1.0.4" typed-array-length "^1.0.7" unbox-primitive "^1.1.0" - which-typed-array "^1.1.18" + which-typed-array "^1.1.19" -es-define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" - integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== - dependencies: - get-intrinsic "^1.2.4" - -es-define-property@^1.0.1: +es-define-property@^1.0.0, es-define-property@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== -es-errors@^1.2.1, es-errors@^1.3.0: +es-errors@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== @@ -5245,23 +4826,14 @@ es-iterator-helpers@^1.2.1: iterator.prototype "^1.1.4" safe-array-concat "^1.1.3" -es-object-atoms@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941" - integrity sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw== +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== dependencies: es-errors "^1.3.0" -es-set-tostringtag@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz#8bb60f0a440c2e4281962428438d58545af39777" - integrity sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ== - dependencies: - get-intrinsic "^1.2.4" - has-tostringtag "^1.0.2" - hasown "^2.0.1" - -es-set-tostringtag@^2.1.0: +es-set-tostringtag@^2.0.3, es-set-tostringtag@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== @@ -5271,21 +4843,12 @@ es-set-tostringtag@^2.1.0: has-tostringtag "^1.0.2" hasown "^2.0.2" -es-shim-unscopables@^1.0.0, es-shim-unscopables@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz#1f6942e71ecc7835ed1c8a83006d8771a63a3763" - integrity sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw== +es-shim-unscopables@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz#438df35520dac5d105f3943d927549ea3b00f4b5" + integrity sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw== dependencies: - hasown "^2.0.0" - -es-to-primitive@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" - integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== - dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" + hasown "^2.0.2" es-to-primitive@^1.3.0: version "1.3.0" @@ -5367,20 +4930,15 @@ escalade@^3.1.1, escalade@^3.2.0: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== - escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== eslint-config-prettier@^9.1.0: - version "9.1.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz#31af3d94578645966c082fcb71a5846d3c94867f" - integrity sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw== + version "9.1.2" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-9.1.2.tgz#90deb4fa0259592df774b600dbd1d2249a78ce91" + integrity sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ== eslint-plugin-jsx-a11y@^6.10.2: version "6.10.2" @@ -5404,12 +4962,12 @@ eslint-plugin-jsx-a11y@^6.10.2: string.prototype.includes "^2.0.1" eslint-plugin-prettier@^5.0.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz#d1c8f972d8f60e414c25465c163d16f209411f95" - integrity sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw== + version "5.5.4" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz#9d61c4ea11de5af704d4edf108c82ccfa7f2e61c" + integrity sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg== dependencies: prettier-linter-helpers "^1.0.0" - synckit "^0.9.1" + synckit "^0.11.7" eslint-plugin-react-hooks@^4.6.0: version "4.6.2" @@ -5417,9 +4975,9 @@ eslint-plugin-react-hooks@^4.6.0: integrity sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ== eslint-plugin-react@^7.37.4: - version "7.37.4" - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.37.4.tgz#1b6c80b6175b6ae4b26055ae4d55d04c414c7181" - integrity sha512-BGP0jRmfYyvOyvMoRX/uoUeW+GqNj9y16bPQzqAHf3AYII/tDs+jMN0dBVkl88/OZwNGwrVFxE7riHsXVfy/LQ== + version "7.37.5" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz#2975511472bdda1b272b34d779335c9b0e877065" + integrity sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA== dependencies: array-includes "^3.1.8" array.prototype.findlast "^1.2.5" @@ -5431,7 +4989,7 @@ eslint-plugin-react@^7.37.4: hasown "^2.0.2" jsx-ast-utils "^2.4.1 || ^3.0.0" minimatch "^3.1.2" - object.entries "^1.1.8" + object.entries "^1.1.9" object.fromentries "^2.0.8" object.values "^1.2.1" prop-types "^15.8.1" @@ -5448,7 +5006,7 @@ eslint-scope@^7.2.2: esrecurse "^4.3.0" estraverse "^5.2.0" -eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: +eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: version "3.4.3" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== @@ -5541,9 +5099,9 @@ event-target-shim@^5.0.0: integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== exponential-backoff@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.1.tgz#64ac7526fe341ab18a39016cd22c787d01e00bf6" - integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw== + version "3.1.3" + resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.3.tgz#51cf92c1c0493c766053f9d3abee4434c244d2f6" + integrity sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA== extract-zip@^2.0.1: version "2.0.1" @@ -5572,15 +5130,15 @@ fast-diff@^1.1.2: integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== fast-glob@^3.2.9: - version "3.3.2" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" - integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + version "3.3.3" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" + integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" glob-parent "^5.1.2" merge2 "^1.3.0" - micromatch "^4.0.4" + micromatch "^4.0.8" fast-json-stable-stringify@^2.0.0: version "2.1.0" @@ -5592,17 +5150,22 @@ fast-levenshtein@^2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== -fast-xml-parser@4.4.1: - version "4.4.1" - resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz#86dbf3f18edf8739326447bcaac31b4ae7f6514f" - integrity sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw== +fast-uri@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.0.tgz#66eecff6c764c0df9b762e62ca7edcfb53b4edfa" + integrity sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA== + +fast-xml-parser@5.2.5: + version "5.2.5" + resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz#4809fdfb1310494e341098c25cb1341a01a9144a" + integrity sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ== dependencies: - strnum "^1.0.5" + strnum "^2.1.0" fastq@^1.6.0: - version "1.17.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" - integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== + version "1.19.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.1.tgz#d50eaba803c8846a883c16492821ebcd2cda55f5" + integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ== dependencies: reusify "^1.0.4" @@ -5684,40 +5247,31 @@ flat-cache@^3.0.4: rimraf "^3.0.2" flatted@^3.2.9: - version "3.3.1" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" - integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== + version "3.3.3" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" + integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== follow-redirects@^1.15.6: - version "1.15.9" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" - integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== + version "1.15.11" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.11.tgz#777d73d72a92f8ec4d2e410eb47352a56b8e8340" + integrity sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ== -for-each@^0.3.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" - integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== +for-each@^0.3.3, for-each@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.5.tgz#d650688027826920feeb0af747ee7b9421a41d47" + integrity sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg== dependencies: - is-callable "^1.1.3" + is-callable "^1.2.7" foreground-child@^3.1.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.0.tgz#0ac8644c06e431439f8561db8ecf29a7b5519c77" - integrity sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg== + version "3.3.1" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.1.tgz#32e8e9ed1b68a3497befb9ac2b6adf92a638576f" + integrity sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw== dependencies: - cross-spawn "^7.0.0" + cross-spawn "^7.0.6" signal-exit "^4.0.1" -form-data@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" - integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - -form-data@^4.0.4: +form-data@^4.0.0, form-data@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.4.tgz#784cdcce0669a9d68e94d11ac4eea98088edd2c4" integrity sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow== @@ -5736,12 +5290,12 @@ formdata-polyfill@^4.0.10: fetch-blob "^3.1.2" framer-motion@^12.15.0: - version "12.15.0" - resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-12.15.0.tgz#6892283fc7967b071f537d6d160ab49e3d5e73ae" - integrity sha512-XKg/LnKExdLGugZrDILV7jZjI599785lDIJZLxMiiIFidCsy0a4R2ZEf+Izm67zyOuJgQYTHOmodi7igQsw3vg== + version "12.23.24" + resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-12.23.24.tgz#4895b67e880bd2b1089e61fbaa32ae802fc24b8c" + integrity sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w== dependencies: - motion-dom "^12.15.0" - motion-utils "^12.12.1" + motion-dom "^12.23.23" + motion-utils "^12.23.6" tslib "^2.4.0" fs-extra@^10.0.0, fs-extra@^10.1.0: @@ -5754,9 +5308,9 @@ fs-extra@^10.0.0, fs-extra@^10.1.0: universalify "^2.0.0" fs-extra@^11.1.1: - version "11.2.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.2.0.tgz#e70e17dfad64232287d01929399e0ea7c86b0e5b" - integrity sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw== + version "11.3.2" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.2.tgz#c838aeddc6f4a8c74dd15f85e11fe5511bfe02a4" + integrity sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A== dependencies: graceful-fs "^4.2.0" jsonfile "^6.0.1" @@ -5803,17 +5357,7 @@ function-bind@^1.1.2: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== -function.prototype.name@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" - integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - functions-have-names "^1.2.3" - -function.prototype.name@^1.1.8: +function.prototype.name@^1.1.6, function.prototype.name@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.8.tgz#e68e1df7b259a5c949eeef95cdbde53edffabb78" integrity sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q== @@ -5830,6 +5374,11 @@ functions-have-names@^1.2.3: resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== +generator-function@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/generator-function/-/generator-function-2.0.1.tgz#0e75dd410d1243687a0ba2e951b94eedb8f737a2" + integrity sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g== + gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -5840,44 +5389,17 @@ get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" - integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== +get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.2.7, get-intrinsic@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== dependencies: - es-errors "^1.3.0" - function-bind "^1.1.2" - has-proto "^1.0.1" - has-symbols "^1.0.3" - hasown "^2.0.0" - -get-intrinsic@^1.2.5, get-intrinsic@^1.2.6: - version "1.2.6" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.6.tgz#43dd3dd0e7b49b82b2dfcad10dc824bf7fc265d5" - integrity sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA== - dependencies: - call-bind-apply-helpers "^1.0.1" - dunder-proto "^1.0.0" + call-bind-apply-helpers "^1.0.2" es-define-property "^1.0.1" es-errors "^1.3.0" - es-object-atoms "^1.0.0" + es-object-atoms "^1.1.1" function-bind "^1.1.2" - gopd "^1.2.0" - has-symbols "^1.1.0" - hasown "^2.0.2" - math-intrinsics "^1.0.0" - -get-intrinsic@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.7.tgz#dcfcb33d3272e15f445d15124bc0a216189b9044" - integrity sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA== - dependencies: - call-bind-apply-helpers "^1.0.1" - es-define-property "^1.0.1" - es-errors "^1.3.0" - es-object-atoms "^1.0.0" - function-bind "^1.1.2" - get-proto "^1.0.0" + get-proto "^1.0.1" gopd "^1.2.0" has-symbols "^1.1.0" hasown "^2.0.2" @@ -5908,15 +5430,6 @@ get-stream@^5.1.0: dependencies: pump "^3.0.0" -get-symbol-description@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz#533744d5aa20aca4e079c8e5daf7fd44202821f5" - integrity sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg== - dependencies: - call-bind "^1.0.5" - es-errors "^1.3.0" - get-intrinsic "^1.2.4" - get-symbol-description@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.1.0.tgz#7bdd54e0befe8ffc9f3b4e203220d9f1e881b6ee" @@ -5949,7 +5462,7 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" -glob@^10.3.12, glob@^10.3.7: +glob@^10.3.12: version "10.4.5" resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== @@ -6003,11 +5516,6 @@ global-directory@^4.0.1: dependencies: ini "4.1.1" -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - globals@^13.19.0: version "13.24.0" resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" @@ -6015,7 +5523,7 @@ globals@^13.19.0: dependencies: type-fest "^0.20.2" -globalthis@^1.0.1, globalthis@^1.0.3, globalthis@^1.0.4: +globalthis@^1.0.1, globalthis@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== @@ -6035,14 +5543,7 @@ globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" -gopd@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" - integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== - dependencies: - get-intrinsic "^1.1.3" - -gopd@^1.2.0: +gopd@^1.0.1, gopd@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== @@ -6074,15 +5575,10 @@ graphemer@^1.4.0: resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== -has-bigints@^1.0.1, has-bigints@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" - integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== +has-bigints@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.1.0.tgz#28607e965ac967e03cd2a2c70a2636a1edad49fe" + integrity sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg== has-flag@^4.0.0: version "4.0.0" @@ -6096,11 +5592,6 @@ has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: dependencies: es-define-property "^1.0.0" -has-proto@^1.0.1, has-proto@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" - integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== - has-proto@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.2.0.tgz#5de5a6eabd95fdffd9818b43055e8065e39fe9d5" @@ -6108,24 +5599,19 @@ has-proto@^1.2.0: dependencies: dunder-proto "^1.0.0" -has-symbols@^1.0.2, has-symbols@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== - -has-symbols@^1.1.0: +has-symbols@^1.0.3, has-symbols@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== -has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: +has-tostringtag@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== dependencies: has-symbols "^1.0.3" -hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: +hasown@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== @@ -6161,9 +5647,9 @@ html-parse-stringify@^3.0.1: void-elements "3.1.0" http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" - integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== + version "4.2.0" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz#205f4db64f8562b76a4ff9235aa5279839a09dd5" + integrity sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ== http-cookie-agent@^6.0.8: version "6.0.8" @@ -6206,11 +5692,11 @@ https-proxy-agent@^5.0.0: debug "4" https-proxy-agent@^7.0.0, https-proxy-agent@^7.0.5: - version "7.0.5" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz#9e8b5013873299e11fab6fd548405da2d6c602b2" - integrity sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw== + version "7.0.6" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz#da8dfeac7da130b05c2ba4b59c9b6cd66611a6b9" + integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw== dependencies: - agent-base "^7.0.2" + agent-base "^7.1.2" debug "4" humanize-ms@^1.2.1: @@ -6231,16 +5717,16 @@ husky@^9.1.7: integrity sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA== i18next-browser-languagedetector@^7.2.1: - version "7.2.1" - resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.2.1.tgz#1968196d437b4c8db847410c7c33554f6c448f6f" - integrity sha512-h/pM34bcH6tbz8WgGXcmWauNpQupCGr25XPp9cZwZInR9XHSjIFDYp1SIok7zSPsTOMxdvuLyu86V+g2Kycnfw== + version "7.2.2" + resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.2.2.tgz#748e7dc192847613911d8a79d9d9a6c2d266133e" + integrity sha512-6b7r75uIJDWCcCflmbof+sJ94k9UQO4X0YR62oUfqGI/GjCLVzlCwu8TFdRZIqVLzWbzNcmkmhfqKEr4TLz4HQ== dependencies: "@babel/runtime" "^7.23.2" i18next@^23.11.2: - version "23.15.1" - resolved "https://registry.yarnpkg.com/i18next/-/i18next-23.15.1.tgz#c50de337bf12ca5195e697cc0fbe5f32304871d9" - integrity sha512-wB4abZ3uK7EWodYisHl/asf8UYEhrI/vj/8aoSsrj/ZDxj4/UXPOa1KvFt1Fq5hkUHquNqwFlDprmjZ8iySgYA== + version "23.16.8" + resolved "https://registry.yarnpkg.com/i18next/-/i18next-23.16.8.tgz#3ae1373d344c2393f465556f394aba5a9233b93a" + integrity sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg== dependencies: "@babel/runtime" "^7.23.2" @@ -6270,27 +5756,27 @@ ignore@^5.2.0, ignore@^5.3.1: integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== immer@^10.0.3: - version "10.1.1" - resolved "https://registry.yarnpkg.com/immer/-/immer-10.1.1.tgz#206f344ea372d8ea176891545ee53ccc062db7bc" - integrity sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw== + version "10.1.3" + resolved "https://registry.yarnpkg.com/immer/-/immer-10.1.3.tgz#e38a0b97db59949d31d9b381b04c2e441b1c3747" + integrity sha512-tmjF/k8QDKydUlm3mZU+tjM6zeq9/fFpPqH9SzWmBnVVKsPBg/V66qsMwb3/Bo90cgUN+ghdVBess+hPsxUyRw== -immutable@^4.0.0: - version "4.3.7" - resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.7.tgz#c70145fc90d89fb02021e65c84eb0226e4e5a381" - integrity sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw== +immutable@^5.0.2: + version "5.1.4" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-5.1.4.tgz#e3f8c1fe7b567d56cf26698f31918c241dae8c1f" + integrity sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA== import-fresh@^3.2.1, import-fresh@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" - integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + version "3.3.1" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" + integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== dependencies: parent-module "^1.0.0" resolve-from "^4.0.0" import-meta-resolve@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz#f9db8bead9fafa61adb811db77a2bf22c5399706" - integrity sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw== + version "4.2.0" + resolved "https://registry.yarnpkg.com/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz#08cb85b5bd37ecc8eb1e0f670dc2767002d43734" + integrity sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg== imurmurhash@^0.1.4: version "0.1.4" @@ -6325,15 +5811,6 @@ ini@4.1.1: resolved "https://registry.yarnpkg.com/ini/-/ini-4.1.1.tgz#d95b3d843b1e906e56d6747d5447904ff50ce7a1" integrity sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g== -internal-slot@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802" - integrity sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g== - dependencies: - es-errors "^1.3.0" - hasown "^2.0.0" - side-channel "^1.0.4" - internal-slot@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.1.0.tgz#1eac91762947d2f7056bc838d93e13b2e9604961" @@ -6343,23 +5820,12 @@ internal-slot@^1.1.0: hasown "^2.0.2" side-channel "^1.1.0" -ip-address@^9.0.5: - version "9.0.5" - resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-9.0.5.tgz#117a960819b08780c3bd1f14ef3c1cc1d3f3ea5a" - integrity sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g== - dependencies: - jsbn "1.1.0" - sprintf-js "^1.1.3" +ip-address@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-10.0.1.tgz#a8180b783ce7788777d796286d61bce4276818ed" + integrity sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA== -is-array-buffer@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" - integrity sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.2.1" - -is-array-buffer@^3.0.5: +is-array-buffer@^3.0.4, is-array-buffer@^3.0.5: version "3.0.5" resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz#65742e1e687bd2cc666253068fd8707fe4d44280" integrity sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A== @@ -6374,23 +5840,20 @@ is-arrayish@^0.2.1: integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== is-arrayish@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" - integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + version "0.3.4" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.4.tgz#1ee5553818511915685d33bb13d31bf854e5059d" + integrity sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA== is-async-function@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.0.0.tgz#8e4418efd3e5d3a6ebb0164c05ef5afb69aa9646" - integrity sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA== + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.1.1.tgz#3e69018c8e04e73b738793d020bfe884b9fd3523" + integrity sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ== dependencies: - has-tostringtag "^1.0.0" - -is-bigint@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" - integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== - dependencies: - has-bigints "^1.0.1" + async-function "^1.0.0" + call-bound "^1.0.3" + get-proto "^1.0.1" + has-tostringtag "^1.0.2" + safe-regex-test "^1.1.0" is-bigint@^1.1.0: version "1.1.0" @@ -6399,20 +5862,12 @@ is-bigint@^1.1.0: dependencies: has-bigints "^1.0.2" -is-boolean-object@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" - integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== +is-boolean-object@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.2.2.tgz#7067f47709809a393c71ff5bb3e135d8a9215d9e" + integrity sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A== dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-boolean-object@^1.2.0, is-boolean-object@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.2.1.tgz#c20d0c654be05da4fbc23c562635c019e93daf89" - integrity sha512-l9qO6eFlUETHtuihLcYOaLKByJ1f+N4kthcU9YjHy3N+B3hWv0y/2Nd0mu/7lTFnRQHTrSdXF50HQ3bl5fEnng== - dependencies: - call-bound "^1.0.2" + call-bound "^1.0.3" has-tostringtag "^1.0.2" is-buffer@^2.0.5: @@ -6420,7 +5875,7 @@ is-buffer@^2.0.5: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== -is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: +is-callable@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== @@ -6433,20 +5888,13 @@ is-ci@^3.0.0: ci-info "^3.2.0" is-core-module@^2.13.0: - version "2.15.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.1.tgz#a7363a25bee942fefab0de13bf6aa372c82dcc37" - integrity sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ== + version "2.16.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== dependencies: hasown "^2.0.2" -is-data-view@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.1.tgz#4b4d3a511b70f3dc26d42c03ca9ca515d847759f" - integrity sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w== - dependencies: - is-typed-array "^1.1.13" - -is-data-view@^1.0.2: +is-data-view@^1.0.1, is-data-view@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.2.tgz#bae0a41b9688986c2188dda6657e56b8f9e63b8e" integrity sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw== @@ -6455,13 +5903,6 @@ is-data-view@^1.0.2: get-intrinsic "^1.2.6" is-typed-array "^1.1.13" -is-date-object@^1.0.1: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" - integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== - dependencies: - has-tostringtag "^1.0.0" - is-date-object@^1.0.5, is-date-object@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.1.0.tgz#ad85541996fc7aa8b2729701d27b7319f95d82f7" @@ -6476,11 +5917,11 @@ is-extglob@^2.1.1: integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== is-finalizationregistry@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.1.0.tgz#d74a7d0c5f3578e34a20729e69202e578d495dc2" - integrity sha512-qfMdqbAQEwBw78ZyReKnlA8ezmPdb9BemzIIip/JkjaZUhitfXDkkr+3QTboW0JrSXT1QWyYShpvnNHGZ4c4yA== + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz#eefdcdc6c94ddd0674d9c85887bf93f944a97c90" + integrity sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg== dependencies: - call-bind "^1.0.7" + call-bound "^1.0.3" is-fullwidth-code-point@^3.0.0: version "3.0.0" @@ -6488,11 +5929,15 @@ is-fullwidth-code-point@^3.0.0: integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== is-generator-function@^1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" - integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.1.2.tgz#ae3b61e3d5ea4e4839b90bad22b02335051a17d5" + integrity sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA== dependencies: - has-tostringtag "^1.0.0" + call-bound "^1.0.4" + generator-function "^2.0.0" + get-proto "^1.0.1" + has-tostringtag "^1.0.2" + safe-regex-test "^1.1.0" is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: version "4.0.3" @@ -6521,21 +5966,6 @@ is-negative-zero@^2.0.3: resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== -is-number-object@^1.0.4: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" - integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== - dependencies: - has-tostringtag "^1.0.0" - -is-number-object@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.1.0.tgz#5a867e9ecc3d294dda740d9f127835857af7eb05" - integrity sha512-KVSZV0Dunv9DTPkhXwcZ3Q+tUc9TsaE1ZwX5J2WMvsSGS6Md8TFPun5uwh0yRdrNerI6vf/tbJxqSx4c1ZI1Lw== - dependencies: - call-bind "^1.0.7" - has-tostringtag "^1.0.2" - is-number-object@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.1.1.tgz#144b21e95a1bc148205dcc2814a9134ec41b2541" @@ -6564,14 +5994,6 @@ is-potential-custom-element-name@^1.0.1: resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== -is-regex@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" - integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - is-regex@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.2.1.tgz#76d70a3ed10ef9be48eb577887d74205bf0cad22" @@ -6587,13 +6009,6 @@ is-set@^2.0.3: resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== -is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz#1237f1cba059cdb62431d378dcc37d9680181688" - integrity sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg== - dependencies: - call-bind "^1.0.7" - is-shared-array-buffer@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz#9b67844bd9b7f246ba0708c3a93e34269c774f6f" @@ -6601,21 +6016,6 @@ is-shared-array-buffer@^1.0.4: dependencies: call-bound "^1.0.3" -is-string@^1.0.5, is-string@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" - integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== - dependencies: - has-tostringtag "^1.0.0" - -is-string@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.1.0.tgz#8cb83c5d57311bf8058bc6c8db294711641da45d" - integrity sha512-PlfzajuF9vSo5wErv3MJAKD/nqf9ngAs1NFQYm16nUYFO2IzxJ2hcm+IOCg+EEopdykNNUhVq5cz35cAUxU8+g== - dependencies: - call-bind "^1.0.7" - has-tostringtag "^1.0.2" - is-string@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.1.1.tgz#92ea3f3d5c5b6e039ca8677e5ac8d07ea773cbb9" @@ -6624,14 +6024,7 @@ is-string@^1.1.1: call-bound "^1.0.3" has-tostringtag "^1.0.2" -is-symbol@^1.0.2, is-symbol@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" - integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== - dependencies: - has-symbols "^1.0.2" - -is-symbol@^1.0.4, is-symbol@^1.1.0, is-symbol@^1.1.1: +is-symbol@^1.0.4, is-symbol@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.1.1.tgz#f47761279f532e2b05a7024a7506dbbedacd0634" integrity sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w== @@ -6647,14 +6040,7 @@ is-text-path@^2.0.0: dependencies: text-extensions "^2.0.0" -is-typed-array@^1.1.13: - version "1.1.13" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.13.tgz#d6c5ca56df62334959322d7d7dd1cca50debe229" - integrity sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw== - dependencies: - which-typed-array "^1.1.14" - -is-typed-array@^1.1.14, is-typed-array@^1.1.15: +is-typed-array@^1.1.13, is-typed-array@^1.1.14, is-typed-array@^1.1.15: version "1.1.15" resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.15.tgz#4bfb4a45b61cee83a5a46fba778e4e8d59c0ce0b" integrity sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ== @@ -6671,27 +6057,20 @@ is-weakmap@^2.0.2: resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== -is-weakref@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" - integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== +is-weakref@^1.0.2, is-weakref@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.1.1.tgz#eea430182be8d64174bd96bffbc46f21bf3f9293" + integrity sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew== dependencies: - call-bind "^1.0.2" - -is-weakref@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.1.0.tgz#47e3472ae95a63fa9cf25660bcf0c181c39770ef" - integrity sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q== - dependencies: - call-bound "^1.0.2" + call-bound "^1.0.3" is-weakset@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.3.tgz#e801519df8c0c43e12ff2834eead84ec9e624007" - integrity sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ== + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.4.tgz#c9f5deb0bc1906c6d6f1027f284ddf459249daca" + integrity sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ== dependencies: - call-bind "^1.0.7" - get-intrinsic "^1.2.4" + call-bound "^1.0.3" + get-intrinsic "^1.2.6" isarray@^2.0.5: version "2.0.5" @@ -6704,9 +6083,9 @@ isbinaryfile@^4.0.8: integrity sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw== isbinaryfile@^5.0.0: - version "5.0.2" - resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-5.0.2.tgz#fe6e4dfe2e34e947ffa240c113444876ba393ae0" - integrity sha512-GvcjojwonMjWbTkfMpnVHVqXW/wKMYDfEpY94/8zy8HFMOqb/VL6oeONq9v87q4ttVlaTLnGXnJD4B5B1OTGIg== + version "5.0.6" + resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-5.0.6.tgz#01eac28867aeffaebaee7eaf21d1dd3a67d7c0c7" + integrity sha512-I+NmIfBHUl+r2wcDd6JwE9yWje/PIVY/R5/CmV8dXLZd5K+L9X2klAOwfAHNnondLXkbHyTAleQAWonpTJBTtw== isexe@^2.0.0: version "2.0.0" @@ -6735,19 +6114,18 @@ jackspeak@^3.1.2: "@pkgjs/parseargs" "^0.11.0" jake@^10.8.5: - version "10.9.2" - resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f" - integrity sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA== + version "10.9.4" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.4.tgz#d626da108c63d5cfb00ab5c25fadc7e0084af8e6" + integrity sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA== dependencies: - async "^3.2.3" - chalk "^4.0.2" + async "^3.2.6" filelist "^1.0.4" - minimatch "^3.1.2" + picocolors "^1.1.1" -jiti@^1.19.1: - version "1.21.6" - resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.6.tgz#6c7f7398dd4b3142767f9a168af2f317a428d268" - integrity sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w== +jiti@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.6.1.tgz#178ef2fc9a1a594248c20627cd820187a4d78d92" + integrity sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ== "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" @@ -6761,11 +6139,6 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" -jsbn@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" - integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A== - jsdom@^24.0.0: version "24.1.3" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-24.1.3.tgz#88e4a07cb9dd21067514a619e9f17b090a394a9f" @@ -6793,11 +6166,6 @@ jsdom@^24.0.0: ws "^8.18.0" xml-name-validator "^5.0.0" -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" - integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== - jsesc@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" @@ -6846,9 +6214,9 @@ jsonfile@^4.0.0: graceful-fs "^4.1.6" jsonfile@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" - integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + version "6.2.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.2.0.tgz#7c265bd1b65de6977478300087c99f1c84383f62" + integrity sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg== dependencies: universalify "^2.0.0" optionalDependencies: @@ -6886,11 +6254,11 @@ jsonwebtoken@^9.0.2: object.values "^1.1.6" jwa@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" - integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== + version "1.4.2" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.2.tgz#16011ac6db48de7b102777e57897901520eec7b9" + integrity sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw== dependencies: - buffer-equal-constant-time "1.0.1" + buffer-equal-constant-time "^1.0.1" ecdsa-sig-formatter "1.0.11" safe-buffer "^5.0.1" @@ -7105,7 +6473,7 @@ lowercase-keys@^2.0.0: resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== -lru-cache@^10.2.0: +lru-cache@^10.2.0, lru-cache@^10.4.3: version "10.4.3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== @@ -7135,11 +6503,11 @@ lucide-react@^0.544.0: integrity sha512-t5tS44bqd825zAW45UQxpG2CvcC4urOwn2TrwSH8u+MjeE+1NnWl6QqeQ/6NdjMqdOygyiT9p3Ev0p1NJykxjw== magic-string@^0.30.17: - version "0.30.17" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453" - integrity sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA== + version "0.30.19" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.19.tgz#cebe9f104e565602e5d2098c5f2e79a77cc86da9" + integrity sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw== dependencies: - "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/sourcemap-codec" "^1.5.5" magnet-uri@^7.0.7: version "7.0.7" @@ -7196,11 +6564,6 @@ matcher@^3.0.0: dependencies: escape-string-regexp "^4.0.0" -math-intrinsics@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.0.0.tgz#4e04bf87c85aa51e90d078dac2252b4eb5260817" - integrity sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA== - math-intrinsics@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" @@ -7226,7 +6589,7 @@ merge2@^1.3.0, merge2@^1.4.1: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== -micromatch@^4.0.4: +micromatch@^4.0.5, micromatch@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== @@ -7267,11 +6630,11 @@ mimic-response@^3.1.0: integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== minimatch@^10.0.0: - version "10.0.1" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.0.1.tgz#ce0521856b453c86e25f2c4c0d03e6ff7ddc440b" - integrity sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ== + version "10.0.3" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.0.3.tgz#cf7a0314a16c4d9ab73a7730a0e8e3c3502d47aa" + integrity sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw== dependencies: - brace-expansion "^2.0.1" + "@isaacs/brace-expansion" "^5.0.0" minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" @@ -7363,13 +6726,12 @@ minizlib@^2.1.1, minizlib@^2.1.2: minipass "^3.0.0" yallist "^4.0.0" -minizlib@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-3.0.1.tgz#46d5329d1eb3c83924eff1d3b858ca0a31581012" - integrity sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg== +minizlib@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-3.1.0.tgz#6ad76c3a8f10227c9b51d1c9ac8e30b27f5a251c" + integrity sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw== dependencies: - minipass "^7.0.4" - rimraf "^5.0.5" + minipass "^7.1.2" mkdirp@^0.5.1: version "0.5.6" @@ -7383,27 +6745,22 @@ mkdirp@^1.0.3, mkdirp@^1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -mkdirp@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50" - integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg== - module-error@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/module-error/-/module-error-1.0.2.tgz#8d1a48897ca883f47a45816d4fb3e3c6ba404d86" integrity sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA== -motion-dom@^12.15.0: - version "12.15.0" - resolved "https://registry.yarnpkg.com/motion-dom/-/motion-dom-12.15.0.tgz#eca7c9d8c28976b8c920f175f92d5288f5a17785" - integrity sha512-D2ldJgor+2vdcrDtKJw48k3OddXiZN1dDLLWrS8kiHzQdYVruh0IoTwbJBslrnTXIPgFED7PBN2Zbwl7rNqnhA== +motion-dom@^12.23.23: + version "12.23.23" + resolved "https://registry.yarnpkg.com/motion-dom/-/motion-dom-12.23.23.tgz#8f874333ea1a04ee3a89eb928f518b463d589e0e" + integrity sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA== dependencies: - motion-utils "^12.12.1" + motion-utils "^12.23.6" -motion-utils@^12.12.1: - version "12.12.1" - resolved "https://registry.yarnpkg.com/motion-utils/-/motion-utils-12.12.1.tgz#63e28751325cb9d1cd684f3c273a570022b0010e" - integrity sha512-f9qiqUHm7hWSLlNW8gS9pisnsN7CRFRD58vNjptKdsqFLpkVnX00TNeD6Q0d27V9KzT7ySFyK1TZ/DShfVOv6w== +motion-utils@^12.23.6: + version "12.23.6" + resolved "https://registry.yarnpkg.com/motion-utils/-/motion-utils-12.23.6.tgz#fafef80b4ea85122dd0d6c599a0c63d72881f312" + integrity sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ== ms@^2.0.0, ms@^2.1.1, ms@^2.1.3: version "2.1.3" @@ -7426,9 +6783,9 @@ natural-compare@^1.4.0: integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== negotiator@^0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" - integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + version "0.6.4" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.4.tgz#777948e2452651c570b712dd01c23e262713fff7" + integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w== no-case@^3.0.4: version "3.0.4" @@ -7439,9 +6796,9 @@ no-case@^3.0.4: tslib "^2.0.3" node-abi@^3.45.0: - version "3.68.0" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.68.0.tgz#8f37fb02ecf4f43ebe694090dcb52e0c4cc4ba25" - integrity sha512-7vbj10trelExNjFSBm5kTvZXXa7pZyKWx9RCKIyqe6I9Ev3IzGpQoqBP3a+cOdxY+pWj6VkP28n/2wWysBHD/A== + version "3.78.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.78.0.tgz#fd0ecbd0aa89857b98da06bd3909194abb0821ba" + integrity sha512-E2wEyrgX/CqvicaQYU3Ze1PFGjc4QYPGsjUrlYkqAE0WjHEZwgOsGMPMzkMse4LjJbDmaEuDX3CM036j5K2DSQ== dependencies: semver "^7.3.5" @@ -7450,10 +6807,15 @@ node-addon-api@^1.6.3: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.7.2.tgz#3df30b95720b53c24e59948b49532b662444f54d" integrity sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg== +node-addon-api@^7.0.0: + version "7.1.1" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558" + integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ== + node-api-version@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/node-api-version/-/node-api-version-0.2.0.tgz#5177441da2b1046a4d4547ab9e0972eed7b1ac1d" - integrity sha512-fthTTsi8CxaBXMaBAD7ST2uylwvsnYxh2PfaScwpMhos6KlSFajXQPcM4ogNE1q2s3Lbz9GCGqeIHC+C6OZnKg== + version "0.2.1" + resolved "https://registry.yarnpkg.com/node-api-version/-/node-api-version-0.2.1.tgz#19bad54f6d65628cbee4e607a325e4488ace2de9" + integrity sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q== dependencies: semver "^7.3.5" @@ -7476,15 +6838,10 @@ node-gyp-build@^4.3.0: resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.4.tgz#8a70ee85464ae52327772a90d66c6077a900cfc8" integrity sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ== -node-releases@^2.0.18: - version "2.0.18" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" - integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== - -node-releases@^2.0.19: - version "2.0.19" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" - integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== +node-releases@^2.0.21: + version "2.0.23" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.23.tgz#2ecf3d7ba571ece05c67c77e5b7b1b6fb9e18cea" + integrity sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg== nopt@^6.0.0: version "6.0.0" @@ -7499,24 +6856,19 @@ normalize-url@^6.0.1: integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== nwsapi@^2.2.12: - version "2.2.13" - resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.13.tgz#e56b4e98960e7a040e5474536587e599c4ff4655" - integrity sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ== + version "2.2.22" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.22.tgz#109f9530cda6c156d6a713cdf5939e9f0de98b9d" + integrity sha512-ujSMe1OWVn55euT1ihwCI1ZcAaAU3nxUiDwfDQldc51ZXaB9m2AyOn6/jh1BLe2t/G8xd6uKG1UBF2aZJeg2SQ== object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== -object-inspect@^1.13.1: - version "1.13.2" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff" - integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g== - -object-inspect@^1.13.3: - version "1.13.3" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.3.tgz#f14c183de51130243d6d18ae149375ff50ea488a" - integrity sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA== +object-inspect@^1.13.3, object-inspect@^1.13.4: + version "1.13.4" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" + integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== object-keys@^1.1.1: version "1.1.1" @@ -7530,17 +6882,7 @@ object-sizeof@^1.2.0: dependencies: buffer "^5.6.0" -object.assign@^4.1.4, object.assign@^4.1.5: - version "4.1.5" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0" - integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== - dependencies: - call-bind "^1.0.5" - define-properties "^1.2.1" - has-symbols "^1.0.3" - object-keys "^1.1.1" - -object.assign@^4.1.7: +object.assign@^4.1.4, object.assign@^4.1.7: version "4.1.7" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.7.tgz#8c14ca1a424c6a561b0bb2a22f66f5049a945d3d" integrity sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw== @@ -7552,14 +6894,15 @@ object.assign@^4.1.7: has-symbols "^1.1.0" object-keys "^1.1.1" -object.entries@^1.1.8: - version "1.1.8" - resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.8.tgz#bffe6f282e01f4d17807204a24f8edd823599c41" - integrity sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ== +object.entries@^1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.9.tgz#e4770a6a1444afb61bd39f984018b5bede25f8b3" + integrity sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw== dependencies: - call-bind "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.4" define-properties "^1.2.1" - es-object-atoms "^1.0.0" + es-object-atoms "^1.1.1" object.fromentries@^2.0.8: version "2.0.8" @@ -7571,16 +6914,7 @@ object.fromentries@^2.0.8: es-abstract "^1.23.2" es-object-atoms "^1.0.0" -object.values@^1.1.6: - version "1.2.0" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.0.tgz#65405a9d92cee68ac2d303002e0b8470a4d9ab1b" - integrity sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" - -object.values@^1.2.1: +object.values@^1.1.6, object.values@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.1.tgz#deed520a50809ff7f75a7cfd4bc64c7a038c6216" integrity sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA== @@ -7686,9 +7020,9 @@ p-map@^4.0.0: aggregate-error "^3.0.0" package-json-from-dist@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz#e501cd3094b278495eb4258d4c9f6d5ac3019f00" - integrity sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw== + version "1.0.1" + resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" + integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== parent-module@^1.0.0: version "1.0.1" @@ -7708,9 +7042,9 @@ parse-json@^5.2.0: lines-and-columns "^1.1.6" parse-torrent@^11.0.18: - version "11.0.18" - resolved "https://registry.yarnpkg.com/parse-torrent/-/parse-torrent-11.0.18.tgz#2ae7e52160fd0e59b6f1e539fb3670a71882d7a6" - integrity sha512-C1igbmTrQQuKlspAfP1wcLaOPlvtu5qi4pMdPoCCfepHmxDOk8iArJ2J1yblLx11UefZJUaKEPSxIwMdG11SuA== + version "11.0.19" + resolved "https://registry.yarnpkg.com/parse-torrent/-/parse-torrent-11.0.19.tgz#ab9ccd51277cc271da1a78da18e92105219d3fbe" + integrity sha512-T0lEkDdFVQsy0YxHIKjzDHSgt/yl57f3INs5jl7OZqAm77XDF0FgRgrv3LCKgSqsTOrMwYaF0t2761WKdvhgig== dependencies: bencode "^4.0.0" cross-fetch-ponyfill "^1.0.3" @@ -7720,11 +7054,11 @@ parse-torrent@^11.0.18: uint8-util "^2.2.5" parse5@^7.0.0, parse5@^7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" - integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== + version "7.3.0" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.3.0.tgz#d7e224fa72399c7a175099f45fc2ad024b05ec05" + integrity sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw== dependencies: - entities "^4.4.0" + entities "^6.0.0" path-exists@^4.0.0: version "4.0.0" @@ -7769,21 +7103,11 @@ pe-library@^0.4.1: resolved "https://registry.yarnpkg.com/pe-library/-/pe-library-0.4.1.tgz#e269be0340dcb13aa6949d743da7d658c3e2fbea" integrity sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw== -peek-readable@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-7.0.0.tgz#c6e4e78ec76f7005e5f6b51ffc93fdb91ede6512" - integrity sha512-nri2TO5JE3/mRryik9LlHFT53cgHfRK0Lt0BAZQXku/AW3E6XLt2GaY8siWi7dvW/m1z0ecn+J+bpDa9ZN3IsQ== - pend@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== -picocolors@^1.0.0, picocolors@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.0.tgz#5358b76a78cde483ba5cef6a9dc9671440b27d59" - integrity sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw== - picocolors@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" @@ -7809,9 +7133,9 @@ plist@3.1.0, plist@^3.0.4, plist@^3.0.5, plist@^3.1.0: xmlbuilder "^15.1.1" possible-typed-array-names@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" - integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== + version "1.1.0" + resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz#93e3582bc0e5426586d9d07b79ee40fc841de4ae" + integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg== postcss@^8.4.43: version "8.5.6" @@ -7835,9 +7159,9 @@ prettier-linter-helpers@^1.0.0: fast-diff "^1.1.2" prettier@^3.4.2: - version "3.4.2" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.4.2.tgz#a5ce1fb522a588bf2b78ca44c6e6fe5aa5a2b13f" - integrity sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ== + version "3.6.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.6.2.tgz#ccda02a1003ebbb2bfda6f83a074978f608b9393" + integrity sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ== proc-log@^2.0.1: version "2.0.1" @@ -8022,9 +7346,9 @@ prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transfor prosemirror-model "^1.21.0" prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.27.0, prosemirror-view@^1.31.0, prosemirror-view@^1.38.1, prosemirror-view@^1.39.1: - version "1.41.2" - resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.41.2.tgz#e69ad3883bfd3c9f3c9cf6da5cee940210df0b6f" - integrity sha512-PGS/jETmh+Qjmre/6vcG7SNHAKiGc4vKOJmHMPRmvcUl7ISuVtrtHmH06UDUwaim4NDJfZfVMl7U7JkMMETa6g== + version "1.41.3" + resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.41.3.tgz#753a37ebe172a3e313ad2c3d85496f9ed1b2c256" + integrity sha512-SqMiYMUQNNBP9kfPhLO8WXEk/fon47vc52FQsUiJzTBuyjKgEcoAwMyF04eQ4WZ2ArMn7+ReypYL60aKngbACQ== dependencies: prosemirror-model "^1.20.0" prosemirror-state "^1.0.0" @@ -8036,14 +7360,16 @@ proxy-from-env@^1.1.0: integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== psl@^1.1.33: - version "1.9.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" - integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== + version "1.15.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.15.0.tgz#bdace31896f1d97cec6a79e8224898ce93d974c6" + integrity sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w== + dependencies: + punycode "^2.3.1" pump@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.2.tgz#836f3edd6bc2ee599256c924ffe0d88573ddcbf8" - integrity sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw== + version "3.0.3" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.3.tgz#151d979f1a29668dc0025ec589a455b53282268d" + integrity sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA== dependencies: end-of-stream "^1.1.0" once "^1.3.1" @@ -8084,17 +7410,17 @@ rc-resize-observer@^1.0.0: resize-observer-polyfill "^1.5.1" rc-util@^5.36.0, rc-util@^5.44.1: - version "5.44.2" - resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.44.2.tgz#6bc5db0e96ebdb515eb5977a7371887e5413a6f8" - integrity sha512-uGSk3hpPBLa3/0QAcKhCjgl4SFnhQCJDLvvpoLdbR6KgDuXrujG+dQaUeUvBJr2ZWak1O/9n+cYbJiWmmk95EQ== + version "5.44.4" + resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.44.4.tgz#89ee9037683cca01cd60f1a6bbda761457dd6ba5" + integrity sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w== dependencies: "@babel/runtime" "^7.18.3" react-is "^18.2.0" rc-virtual-list@^3.18.3: - version "3.18.6" - resolved "https://registry.yarnpkg.com/rc-virtual-list/-/rc-virtual-list-3.18.6.tgz#87898dbb22563b3c58ca7fffcc1c77e32044a824" - integrity sha512-TQ5SsutL3McvWmmxqQtMIbfeoE3dGjJrRSfKekgby7WQMpPIFvv4ghytp5Z0s3D8Nik9i9YNOCqHBfk86AwgAA== + version "3.19.2" + resolved "https://registry.yarnpkg.com/rc-virtual-list/-/rc-virtual-list-3.19.2.tgz#1dd2d782c9a3ccbe537bb873447d73f83af8de0f" + integrity sha512-Ys6NcjwGkuwkeaWBDqfI3xWuZ7rDiQXlH1o2zLfFzATfEgXcqpk8CkgMfbJD81McqjcJVez25a3kPxCR807evA== dependencies: "@babel/runtime" "^7.20.0" classnames "^2.2.6" @@ -8128,9 +7454,9 @@ react-dom@^18.2.0: scheduler "^0.23.2" react-hook-form@^7.53.0: - version "7.53.0" - resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.53.0.tgz#3cf70951bf41fa95207b34486203ebefbd3a05ab" - integrity sha512-M1n3HhqCww6S2hxLxciEXy2oISPnAzxY7gvwVPrtlczTM/1dDadXgUxDpHMrMTblDOcm/AXtXxHwZ3jpg1mqKQ== + version "7.65.0" + resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.65.0.tgz#6139dac77ed1081d0178b6830dc6f5ff6ff86361" + integrity sha512-xtOzDz063WcXvGWaHgLNrNzlsdFgtUWcb32E6WFaGTd7kPZG3EeDusjdZfUsPwKCKVXy1ZlntifaHZ4l8pAsmw== react-i18next@^14.1.0: version "14.1.3" @@ -8156,17 +7482,17 @@ react-loading-skeleton@^3.4.0: integrity sha512-gxxSyLbrEAdXTKgfbpBEFZCO/P153DnqSCQau2+o6lNy1jgMRr2MmRmOzMmyrwSaSYLRB8g7b0waYPmUjz7IhQ== react-redux@^9.1.1: - version "9.1.2" - resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-9.1.2.tgz#deba38c64c3403e9abd0c3fbeab69ffd9d8a7e4b" - integrity sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w== + version "9.2.0" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-9.2.0.tgz#96c3ab23fb9a3af2cb4654be4b51c989e32366f5" + integrity sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g== dependencies: - "@types/use-sync-external-store" "^0.0.3" - use-sync-external-store "^1.0.0" + "@types/use-sync-external-store" "^0.0.6" + use-sync-external-store "^1.4.0" -react-refresh@^0.14.0: - version "0.14.2" - resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.2.tgz#3833da01ce32da470f1f936b9d477da5c7028bf9" - integrity sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA== +react-refresh@^0.17.0: + version "0.17.0" + resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.17.0.tgz#b7e579c3657f23d04eccbe4ad2e58a8ed51e7e53" + integrity sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ== react-remove-scroll-bar@^2.3.7: version "2.3.8" @@ -8188,19 +7514,19 @@ react-remove-scroll@^2.6.3: use-sidecar "^1.1.3" react-router-dom@^6.22.3: - version "6.26.2" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.26.2.tgz#a6e3b0cbd6bfd508e42b9342099d015a0ac59680" - integrity sha512-z7YkaEW0Dy35T3/QKPYB1LjMK2R1fxnHO8kWpUMTBdfVzZrWOiY9a7CtN8HqdWtDUWd5FY6Dl8HFsqVwH4uOtQ== + version "6.30.1" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.30.1.tgz#da2580c272ddb61325e435478566be9563a4a237" + integrity sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw== dependencies: - "@remix-run/router" "1.19.2" - react-router "6.26.2" + "@remix-run/router" "1.23.0" + react-router "6.30.1" -react-router@6.26.2: - version "6.26.2" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.26.2.tgz#2f0a68999168954431cdc29dd36cec3b6fa44a7e" - integrity sha512-tvN1iuT03kHgOFnLPfLJ8V95eijteveqdOSk+srqfePtQvqCExB8eHOYnlilbOcyJyKnYkr1vJvf7YqotAJu1A== +react-router@6.30.1: + version "6.30.1" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.30.1.tgz#ecb3b883c9ba6dbf5d319ddbc996747f4ab9f4c3" + integrity sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ== dependencies: - "@remix-run/router" "1.19.2" + "@remix-run/router" "1.23.0" react-shadow@^20.6.0: version "20.6.0" @@ -8218,9 +7544,9 @@ react-style-singleton@^2.2.2, react-style-singleton@^2.2.3: tslib "^2.0.0" react-tooltip@^5.28.1: - version "5.28.1" - resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-5.28.1.tgz#5b25c7c53ce008b7ad0685e9f516101d80925cbc" - integrity sha512-ZA4oHwoIIK09TS7PvSLFcRlje1wGZaxw6xHvfrzn6T82UcMEfEmHVCad16Gnr4NDNDh93HyN037VK4HDi5odfQ== + version "5.30.0" + resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-5.30.0.tgz#8e1af2d1bf91a2a5169302bd0eb82b62847ad224" + integrity sha512-Yn8PfbgQ/wmqnL7oBpz1QiDaLKrzZMdSUUdk7nVeGTwzbxCAJiJzR4VSYW+eIO42F1INt57sPUmpgKv0KwJKtg== dependencies: "@floating-ui/dom" "^1.6.1" classnames "^2.3.0" @@ -8248,6 +7574,11 @@ readable-stream@^3.4.0: string_decoder "^1.1.1" util-deprecate "^1.0.1" +readdirp@^4.0.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.2.tgz#eb85801435fbf2a7ee58f19e0921b068fc69948d" + integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg== + redux-thunk@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-3.1.0.tgz#94aa6e04977c30e14e892eae84978c1af6058ff3" @@ -8279,29 +7610,16 @@ reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9: get-proto "^1.0.1" which-builtin-type "^1.2.1" -regenerator-runtime@^0.14.0: - version "0.14.1" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" - integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== - -regexp.prototype.flags@^1.5.2: - version "1.5.2" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334" - integrity sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw== +regexp.prototype.flags@^1.5.3, regexp.prototype.flags@^1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz#1ad6c62d44a259007e55b3970e00f746efbcaa19" + integrity sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA== dependencies: - call-bind "^1.0.6" - define-properties "^1.2.1" - es-errors "^1.3.0" - set-function-name "^2.0.1" - -regexp.prototype.flags@^1.5.3: - version "1.5.3" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz#b3ae40b1d2499b8350ab2c3fe6ef3845d3a96f42" - integrity sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ== - dependencies: - call-bind "^1.0.7" + call-bind "^1.0.8" define-properties "^1.2.1" es-errors "^1.3.0" + get-proto "^1.0.1" + gopd "^1.2.0" set-function-name "^2.0.2" require-directory@^2.1.1: @@ -8320,9 +7638,9 @@ requires-port@^1.0.0: integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== resedit@^1.7.0: - version "1.7.1" - resolved "https://registry.yarnpkg.com/resedit/-/resedit-1.7.1.tgz#150c101000210968730141ae2eb504ca0aead165" - integrity sha512-/FJ6/gKAXbcHtivannhecWsa43kGVFK3aHHv9Jm3x0eFiM31MoGihkAOWbm3UsvjYLRVw0zTkfARy2dI96JL1Q== + version "1.7.2" + resolved "https://registry.yarnpkg.com/resedit/-/resedit-1.7.2.tgz#b1041170b99811710c13f949c7d225871de4cc78" + integrity sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA== dependencies: pe-library "^0.4.1" @@ -8381,9 +7699,9 @@ retry@^0.12.0: integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + version "1.1.0" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" + integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== rimraf@^3.0.2: version "3.0.2" @@ -8392,13 +7710,6 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" -rimraf@^5.0.5: - version "5.0.10" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.10.tgz#23b9843d3dc92db71f96e1a2ce92e39fd2a8221c" - integrity sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ== - dependencies: - glob "^10.3.7" - roarr@^2.15.3: version "2.15.4" resolved "https://registry.yarnpkg.com/roarr/-/roarr-2.15.4.tgz#f5fe795b7b838ccfe35dc608e0282b9eba2e7afd" @@ -8452,6 +7763,11 @@ rrweb-cssom@^0.7.1: resolved "https://registry.yarnpkg.com/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz#c73451a484b86dd7cfb1e0b2898df4b703183e4b" integrity sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg== +rrweb-cssom@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz#3021d1b4352fbf3b614aaeed0bc0d5739abe0bc2" + integrity sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw== + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -8460,22 +7776,12 @@ run-parallel@^1.1.9: queue-microtask "^1.2.2" rxjs@^7.4.0: - version "7.8.1" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" - integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== + version "7.8.2" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.2.tgz#955bc473ed8af11a002a2be52071bf475638607b" + integrity sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA== dependencies: tslib "^2.1.0" -safe-array-concat@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb" - integrity sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q== - dependencies: - call-bind "^1.0.7" - get-intrinsic "^1.2.4" - has-symbols "^1.0.3" - isarray "^2.0.5" - safe-array-concat@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.3.tgz#c9e54ec4f603b0bbb8e7e5007a5ee7aecd1538c3" @@ -8500,16 +7806,7 @@ safe-push-apply@^1.0.0: es-errors "^1.3.0" isarray "^2.0.5" -safe-regex-test@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377" - integrity sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw== - dependencies: - call-bind "^1.0.6" - es-errors "^1.3.0" - is-regex "^1.1.4" - -safe-regex-test@^1.1.0: +safe-regex-test@^1.0.3, safe-regex-test@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz#7f87dfb67a3150782eaaf18583ff5d1711ac10c1" integrity sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw== @@ -8530,139 +7827,143 @@ sanitize-filename@^1.6.3: dependencies: truncate-utf8-bytes "^1.0.0" -sass-embedded-android-arm64@1.80.6: - version "1.80.6" - resolved "https://registry.yarnpkg.com/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.80.6.tgz#748df1f7deabea3a81c2c647661d9696090e1b81" - integrity sha512-4rC4ZGM/k4ENVjLXnK3JTst8e8FI9MHSol2Fl7dCdYyJ3KLnlt4qL4AEYfU8zq1tcBb7CBOSZVR+CzCKubnXdg== +sass-embedded-all-unknown@1.93.2: + version "1.93.2" + resolved "https://registry.yarnpkg.com/sass-embedded-all-unknown/-/sass-embedded-all-unknown-1.93.2.tgz#dd9207a0dd4eea2f14774359e1d37efc61c7a63c" + integrity sha512-GdEuPXIzmhRS5J7UKAwEvtk8YyHQuFZRcpnEnkA3rwRUI27kwjyXkNeIj38XjUQ3DzrfMe8HcKFaqWGHvblS7Q== + dependencies: + sass "1.93.2" -sass-embedded-android-arm@1.80.6: - version "1.80.6" - resolved "https://registry.yarnpkg.com/sass-embedded-android-arm/-/sass-embedded-android-arm-1.80.6.tgz#1fa2e08e5b2a77709f6e8cb8186c801ee140c0a9" - integrity sha512-UeUKMTRsnz4/dh7IzvhjONxa4/jmVp539CHDd8VZOsqg9M3HcNJNIkUzQWbuwZ+nSlWrTuo7Tvn3XlypopCBzw== +sass-embedded-android-arm64@1.93.2: + version "1.93.2" + resolved "https://registry.yarnpkg.com/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.93.2.tgz#e5a6a7c9e98d4b4b12f2cb5e9f340564193a9ee4" + integrity sha512-346f4iVGAPGcNP6V6IOOFkN5qnArAoXNTPr5eA/rmNpeGwomdb7kJyQ717r9rbJXxOG8OAAUado6J0qLsjnjXQ== -sass-embedded-android-ia32@1.80.6: - version "1.80.6" - resolved "https://registry.yarnpkg.com/sass-embedded-android-ia32/-/sass-embedded-android-ia32-1.80.6.tgz#43674c98454a3adb5da3cca0ba23340128bfbdff" - integrity sha512-Lxz2SXE2KdHnynuHF+D6flDvrd55/zaEAWUeka9MxEr6FmR66d8UBOIy5ETwCSUd//S/SE5Jl6oTnHppgD1zNA== +sass-embedded-android-arm@1.93.2: + version "1.93.2" + resolved "https://registry.yarnpkg.com/sass-embedded-android-arm/-/sass-embedded-android-arm-1.93.2.tgz#5970c732137b0a1abbb889305c462cf2ab11b91e" + integrity sha512-I8bpO8meZNo5FvFx5FIiE7DGPVOYft0WjuwcCCdeJ6duwfkl6tZdatex1GrSigvTsuz9L0m4ngDcX/Tj/8yMow== -sass-embedded-android-riscv64@1.80.6: - version "1.80.6" - resolved "https://registry.yarnpkg.com/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.80.6.tgz#59c9d53b1c800b53221d56cf55d9cf84b4418bac" - integrity sha512-hKdxY/oOqB+JJhSoBTDM5DJO1j/xtxQgayh2cLCCUx37IQQe3SEdc3V2JFf/4mIo5peaS4cjqwwSATF+l2zaXg== +sass-embedded-android-riscv64@1.93.2: + version "1.93.2" + resolved "https://registry.yarnpkg.com/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.93.2.tgz#0b52b538729237cd08efc008fd39291ea36e058d" + integrity sha512-hSMW1s4yJf5guT9mrdkumluqrwh7BjbZ4MbBW9tmi1DRDdlw1Wh9Oy1HnnmOG8x9XcI1qkojtPL6LUuEJmsiDg== -sass-embedded-android-x64@1.80.6: - version "1.80.6" - resolved "https://registry.yarnpkg.com/sass-embedded-android-x64/-/sass-embedded-android-x64-1.80.6.tgz#a06a9f55f904c3ba628a3ae03c244f134f4f2bb1" - integrity sha512-Eap2Fi3kTx/rVLBsOnOp5RYPr5+lFjTZ652zR24dmYFe9/sDgasakJIOPjOvD2bRuL9z0uWEY1AXVeeOPeZKrg== +sass-embedded-android-x64@1.93.2: + version "1.93.2" + resolved "https://registry.yarnpkg.com/sass-embedded-android-x64/-/sass-embedded-android-x64-1.93.2.tgz#793483a0a4848d06cd4f0cb44d2e0ebc906c0a20" + integrity sha512-JqktiHZduvn+ldGBosE40ALgQ//tGCVNAObgcQ6UIZznEJbsHegqStqhRo8UW3x2cgOO2XYJcrInH6cc7wdKbw== -sass-embedded-darwin-arm64@1.80.6: - version "1.80.6" - resolved "https://registry.yarnpkg.com/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.80.6.tgz#81a7019c92a8c8f76f7f98dfe15f6adfaf621e92" - integrity sha512-0mnAx8Vq6Gxj3PQt3imgITfK33hhqrSKpyHSuab71gZZni5opsdtoggq2JawW+1taRFTEZwbZJLKZ0MBDbwCCA== +sass-embedded-darwin-arm64@1.93.2: + version "1.93.2" + resolved "https://registry.yarnpkg.com/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.93.2.tgz#03c2832c9e9b1e42563c36e7636e01bd724da134" + integrity sha512-qI1X16qKNeBJp+M/5BNW7v/JHCDYWr1/mdoJ7+UMHmP0b5AVudIZtimtK0hnjrLnBECURifd6IkulybR+h+4UA== -sass-embedded-darwin-x64@1.80.6: - version "1.80.6" - resolved "https://registry.yarnpkg.com/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.80.6.tgz#d1b551443930658bfa4cc92ca026d4b22023ce9d" - integrity sha512-Ib20yNZFOrJ7YVT+ltoe+JQNKPcRclM3iLAK69XZZYcSeFM/72SCoQBAaVGIpT23dxDp7FXiE4lO602c3xTRwQ== +sass-embedded-darwin-x64@1.93.2: + version "1.93.2" + resolved "https://registry.yarnpkg.com/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.93.2.tgz#492f8e46ee0790b22c5119c9f2e729ae78cb24c7" + integrity sha512-4KeAvlkQ0m0enKUnDGQJZwpovYw99iiMb8CTZRSsQm8Eh7halbJZVmx67f4heFY/zISgVOCcxNg19GrM5NTwtA== -sass-embedded-linux-arm64@1.80.6: - version "1.80.6" - resolved "https://registry.yarnpkg.com/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.80.6.tgz#fe6255bbbe55590fb6405f098431b3841221a1b0" - integrity sha512-n5r98pBXawrQQKaxIYCMM1zDpnngsqxTkOrmvsYLFiAMCSbR0lWf/7sBB33k/Pm0D6dsbp3jpHilCoQNKI3jIw== +sass-embedded-linux-arm64@1.93.2: + version "1.93.2" + resolved "https://registry.yarnpkg.com/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.93.2.tgz#144a4a056778c17011726885b0f94fccb6f389f7" + integrity sha512-9ftX6nd5CsShJqJ2WRg+ptaYvUW+spqZfJ88FbcKQBNFQm6L87luj3UI1rB6cP5EWrLwHA754OKxRJyzWiaN6g== -sass-embedded-linux-arm@1.80.6: - version "1.80.6" - resolved "https://registry.yarnpkg.com/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.80.6.tgz#a6603b1ec087762a1128e836fd36851050b537b4" - integrity sha512-QR0Q6TZox/ThuU2r9c0s3fKCgU2rXAEocpitdgxFp6tta+GsQlMFV3oON2unAa8Bwnuxkmf0YOaK0Oy/TwzkXw== +sass-embedded-linux-arm@1.93.2: + version "1.93.2" + resolved "https://registry.yarnpkg.com/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.93.2.tgz#0df4594316c3b21b18bf60c8d75e2acddda09ef0" + integrity sha512-N3+D/ToHtzwLDO+lSH05Wo6/KRxFBPnbjVHASOlHzqJnK+g5cqex7IFAp6ozzlRStySk61Rp6d+YGrqZ6/P0PA== -sass-embedded-linux-ia32@1.80.6: - version "1.80.6" - resolved "https://registry.yarnpkg.com/sass-embedded-linux-ia32/-/sass-embedded-linux-ia32-1.80.6.tgz#bedb53348f7e384ceabc9e0158b086045779af07" - integrity sha512-O6dWZdcOkryRdDCxVMGOeVowgblpDgVcAuRtZ1F1X7XfbpDriTQm64D+9vVZIrywYSPoJfQMJJ662cr0wUs9IQ== +sass-embedded-linux-musl-arm64@1.93.2: + version "1.93.2" + resolved "https://registry.yarnpkg.com/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.93.2.tgz#212a5107f9d05853236def3cee40cba35ff6dd7b" + integrity sha512-+3EHuDPkMiAX5kytsjEC1bKZCawB9J6pm2eBIzzLMPWbf5xdx++vO1DpT7hD4bm4ZGn0eVHgSOKIfP6CVz6tVg== -sass-embedded-linux-musl-arm64@1.80.6: - version "1.80.6" - resolved "https://registry.yarnpkg.com/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.80.6.tgz#0f0e0bb68c90f0ae920d9a06760241f3f58c7a76" - integrity sha512-VeUSHUi3MAsvOlg9QI4X/2j04h1659aE+7qKP/282CYBTrGkjFGSXZhIki9WKWDgIpDiSInRYXfQQRWhPhjCDg== +sass-embedded-linux-musl-arm@1.93.2: + version "1.93.2" + resolved "https://registry.yarnpkg.com/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.93.2.tgz#29ebecef7ed479d0441b1cb8305c59724816fd07" + integrity sha512-XBTvx66yRenvEsp3VaJCb3HQSyqCsUh7R+pbxcN5TuzueybZi0LXvn9zneksdXcmjACMlMpIVXi6LyHPQkYc8A== -sass-embedded-linux-musl-arm@1.80.6: - version "1.80.6" - resolved "https://registry.yarnpkg.com/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.80.6.tgz#41039f857684ee3470ec9e0d310add5f6f1b694d" - integrity sha512-X9FC8s8fvQGRiXc+eATlZ57N44Iq3nNa0M0ugi3ysdJwkaNYvOeS4QzBHKQAaw3QiTqdxTnLUHHVBkyzdCi9pw== +sass-embedded-linux-musl-riscv64@1.93.2: + version "1.93.2" + resolved "https://registry.yarnpkg.com/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.93.2.tgz#935da64e1f8e10e9a0a0d045b2e2a78a26d26267" + integrity sha512-0sB5kmVZDKTYzmCSlTUnjh6mzOhzmQiW/NNI5g8JS4JiHw2sDNTvt1dsFTuqFkUHyEOY3ESTsfHHBQV8Ip4bEA== -sass-embedded-linux-musl-ia32@1.80.6: - version "1.80.6" - resolved "https://registry.yarnpkg.com/sass-embedded-linux-musl-ia32/-/sass-embedded-linux-musl-ia32-1.80.6.tgz#8ff88c78ba3503a19d59c5b8fa172fdea00a67f8" - integrity sha512-GqitS2Nab8ah0+wfCqaxW1hnI1piC08FimL6+lM9YWK5DbCOOF82IapbvJOy0feUmd/wNnHmyNTgE9h0zVMFdQ== +sass-embedded-linux-musl-x64@1.93.2: + version "1.93.2" + resolved "https://registry.yarnpkg.com/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.93.2.tgz#0f6a79b54ab7393376f7a7a4d19e8b5f6752db37" + integrity sha512-t3ejQ+1LEVuHy7JHBI2tWHhoMfhedUNDjGJR2FKaLgrtJntGnyD1RyX0xb3nuqL/UXiEAtmTmZY+Uh3SLUe1Hg== -sass-embedded-linux-musl-riscv64@1.80.6: - version "1.80.6" - resolved "https://registry.yarnpkg.com/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.80.6.tgz#aaa086a6ce9718d0fd7cbe584c0f2bf81bef3122" - integrity sha512-ySs15z7QSRRQK/aByEEqaJLYW/sTpfynefNPZCtsVNVEzNRwy+DRpxNChtxo+QjKq97ocXETbdG5KLik7QOTJg== +sass-embedded-linux-riscv64@1.93.2: + version "1.93.2" + resolved "https://registry.yarnpkg.com/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.93.2.tgz#d98024c3a7dfac5d7410a75ca3cc1a7ff86ec3b7" + integrity sha512-e7AndEwAbFtXaLy6on4BfNGTr3wtGZQmypUgYpSNVcYDO+CWxatKVY4cxbehMPhxG9g5ru+eaMfynvhZt7fLaA== -sass-embedded-linux-musl-x64@1.80.6: - version "1.80.6" - resolved "https://registry.yarnpkg.com/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.80.6.tgz#ea0bb093837be3b6f473b9a1b5d09c4633f8e504" - integrity sha512-DzeNqU/SN0mWFznoOH4RtVGcrg3Eoa41pUQhKMtrhNbCmIE1zNDunUiAEVTNpdHJF4nxf7ELUPXWmStM31CbUQ== +sass-embedded-linux-x64@1.93.2: + version "1.93.2" + resolved "https://registry.yarnpkg.com/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.93.2.tgz#d38476e53ba26b98c960dadd94f6d182566d69d0" + integrity sha512-U3EIUZQL11DU0xDDHXexd4PYPHQaSQa2hzc4EzmhHqrAj+TyfYO94htjWOd+DdTPtSwmLp+9cTWwPZBODzC96w== -sass-embedded-linux-riscv64@1.80.6: - version "1.80.6" - resolved "https://registry.yarnpkg.com/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.80.6.tgz#bc19c15796a3b3a3d67ea626058b3bab48344b1d" - integrity sha512-AyoHJ3icV9xuJjq1YzJqpEj2XfiC/KBkVYTUrCELKiXP0DN1gi/BpUwZNCAgCM3CyEdMef4LQM/ztCYJxYzdyg== +sass-embedded-unknown-all@1.93.2: + version "1.93.2" + resolved "https://registry.yarnpkg.com/sass-embedded-unknown-all/-/sass-embedded-unknown-all-1.93.2.tgz#51d0618999426bf311d16e48d7b53c208c26b514" + integrity sha512-7VnaOmyewcXohiuoFagJ3SK5ddP9yXpU0rzz+pZQmS1/+5O6vzyFCUoEt3HDRaLctH4GT3nUGoK1jg0ae62IfQ== + dependencies: + sass "1.93.2" -sass-embedded-linux-x64@1.80.6: - version "1.80.6" - resolved "https://registry.yarnpkg.com/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.80.6.tgz#db076fdf0bc3c1382c8f73c8d396293b893b31ef" - integrity sha512-EohsE9CEqx0ycylnsEj/0DNPG99Tb0qAVZspiAs5xHFCJjXOFfp3cRQu0BRf+lZ1b72IhPFXymzVtojvzUHb7g== +sass-embedded-win32-arm64@1.93.2: + version "1.93.2" + resolved "https://registry.yarnpkg.com/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.93.2.tgz#806310afadb2e040204878718180b10828651d8a" + integrity sha512-Y90DZDbQvtv4Bt0GTXKlcT9pn4pz8AObEjFF8eyul+/boXwyptPZ/A1EyziAeNaIEIfxyy87z78PUgCeGHsx3Q== -sass-embedded-win32-arm64@1.80.6: - version "1.80.6" - resolved "https://registry.yarnpkg.com/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.80.6.tgz#e889c421b0b31e1297414dd89448385fe443d5e2" - integrity sha512-29wETQi1ykeVvpd4zMVokpQKFSOZskGJzZawuuNCdo7BHjHKIRDsqbz8YT1CewHPBshI0hfD21fenmjxYjGXPQ== - -sass-embedded-win32-ia32@1.80.6: - version "1.80.6" - resolved "https://registry.yarnpkg.com/sass-embedded-win32-ia32/-/sass-embedded-win32-ia32-1.80.6.tgz#0934f3598e4fbe701b5e4e4d2a1fa6ef952f7024" - integrity sha512-1s3OpK2iTIfIL/a91QhAQnffsbuWfnsM8Lx4Fxt0f7ErnxjCV6q8MUFTV/UhcLtLyTFnPCA62DLjp2KGCjMI9A== - -sass-embedded-win32-x64@1.80.6: - version "1.80.6" - resolved "https://registry.yarnpkg.com/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.80.6.tgz#b7357b399c12cbb80dab2bee6f541b88a6015d76" - integrity sha512-0pH4Zr9silHkcmLPC0ghnD3DI0vMsjA7dKvGR32/RbbjOSvHV5cDQRLiuVJAPp34dfMA7kJd1ysSchRdH0igAQ== +sass-embedded-win32-x64@1.93.2: + version "1.93.2" + resolved "https://registry.yarnpkg.com/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.93.2.tgz#4bc3cefd0e79106d4d15074fed6dd99d0a782b32" + integrity sha512-BbSucRP6PVRZGIwlEBkp+6VQl2GWdkWFMN+9EuOTPrLxCJZoq+yhzmbjspd3PeM8+7WJ7AdFu/uRYdO8tor1iQ== sass-embedded@^1.80.6: - version "1.80.6" - resolved "https://registry.yarnpkg.com/sass-embedded/-/sass-embedded-1.80.6.tgz#efd66c12c7f117c2dcb558e8bf6bd00cc5cd49b2" - integrity sha512-Og4aqBnaA3oJfIpHaLuNATAqzBRgUJDYJy2X15V59cot2wYOtiT/ciPnyuq1o7vpDEeOkHhEd+mSviSlXoETug== + version "1.93.2" + resolved "https://registry.yarnpkg.com/sass-embedded/-/sass-embedded-1.93.2.tgz#8747862cfe2e3e70772a309223984681bf33f499" + integrity sha512-FvQdkn2dZ8DGiLgi0Uf4zsj7r/BsiLImNa5QJ10eZalY6NfZyjrmWGFcuCN5jNwlDlXFJnftauv+UtvBKLvepQ== dependencies: - "@bufbuild/protobuf" "^2.0.0" + "@bufbuild/protobuf" "^2.5.0" buffer-builder "^0.2.0" colorjs.io "^0.5.0" - immutable "^4.0.0" + immutable "^5.0.2" rxjs "^7.4.0" supports-color "^8.1.1" + sync-child-process "^1.0.2" varint "^6.0.0" optionalDependencies: - sass-embedded-android-arm "1.80.6" - sass-embedded-android-arm64 "1.80.6" - sass-embedded-android-ia32 "1.80.6" - sass-embedded-android-riscv64 "1.80.6" - sass-embedded-android-x64 "1.80.6" - sass-embedded-darwin-arm64 "1.80.6" - sass-embedded-darwin-x64 "1.80.6" - sass-embedded-linux-arm "1.80.6" - sass-embedded-linux-arm64 "1.80.6" - sass-embedded-linux-ia32 "1.80.6" - sass-embedded-linux-musl-arm "1.80.6" - sass-embedded-linux-musl-arm64 "1.80.6" - sass-embedded-linux-musl-ia32 "1.80.6" - sass-embedded-linux-musl-riscv64 "1.80.6" - sass-embedded-linux-musl-x64 "1.80.6" - sass-embedded-linux-riscv64 "1.80.6" - sass-embedded-linux-x64 "1.80.6" - sass-embedded-win32-arm64 "1.80.6" - sass-embedded-win32-ia32 "1.80.6" - sass-embedded-win32-x64 "1.80.6" + sass-embedded-all-unknown "1.93.2" + sass-embedded-android-arm "1.93.2" + sass-embedded-android-arm64 "1.93.2" + sass-embedded-android-riscv64 "1.93.2" + sass-embedded-android-x64 "1.93.2" + sass-embedded-darwin-arm64 "1.93.2" + sass-embedded-darwin-x64 "1.93.2" + sass-embedded-linux-arm "1.93.2" + sass-embedded-linux-arm64 "1.93.2" + sass-embedded-linux-musl-arm "1.93.2" + sass-embedded-linux-musl-arm64 "1.93.2" + sass-embedded-linux-musl-riscv64 "1.93.2" + sass-embedded-linux-musl-x64 "1.93.2" + sass-embedded-linux-riscv64 "1.93.2" + sass-embedded-linux-x64 "1.93.2" + sass-embedded-unknown-all "1.93.2" + sass-embedded-win32-arm64 "1.93.2" + sass-embedded-win32-x64 "1.93.2" + +sass@1.93.2: + version "1.93.2" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.93.2.tgz#e97d225d60f59a3b3dbb6d2ae3c1b955fd1f2cd1" + integrity sha512-t+YPtOQHpGW1QWsh1CHQ5cPIr9lbbGZLZnbihP/D/qZj/yuV68m8qarcV17nvkOX81BCrvzAlq2klCQFZghyTg== + dependencies: + chokidar "^4.0.0" + immutable "^5.0.2" + source-map-js ">=0.6.2 <2.0.0" + optionalDependencies: + "@parcel/watcher" "^2.4.1" sax@^1.2.4: version "1.4.1" @@ -8699,9 +8000,9 @@ semver@^6.2.0, semver@^6.3.1: integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== semver@^7.3.2, semver@^7.3.5, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.6.3: - version "7.6.3" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" - integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + version "7.7.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946" + integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q== serialize-error@^7.0.1: version "7.0.1" @@ -8710,7 +8011,7 @@ serialize-error@^7.0.1: dependencies: type-fest "^0.13.1" -set-function-length@^1.2.1, set-function-length@^1.2.2: +set-function-length@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== @@ -8722,7 +8023,7 @@ set-function-length@^1.2.1, set-function-length@^1.2.2: gopd "^1.0.1" has-property-descriptors "^1.0.2" -set-function-name@^2.0.1, set-function-name@^2.0.2: +set-function-name@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== @@ -8782,16 +8083,6 @@ side-channel-weakmap@^1.0.2: object-inspect "^1.13.3" side-channel-map "^1.0.1" -side-channel@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" - integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== - dependencies: - call-bind "^1.0.7" - es-errors "^1.3.0" - get-intrinsic "^1.2.4" - object-inspect "^1.13.1" - side-channel@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" @@ -8814,9 +8105,9 @@ signal-exit@^4.0.1: integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== simple-swizzle@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" - integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== + version "0.2.4" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.4.tgz#a8d11a45a11600d6a1ecdff6363329e3648c3667" + integrity sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw== dependencies: is-arrayish "^0.3.1" @@ -8864,11 +8155,11 @@ socks-proxy-agent@^7.0.0: socks "^2.6.2" socks@^2.6.2: - version "2.8.3" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.3.tgz#1ebd0f09c52ba95a09750afe3f3f9f724a800cb5" - integrity sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw== + version "2.8.7" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.7.tgz#e2fb1d9a603add75050a2067db8c381a0b5669ea" + integrity sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A== dependencies: - ip-address "^9.0.5" + ip-address "^10.0.1" smart-buffer "^4.2.0" sound-play@^1.1.0: @@ -8876,7 +8167,7 @@ sound-play@^1.1.0: resolved "https://registry.yarnpkg.com/sound-play/-/sound-play-1.1.0.tgz#58ffa31d1bf51822d49d91ff7865591fd9376381" integrity sha512-Bd/L0AoCwITFeOnpNLMsfPXrV5GG5NhrC/T6odveahYbhPZkdTnrFXRia9FCC5WBWdUTw1d+yvLBvi4wnD1xOA== -source-map-js@^1.2.1: +"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== @@ -8899,7 +8190,7 @@ split2@^4.0.0: resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== -sprintf-js@^1.1.2, sprintf-js@^1.1.3: +sprintf-js@^1.1.2: version "1.1.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== @@ -8928,6 +8219,14 @@ state-local@^1.0.6: lodash "^4.17.21" object-sizeof "^1.2.0" +stop-iteration-iterator@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz#f481ff70a548f6124d0312c3aa14cbfa7aa542ad" + integrity sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ== + dependencies: + es-errors "^1.3.0" + internal-slot "^1.1.0" + "string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" @@ -9004,25 +8303,6 @@ string.prototype.trim@^1.2.10: es-object-atoms "^1.0.0" has-property-descriptors "^1.0.2" -string.prototype.trim@^1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4" - integrity sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.0" - es-object-atoms "^1.0.0" - -string.prototype.trimend@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz#3651b8513719e8a9f48de7f2f77640b26652b229" - integrity sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" - string.prototype.trimend@^1.0.9: version "1.0.9" resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz#62e2731272cd285041b36596054e9f66569b6942" @@ -9064,9 +8344,9 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: ansi-regex "^5.0.1" strip-ansi@^7.0.1: - version "7.1.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" - integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + version "7.1.2" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.2.tgz#132875abde678c7ea8d691533f2e7e22bb744dba" + integrity sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA== dependencies: ansi-regex "^6.0.1" @@ -9075,18 +8355,17 @@ strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -strnum@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db" - integrity sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA== +strnum@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/strnum/-/strnum-2.1.1.tgz#cf2a6e0cf903728b8b2c4b971b7e36b4e82d46ab" + integrity sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw== strtok3@^10.2.0: - version "10.2.2" - resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-10.2.2.tgz#a4c6d78d15db02c5eb20d92af3eedf81edaf09d2" - integrity sha512-Xt18+h4s7Z8xyZ0tmBoRmzxcop97R4BAh+dXouUDCYn+Em+1P3qpkUfI5ueWLT8ynC5hZ+q4iPEmGG1urvQGBg== + version "10.3.4" + resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-10.3.4.tgz#793ebd0d59df276a085586134b73a406e60be9c1" + integrity sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg== dependencies: "@tokenizer/token" "^0.3.0" - peek-readable "^7.0.0" sudo-prompt@^9.2.1: version "9.2.1" @@ -9100,13 +8379,6 @@ sumchecker@^3.0.1: dependencies: debug "^4.1.0" -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" @@ -9136,13 +8408,24 @@ symbol-tree@^3.2.4: resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== -synckit@^0.9.1: - version "0.9.1" - resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.9.1.tgz#febbfbb6649979450131f64735aa3f6c14575c88" - integrity sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A== +sync-child-process@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/sync-child-process/-/sync-child-process-1.0.2.tgz#45e7c72e756d1243e80b547ea2e17957ab9e367f" + integrity sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA== dependencies: - "@pkgr/core" "^0.1.0" - tslib "^2.6.2" + sync-message-port "^1.0.0" + +sync-message-port@^1.0.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/sync-message-port/-/sync-message-port-1.1.3.tgz#6055c565ee8c81d2f9ee5aae7db757e6d9088c0c" + integrity sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg== + +synckit@^0.11.7: + version "0.11.11" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.11.11.tgz#c0b619cf258a97faa209155d9cd1699b5c998cb0" + integrity sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw== + dependencies: + "@pkgr/core" "^0.2.9" tar@^6.0.5, tar@^6.1.11, tar@^6.1.12, tar@^6.2.1: version "6.2.1" @@ -9157,15 +8440,14 @@ tar@^6.0.5, tar@^6.1.11, tar@^6.1.12, tar@^6.2.1: yallist "^4.0.0" tar@^7.4.3: - version "7.4.3" - resolved "https://registry.yarnpkg.com/tar/-/tar-7.4.3.tgz#88bbe9286a3fcd900e94592cda7a22b192e80571" - integrity sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw== + version "7.5.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-7.5.1.tgz#750a8bd63b7c44c1848e7bf982260a083cf747c9" + integrity sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g== dependencies: "@isaacs/fs-minipass" "^4.0.0" chownr "^3.0.0" minipass "^7.1.2" - minizlib "^3.0.1" - mkdirp "^3.0.1" + minizlib "^3.1.0" yallist "^5.0.0" temp-file@^3.4.0: @@ -9208,22 +8490,22 @@ tiny-typed-emitter@^2.1.0: resolved "https://registry.yarnpkg.com/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz#b3b027fdd389ff81a152c8e847ee2f5be9fad7b5" integrity sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA== -tinyexec@^0.3.0: - version "0.3.1" - resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-0.3.1.tgz#0ab0daf93b43e2c211212396bdb836b468c97c98" - integrity sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ== +tinyexec@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-1.0.1.tgz#70c31ab7abbb4aea0a24f55d120e5990bfa1e0b1" + integrity sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw== -tldts-core@^6.1.78: - version "6.1.78" - resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-6.1.78.tgz#47b477d9742870daa01dbd5ff9a598a48379728c" - integrity sha512-jS0svNsB99jR6AJBmfmEWuKIgz91Haya91Z43PATaeHJ24BkMoNRb/jlaD37VYjb0mYf6gRL/HOnvS1zEnYBiw== +tldts-core@^6.1.86: + version "6.1.86" + resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-6.1.86.tgz#a93e6ed9d505cb54c542ce43feb14c73913265d8" + integrity sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA== tldts@^6.1.32: - version "6.1.78" - resolved "https://registry.yarnpkg.com/tldts/-/tldts-6.1.78.tgz#ee94576653a60d421ff94162c4e9060f2e62467b" - integrity sha512-fSgYrW0ITH0SR/CqKMXIruYIPpNu5aDgUp22UhYoSrnUQwc7SBqifEBFNce7AAcygUPBo6a/gbtcguWdmko4RQ== + version "6.1.86" + resolved "https://registry.yarnpkg.com/tldts/-/tldts-6.1.86.tgz#087e0555b31b9725ee48ca7e77edc56115cd82f7" + integrity sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ== dependencies: - tldts-core "^6.1.78" + tldts-core "^6.1.86" tmp-promise@^3.0.2: version "3.0.3" @@ -9233,14 +8515,9 @@ tmp-promise@^3.0.2: tmp "^0.2.0" tmp@^0.2.0: - version "0.2.3" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.3.tgz#eb783cc22bc1e8bebd0671476d46ea4eb32a79ae" - integrity sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w== - -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + version "0.2.5" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.5.tgz#b06bcd23f0f3c8357b426891726d16015abfd8f8" + integrity sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow== to-regex-range@^5.0.1: version "5.0.1" @@ -9250,10 +8527,11 @@ to-regex-range@^5.0.1: is-number "^7.0.0" token-types@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/token-types/-/token-types-6.0.0.tgz#1ab26be1ef9c434853500c071acfe5c8dd6544a3" - integrity sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA== + version "6.1.1" + resolved "https://registry.yarnpkg.com/token-types/-/token-types-6.1.1.tgz#85bd0ada82939b9178ecd5285881a538c4c00fdd" + integrity sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ== dependencies: + "@borewit/text-codec" "^0.1.0" "@tokenizer/token" "^0.3.0" ieee754 "^1.2.1" @@ -9273,16 +8551,16 @@ tough-cookie@^4.1.4: url-parse "^1.5.3" tough-cookie@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-5.1.1.tgz#4641c1fdbf024927e29c5532edb7b6e5377ea1f2" - integrity sha512-Ek7HndSVkp10hmHP9V4qZO1u+pn1RU5sI0Fw+jCU3lyvuMZcgqsNgc6CmJJZyByK4Vm/qotGRJlfgAX8q+4JiA== + version "5.1.2" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-5.1.2.tgz#66d774b4a1d9e12dc75089725af3ac75ec31bed7" + integrity sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A== dependencies: tldts "^6.1.32" -tr46@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-5.0.0.tgz#3b46d583613ec7283020d79019f1335723801cec" - integrity sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g== +tr46@^5.1.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-5.1.1.tgz#96ae867cddb8fdb64a49cc3059a8d428bcf238ca" + integrity sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw== dependencies: punycode "^2.3.1" @@ -9294,9 +8572,9 @@ truncate-utf8-bytes@^1.0.0: utf8-byte-length "^1.0.1" ts-api-utils@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" - integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== + version "1.4.3" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.4.3.tgz#bfc2215fe6528fecab2b0fba570a2e8a4263b064" + integrity sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw== ts-node@^10.9.2: version "10.9.2" @@ -9317,16 +8595,11 @@ ts-node@^10.9.2: v8-compile-cache-lib "^3.0.1" yn "3.1.1" -tslib@^2.0.0, tslib@^2.1.0, tslib@^2.4.0: +tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.0, tslib@^2.6.2: version "2.8.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== -tslib@^2.0.3, tslib@^2.6.2: - version "2.7.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01" - integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== - type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" @@ -9349,15 +8622,6 @@ type-fest@^2.19.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== -typed-array-buffer@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz#1867c5d83b20fcb5ccf32649e5e2fc7424474ff3" - integrity sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ== - dependencies: - call-bind "^1.0.7" - es-errors "^1.3.0" - is-typed-array "^1.1.13" - typed-array-buffer@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz#a72395450a4869ec033fd549371b47af3a2ee536" @@ -9367,17 +8631,6 @@ typed-array-buffer@^1.0.3: es-errors "^1.3.0" is-typed-array "^1.1.14" -typed-array-byte-length@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz#d92972d3cff99a3fa2e765a28fcdc0f1d89dec67" - integrity sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw== - dependencies: - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-proto "^1.0.3" - is-typed-array "^1.1.13" - typed-array-byte-length@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz#8407a04f7d78684f3d252aa1a143d2b77b4160ce" @@ -9389,18 +8642,6 @@ typed-array-byte-length@^1.0.3: has-proto "^1.2.0" is-typed-array "^1.1.14" -typed-array-byte-offset@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz#f9ec1acb9259f395093e4567eb3c28a580d02063" - integrity sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA== - dependencies: - available-typed-arrays "^1.0.7" - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-proto "^1.0.3" - is-typed-array "^1.1.13" - typed-array-byte-offset@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz#ae3698b8ec91a8ab945016108aef00d5bff12355" @@ -9414,18 +8655,6 @@ typed-array-byte-offset@^1.0.4: is-typed-array "^1.1.15" reflect.getprototypeof "^1.0.9" -typed-array-length@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.6.tgz#57155207c76e64a3457482dfdc1c9d1d3c4c73a3" - integrity sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g== - dependencies: - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-proto "^1.0.3" - is-typed-array "^1.1.13" - possible-typed-array-names "^1.0.0" - typed-array-length@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.7.tgz#ee4deff984b64be1e118b0de8c9c877d5ce73d3d" @@ -9438,15 +8667,20 @@ typed-array-length@^1.0.7: possible-typed-array-names "^1.0.0" reflect.getprototypeof "^1.0.6" +typescript@5.4.5: + version "5.4.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611" + integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ== + typescript@^3.9: version "3.9.10" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8" integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q== typescript@^5.3.3, typescript@^5.4.3: - version "5.6.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.2.tgz#d1de67b6bef77c41823f822df8f0b3bcff60a5a0" - integrity sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw== + version "5.9.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" + integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== uc.micro@^2.0.0, uc.micro@^2.1.0: version "2.1.0" @@ -9461,19 +8695,9 @@ uint8-util@^2.2.2, uint8-util@^2.2.5: base64-arraybuffer "^1.0.2" uint8array-extras@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/uint8array-extras/-/uint8array-extras-1.4.0.tgz#e42a678a6dd335ec2d21661333ed42f44ae7cc74" - integrity sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ== - -unbox-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" - integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== - dependencies: - call-bind "^1.0.2" - has-bigints "^1.0.2" - has-symbols "^1.0.3" - which-boxed-primitive "^1.0.2" + version "1.5.0" + resolved "https://registry.yarnpkg.com/uint8array-extras/-/uint8array-extras-1.5.0.tgz#10d2a85213de3ada304fea1c454f635c73839e86" + integrity sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A== unbox-primitive@^1.1.0: version "1.1.0" @@ -9485,16 +8709,16 @@ unbox-primitive@^1.1.0: has-symbols "^1.1.0" which-boxed-primitive "^1.1.1" -undici-types@~5.26.4: - version "5.26.5" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" - integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== - undici-types@~6.21.0: version "6.21.0" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== +undici-types@~7.14.0: + version "7.14.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.14.0.tgz#4c037b32ca4d7d62fae042174604341588bc0840" + integrity sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA== + unicorn-magic@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/unicorn-magic/-/unicorn-magic-0.1.0.tgz#1bb9a51c823aaf9d73a8bfcd3d1a23dde94b0ce4" @@ -9534,15 +8758,15 @@ untildify@^3.0.2: resolved "https://registry.yarnpkg.com/untildify/-/untildify-3.0.3.tgz#1e7b42b140bcfd922b22e70ca1265bfe3634c7c9" integrity sha512-iSk/J8efr8uPT/Z4eSUywnqyrQU7DSdMfdqK4iWEaUVVmcP5JcnpRqmVMwcwcnmI1ATFNgC5V90u09tBynNFKA== -update-browserslist-db@^1.1.0, update-browserslist-db@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz#80846fba1d79e82547fb661f8d141e0945755fe5" - integrity sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A== +update-browserslist-db@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420" + integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== dependencies: escalade "^3.2.0" - picocolors "^1.1.0" + picocolors "^1.1.1" -uri-js@^4.2.2, uri-js@^4.4.1: +uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== @@ -9572,20 +8796,15 @@ use-sidecar@^1.1.3: detect-node-es "^1.1.0" tslib "^2.0.0" -use-sync-external-store@^1.0.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz#c3b6390f3a30eba13200d2302dcdf1e7b57b2ef9" - integrity sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw== - use-sync-external-store@^1.4.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz#55122e2a3edd2a6c106174c27485e0fd59bcfca0" - integrity sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A== + version "1.6.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz#b174bfa65cb2b526732d9f2ac0a408027876f32d" + integrity sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w== user-agents@^1.1.387: - version "1.1.387" - resolved "https://registry.yarnpkg.com/user-agents/-/user-agents-1.1.387.tgz#afc69da00b50eee7ffa17724890e755a6672b99f" - integrity sha512-EQ1tx80ucECUrw+mY/lI4lVQTEN7nYflGpuLwqZS6vXw+pfzHMnhKPwZN9NiR+Mo9QxmDeNmk+2ZDEvInsxrxw== + version "1.1.669" + resolved "https://registry.yarnpkg.com/user-agents/-/user-agents-1.1.669.tgz#329b1c3f9fb9349f0b974a0b9cf6c0cd6d1d0c1c" + integrity sha512-pbIzG+AOqCaIpySKJ4IAm1l0VyE4jMnK4y1thV8lm8PYxI+7X5uWcppOK7zY79TCKKTAnJH3/4gaVIZHsjrmJA== dependencies: lodash.clonedeep "^4.5.0" @@ -9604,11 +8823,6 @@ uuid@^13.0.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-13.0.0.tgz#263dc341b19b4d755eb8fe36b78d95a6b65707e8" integrity sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w== -uuid@^9.0.1: - version "9.0.1" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" - integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== - v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" @@ -9695,36 +8909,14 @@ whatwg-mimetype@^4.0.0: integrity sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg== whatwg-url@^14.0.0: - version "14.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-14.0.0.tgz#00baaa7fd198744910c4b1ef68378f2200e4ceb6" - integrity sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw== + version "14.2.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-14.2.0.tgz#4ee02d5d725155dae004f6ae95c73e7ef5d95663" + integrity sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw== dependencies: - tr46 "^5.0.0" + tr46 "^5.1.0" webidl-conversions "^7.0.0" -which-boxed-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" - integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== - dependencies: - is-bigint "^1.0.1" - is-boolean-object "^1.1.0" - is-number-object "^1.0.4" - is-string "^1.0.5" - is-symbol "^1.0.3" - -which-boxed-primitive@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.1.0.tgz#2d850d6c4ac37b95441a67890e19f3fda8b6c6d9" - integrity sha512-Ei7Miu/AXe2JJ4iNF5j/UphAgRoma4trE6PtisM09bPygb3egMH3YLW/befsWb1A1AxvNSFidOFTB18XtnIIng== - dependencies: - is-bigint "^1.1.0" - is-boolean-object "^1.2.0" - is-number-object "^1.1.0" - is-string "^1.1.0" - is-symbol "^1.1.0" - -which-boxed-primitive@^1.1.1: +which-boxed-primitive@^1.1.0, which-boxed-primitive@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz#d76ec27df7fa165f18d5808374a5fe23c29b176e" integrity sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA== @@ -9764,37 +8956,16 @@ which-collection@^1.0.2: is-weakmap "^2.0.2" is-weakset "^2.0.3" -which-typed-array@^1.1.14, which-typed-array@^1.1.15: - version "1.1.15" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d" - integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== - dependencies: - available-typed-arrays "^1.0.7" - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-tostringtag "^1.0.2" - -which-typed-array@^1.1.16: - version "1.1.16" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.16.tgz#db4db429c4706feca2f01677a144278e4a8c216b" - integrity sha512-g+N+GAWiRj66DngFwHvISJd+ITsyphZvD1vChfVg6cEdnzy53GzB3oy0fUNlvhz7H7+MiqhYr26qxQShCpKTTQ== - dependencies: - available-typed-arrays "^1.0.7" - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-tostringtag "^1.0.2" - -which-typed-array@^1.1.18: - version "1.1.18" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.18.tgz#df2389ebf3fbb246a71390e90730a9edb6ce17ad" - integrity sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA== +which-typed-array@^1.1.16, which-typed-array@^1.1.19: + version "1.1.19" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.19.tgz#df03842e870b6b88e117524a4b364b6fc689f956" + integrity sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw== dependencies: available-typed-arrays "^1.0.7" call-bind "^1.0.8" - call-bound "^1.0.3" - for-each "^0.3.3" + call-bound "^1.0.4" + for-each "^0.3.5" + get-proto "^1.0.1" gopd "^1.2.0" has-tostringtag "^1.0.2" @@ -9852,15 +9023,10 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -ws@^8.18.0: - version "8.18.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" - integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== - -ws@^8.18.1: - version "8.18.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.1.tgz#ea131d3784e1dfdff91adb0a4a116b127515e3cb" - integrity sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w== +ws@^8.18.0, ws@^8.18.1: + version "8.18.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.3.tgz#b56b88abffde62791c639170400c93dcb0c95472" + integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg== xml-name-validator@^5.0.0: version "5.0.0" @@ -9898,9 +9064,9 @@ yallist@^5.0.0: integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw== yaml@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.6.1.tgz#42f2b1ba89203f374609572d5349fb8686500773" - integrity sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg== + version "2.8.1" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.8.1.tgz#1870aa02b631f7e8328b93f8bc574fac5d6c4d79" + integrity sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw== yargs-parser@^21.1.1: version "21.1.1" @@ -9939,14 +9105,14 @@ yocto-queue@^0.1.0: integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== yocto-queue@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.1.1.tgz#fef65ce3ac9f8a32ceac5a634f74e17e5b232110" - integrity sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g== + version "1.2.1" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.2.1.tgz#36d7c4739f775b3cbc28e6136e21aa057adec418" + integrity sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg== yup@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/yup/-/yup-1.5.0.tgz#1aaa5e453f04424094b9c8a0e5292e0ac2d97246" - integrity sha512-NJfBIHnp1QbqZwxcgl6irnDMIsb/7d1prNhFx02f1kp8h+orpi4xs3w90szNpOh68a/iHPdMsYvhZWoDmUvXBQ== + version "1.7.1" + resolved "https://registry.yarnpkg.com/yup/-/yup-1.7.1.tgz#4c47c6bb367df08d4bc597f8c4c4f5fc4277f6ab" + integrity sha512-GKHFX2nXul2/4Dtfxhozv701jLQHdf6J34YDh2cEkpqoo8le5Mg6/LrdseVLrFarmFygZTlfIhHx/QKfb/QWXw== dependencies: property-expr "^2.0.5" tiny-case "^1.0.3" @@ -9954,6 +9120,6 @@ yup@^1.5.0: type-fest "^2.19.0" zod@^3.24.1: - version "3.24.1" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.1.tgz#27445c912738c8ad1e9de1bea0359fa44d9d35ee" - integrity sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A== + version "3.25.76" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.76.tgz#26841c3f6fd22a6a2760e7ccb719179768471e34" + integrity sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ== From c60753547ca349df735400d342c8cf923422812f Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Tue, 14 Oct 2025 13:18:42 +0100 Subject: [PATCH 083/197] feat: removing dexie --- .../achievement-notification.scss | 3 +- .../game-details/modals/edit-game-modal.scss | 7 +-- .../game-details/modals/repacks-modal.tsx | 45 +++++++++++-------- .../profile-content/profile-animations.ts | 18 ++++---- .../src/pages/settings/settings-debrid.tsx | 12 ++--- 5 files changed, 45 insertions(+), 40 deletions(-) diff --git a/src/renderer/src/components/achievements/notification/achievement-notification.scss b/src/renderer/src/components/achievements/notification/achievement-notification.scss index 0a41782e..090c91c4 100644 --- a/src/renderer/src/components/achievements/notification/achievement-notification.scss +++ b/src/renderer/src/components/achievements/notification/achievement-notification.scss @@ -302,7 +302,8 @@ $margin-bottom: 28px; } &--rare &__trophy-overlay { - background: linear-gradient( + background: + linear-gradient( 118deg, #e8ad15 18.96%, #d5900f 26.41%, diff --git a/src/renderer/src/pages/game-details/modals/edit-game-modal.scss b/src/renderer/src/pages/game-details/modals/edit-game-modal.scss index 5400df07..33558601 100644 --- a/src/renderer/src/pages/game-details/modals/edit-game-modal.scss +++ b/src/renderer/src/pages/game-details/modals/edit-game-modal.scss @@ -55,11 +55,8 @@ border: 1px solid var(--color-border); border-radius: 8px; background-color: var(--color-background-secondary); - background-image: linear-gradient( - 45deg, - rgba(255, 255, 255, 0.1) 25%, - transparent 25% - ), + background-image: + linear-gradient(45deg, rgba(255, 255, 255, 0.1) 25%, transparent 25%), linear-gradient(-45deg, rgba(255, 255, 255, 0.1) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, rgba(255, 255, 255, 0.1) 75%), linear-gradient(-45deg, transparent 75%, rgba(255, 255, 255, 0.1) 75%); diff --git a/src/renderer/src/pages/game-details/modals/repacks-modal.tsx b/src/renderer/src/pages/game-details/modals/repacks-modal.tsx index 0c4c2d9e..7551a31e 100644 --- a/src/renderer/src/pages/game-details/modals/repacks-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/repacks-modal.tsx @@ -210,25 +210,32 @@ export function RepacksModal({ className={`repacks-modal__download-sources ${isFilterDrawerOpen ? "repacks-modal__download-sources--open" : ""}`} >
    - {downloadSources.map((source) => { - const label = source.name || source.url; - const truncatedLabel = - label.length > 16 ? label.substring(0, 16) + "..." : label; - return ( -
    - toggleFingerprint(source.fingerprint)} - /> -
    - ); - })} + {downloadSources + .filter( + ( + source + ): source is DownloadSource & { fingerprint: string } => + source.fingerprint !== undefined + ) + .map((source) => { + const label = source.name || source.url; + const truncatedLabel = + label.length > 16 ? label.substring(0, 16) + "..." : label; + return ( +
    + toggleFingerprint(source.fingerprint)} + /> +
    + ); + })}
    diff --git a/src/renderer/src/pages/profile/profile-content/profile-animations.ts b/src/renderer/src/pages/profile/profile-content/profile-animations.ts index f9ac0593..d89f4323 100644 --- a/src/renderer/src/pages/profile/profile-content/profile-animations.ts +++ b/src/renderer/src/pages/profile/profile-content/profile-animations.ts @@ -5,7 +5,7 @@ export const sectionVariants = { height: 0, transition: { duration: 0.3, - ease: [0.25, 0.1, 0.25, 1], + ease: [0.25, 0.1, 0.25, 1] as const, opacity: { duration: 0.1 }, y: { duration: 0.1 }, height: { duration: 0.2 }, @@ -17,13 +17,13 @@ export const sectionVariants = { height: "auto", transition: { duration: 0.3, - ease: [0.25, 0.1, 0.25, 1], + ease: [0.25, 0.1, 0.25, 1] as const, opacity: { duration: 0.2, delay: 0.1 }, y: { duration: 0.3 }, height: { duration: 0.3 }, }, }, -}; +} as const; export const gameCardVariants = { hidden: { @@ -37,7 +37,7 @@ export const gameCardVariants = { scale: 1, transition: { duration: 0.4, - ease: [0.25, 0.1, 0.25, 1], + ease: [0.25, 0.1, 0.25, 1] as const, }, }, exit: { @@ -46,10 +46,10 @@ export const gameCardVariants = { scale: 0.95, transition: { duration: 0.3, - ease: [0.25, 0.1, 0.25, 1], + ease: [0.25, 0.1, 0.25, 1] as const, }, }, -}; +} as const; export const gameGridVariants = { hidden: { @@ -76,16 +76,16 @@ export const chevronVariants = { rotate: 0, transition: { duration: 0.2, - ease: "easeInOut", + ease: "easeInOut" as const, }, }, expanded: { rotate: 90, transition: { duration: 0.2, - ease: "easeInOut", + ease: "easeInOut" as const, }, }, -}; +} as const; export const GAME_STATS_ANIMATION_DURATION_IN_MS = 3500; diff --git a/src/renderer/src/pages/settings/settings-debrid.tsx b/src/renderer/src/pages/settings/settings-debrid.tsx index 4bb7d276..f4ac183a 100644 --- a/src/renderer/src/pages/settings/settings-debrid.tsx +++ b/src/renderer/src/pages/settings/settings-debrid.tsx @@ -21,7 +21,7 @@ const sectionVariants = { height: 0, transition: { duration: 0.3, - ease: [0.25, 0.1, 0.25, 1], + ease: [0.25, 0.1, 0.25, 1] as const, opacity: { duration: 0.1 }, y: { duration: 0.1 }, height: { duration: 0.2 }, @@ -33,30 +33,30 @@ const sectionVariants = { height: "auto", transition: { duration: 0.3, - ease: [0.25, 0.1, 0.25, 1], + ease: [0.25, 0.1, 0.25, 1] as const, opacity: { duration: 0.2, delay: 0.1 }, y: { duration: 0.3 }, height: { duration: 0.3 }, }, }, -}; +} as const; const chevronVariants = { collapsed: { rotate: 0, transition: { duration: 0.2, - ease: "easeInOut", + ease: "easeInOut" as const, }, }, expanded: { rotate: 90, transition: { duration: 0.2, - ease: "easeInOut", + ease: "easeInOut" as const, }, }, -}; +} as const; export function SettingsDebrid() { const { t } = useTranslation("settings"); From bfa2fd6166c499cd25dadc971f6cee45a468e411 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Tue, 14 Oct 2025 14:52:47 +0100 Subject: [PATCH 084/197] feat: improving download source import code --- src/main/events/download-sources/helpers.ts | 113 +++++++++++++++--- .../sync-download-sources-from-api.ts | 13 +- .../download-sources/sync-download-sources.ts | 78 +++++++++--- 3 files changed, 162 insertions(+), 42 deletions(-) diff --git a/src/main/events/download-sources/helpers.ts b/src/main/events/download-sources/helpers.ts index 7bd8023c..b0c6841d 100644 --- a/src/main/events/download-sources/helpers.ts +++ b/src/main/events/download-sources/helpers.ts @@ -16,6 +16,8 @@ const downloadSourceSchema = z.object({ }); type SteamGamesByLetter = Record; +type FormattedSteamGame = { id: string; name: string; formattedName: string }; +type FormattedSteamGamesByLetter = Record; const formatName = (name: string) => { return name @@ -29,8 +31,48 @@ const formatRepackName = (name: string) => { return formatName(name.replace("[DL]", "")); }; +interface DownloadSource { + id: number; + url: string; + name: string; + etag: string | null; + status: number; + downloadCount: number; + objectIds: string[]; + fingerprint?: string; + createdAt: Date; + updatedAt: Date; +} + +let downloadSourcesCache: Map | null = null; +let downloadSourcesCacheTime = 0; +const CACHE_TTL = 5000; + +const getDownloadSourcesMap = async (): Promise< + Map +> => { + const now = Date.now(); + if (downloadSourcesCache && now - downloadSourcesCacheTime < CACHE_TTL) { + return downloadSourcesCache; + } + + const map = new Map(); + for await (const [key, source] of downloadSourcesSublevel.iterator()) { + map.set(key, source); + } + + downloadSourcesCache = map; + downloadSourcesCacheTime = now; + return map; +}; + +export const invalidateDownloadSourcesCache = () => { + downloadSourcesCache = null; +}; + export const checkUrlExists = async (url: string): Promise => { - for await (const [, source] of downloadSourcesSublevel.iterator()) { + const sources = await getDownloadSourcesMap(); + for (const source of sources.values()) { if (source.url === url) { return true; } @@ -38,12 +80,31 @@ export const checkUrlExists = async (url: string): Promise => { return false; }; -const getSteamGames = async () => { +let steamGamesCache: FormattedSteamGamesByLetter | null = null; +let steamGamesCacheTime = 0; +const STEAM_GAMES_CACHE_TTL = 300000; + +const getSteamGames = async (): Promise => { + const now = Date.now(); + if (steamGamesCache && now - steamGamesCacheTime < STEAM_GAMES_CACHE_TTL) { + return steamGamesCache; + } + const response = await axios.get( `${import.meta.env.MAIN_VITE_EXTERNAL_RESOURCES_URL}/steam-games-by-letter.json` ); - return response.data; + const formattedData: FormattedSteamGamesByLetter = {}; + for (const [letter, games] of Object.entries(response.data)) { + formattedData[letter] = games.map((game) => ({ + ...game, + formattedName: formatName(game.name), + })); + } + + steamGamesCache = formattedData; + steamGamesCacheTime = now; + return formattedData; }; type SublevelIterator = AsyncIterable<[string, { id: number }]>; @@ -52,33 +113,61 @@ interface SublevelWithId { iterator: () => SublevelIterator; } +let maxRepackId: number | null = null; +let maxDownloadSourceId: number | null = null; + const getNextId = async (sublevel: SublevelWithId): Promise => { + const isRepackSublevel = sublevel === repacksSublevel; + const isDownloadSourceSublevel = sublevel === downloadSourcesSublevel; + + if (isRepackSublevel && maxRepackId !== null) { + return ++maxRepackId; + } + + if (isDownloadSourceSublevel && maxDownloadSourceId !== null) { + return ++maxDownloadSourceId; + } + let maxId = 0; for await (const [, value] of sublevel.iterator()) { if (value.id > maxId) { maxId = value.id; } } + + if (isRepackSublevel) { + maxRepackId = maxId; + } else if (isDownloadSourceSublevel) { + maxDownloadSourceId = maxId; + } + return maxId + 1; }; +export const invalidateIdCaches = () => { + maxRepackId = null; + maxDownloadSourceId = null; +}; + const addNewDownloads = async ( downloadSource: { id: number; name: string }, downloads: z.infer["downloads"], - steamGames: SteamGamesByLetter + steamGames: FormattedSteamGamesByLetter ) => { const now = new Date(); const objectIdsOnSource = new Set(); let nextRepackId = await getNextId(repacksSublevel); + const batch = repacksSublevel.batch(); + for (const download of downloads) { const formattedTitle = formatRepackName(download.title); const [firstLetter] = formattedTitle; const games = steamGames[firstLetter] || []; const gamesInSteam = games.filter((game) => - formattedTitle.startsWith(formatName(game.name)) + formattedTitle.startsWith(game.formattedName) ); if (gamesInSteam.length === 0) continue; @@ -100,9 +189,11 @@ const addNewDownloads = async ( updatedAt: now, }; - await repacksSublevel.put(`${repack.id}`, repack); + batch.put(`${repack.id}`, repack); } + await batch.write(); + const existingSource = await downloadSourcesSublevel.get( `${downloadSource.id}` ); @@ -134,14 +225,6 @@ export const importDownloadSourceToLocal = async ( const now = new Date(); - const urlExistsBeforeInsert = await checkUrlExists(url); - if (urlExistsBeforeInsert) { - if (throwOnDuplicate) { - throw new Error("Download source with this URL already exists"); - } - return null; - } - const nextId = await getNextId(downloadSourcesSublevel); const downloadSource = { @@ -158,6 +241,8 @@ export const importDownloadSourceToLocal = async ( await downloadSourcesSublevel.put(`${downloadSource.id}`, downloadSource); + invalidateDownloadSourcesCache(); + const objectIds = await addNewDownloads( downloadSource, response.data.downloads, diff --git a/src/main/events/download-sources/sync-download-sources-from-api.ts b/src/main/events/download-sources/sync-download-sources-from-api.ts index 21b5a097..23769839 100644 --- a/src/main/events/download-sources/sync-download-sources-from-api.ts +++ b/src/main/events/download-sources/sync-download-sources-from-api.ts @@ -1,6 +1,5 @@ import { HydraApi } from "@main/services"; -import { downloadSourcesSublevel } from "@main/level"; -import { importDownloadSourceToLocal } from "./helpers"; +import { importDownloadSourceToLocal, checkUrlExists } from "./helpers"; export const syncDownloadSourcesFromApi = async () => { try { @@ -8,15 +7,9 @@ export const syncDownloadSourcesFromApi = async () => { { url: string; createdAt: string; updatedAt: string }[] >("/profile/download-sources"); - const localSources: { url: string }[] = []; - for await (const [, source] of downloadSourcesSublevel.iterator()) { - localSources.push(source); - } - - const localUrls = new Set(localSources.map((s) => s.url)); - for (const apiSource of apiSources) { - if (!localUrls.has(apiSource.url)) { + const exists = await checkUrlExists(apiSource.url); + if (!exists) { await importDownloadSourceToLocal(apiSource.url, false); } } diff --git a/src/main/events/download-sources/sync-download-sources.ts b/src/main/events/download-sources/sync-download-sources.ts index 8611aca9..19ab8079 100644 --- a/src/main/events/download-sources/sync-download-sources.ts +++ b/src/main/events/download-sources/sync-download-sources.ts @@ -3,6 +3,11 @@ import axios, { AxiosError } from "axios"; import { z } from "zod"; import { downloadSourcesSublevel, repacksSublevel } from "@main/level"; import { DownloadSourceStatus } from "@shared"; +import { + checkUrlExists, + invalidateDownloadSourcesCache, + invalidateIdCaches, +} from "./helpers"; const downloadSourceSchema = z.object({ name: z.string().max(255), @@ -17,6 +22,8 @@ const downloadSourceSchema = z.object({ }); type SteamGamesByLetter = Record; +type FormattedSteamGame = { id: string; name: string; formattedName: string }; +type FormattedSteamGamesByLetter = Record; const formatName = (name: string) => { return name @@ -30,21 +37,31 @@ const formatRepackName = (name: string) => { return formatName(name.replace("[DL]", "")); }; -const checkUrlExists = async (url: string): Promise => { - for await (const [, source] of downloadSourcesSublevel.iterator()) { - if (source.url === url) { - return true; - } - } - return false; -}; +let steamGamesCache: FormattedSteamGamesByLetter | null = null; +let steamGamesCacheTime = 0; +const STEAM_GAMES_CACHE_TTL = 300000; + +const getSteamGames = async (): Promise => { + const now = Date.now(); + if (steamGamesCache && now - steamGamesCacheTime < STEAM_GAMES_CACHE_TTL) { + return steamGamesCache; + } -const getSteamGames = async () => { const response = await axios.get( `${import.meta.env.MAIN_VITE_EXTERNAL_RESOURCES_URL}/steam-games-by-letter.json` ); - return response.data; + const formattedData: FormattedSteamGamesByLetter = {}; + for (const [letter, games] of Object.entries(response.data)) { + formattedData[letter] = games.map((game) => ({ + ...game, + formattedName: formatName(game.name), + })); + } + + steamGamesCache = formattedData; + steamGamesCacheTime = now; + return formattedData; }; type SublevelIterator = AsyncIterable<[string, { id: number }]>; @@ -53,33 +70,56 @@ interface SublevelWithId { iterator: () => SublevelIterator; } +let maxRepackId: number | null = null; +let maxDownloadSourceId: number | null = null; + const getNextId = async (sublevel: SublevelWithId): Promise => { + const isRepackSublevel = sublevel === repacksSublevel; + const isDownloadSourceSublevel = sublevel === downloadSourcesSublevel; + + if (isRepackSublevel && maxRepackId !== null) { + return ++maxRepackId; + } + + if (isDownloadSourceSublevel && maxDownloadSourceId !== null) { + return ++maxDownloadSourceId; + } + let maxId = 0; for await (const [, value] of sublevel.iterator()) { if (value.id > maxId) { maxId = value.id; } } + + if (isRepackSublevel) { + maxRepackId = maxId; + } else if (isDownloadSourceSublevel) { + maxDownloadSourceId = maxId; + } + return maxId + 1; }; const addNewDownloads = async ( downloadSource: { id: number; name: string }, downloads: z.infer["downloads"], - steamGames: SteamGamesByLetter + steamGames: FormattedSteamGamesByLetter ) => { const now = new Date(); const objectIdsOnSource = new Set(); let nextRepackId = await getNextId(repacksSublevel); + const batch = repacksSublevel.batch(); + for (const download of downloads) { const formattedTitle = formatRepackName(download.title); const [firstLetter] = formattedTitle; const games = steamGames[firstLetter] || []; const gamesInSteam = games.filter((game) => - formattedTitle.startsWith(formatName(game.name)) + formattedTitle.startsWith(game.formattedName) ); if (gamesInSteam.length === 0) continue; @@ -101,9 +141,11 @@ const addNewDownloads = async ( updatedAt: now, }; - await repacksSublevel.put(`${repack.id}`, repack); + batch.put(`${repack.id}`, repack); } + await batch.write(); + const existingSource = await downloadSourcesSublevel.get( `${downloadSource.id}` ); @@ -131,6 +173,9 @@ const deleteDownloadSource = async (id: number) => { await batch.write(); await downloadSourcesSublevel.del(`${id}`); + + invalidateDownloadSourcesCache(); + invalidateIdCaches(); }; const importDownloadSource = async (url: string) => { @@ -145,11 +190,6 @@ const importDownloadSource = async (url: string) => { const now = new Date(); - const urlExistsBeforeInsert = await checkUrlExists(url); - if (urlExistsBeforeInsert) { - return; - } - const nextId = await getNextId(downloadSourcesSublevel); const downloadSource = { @@ -166,6 +206,8 @@ const importDownloadSource = async (url: string) => { await downloadSourcesSublevel.put(`${downloadSource.id}`, downloadSource); + invalidateDownloadSourcesCache(); + await addNewDownloads(downloadSource, response.data.downloads, steamGames); }; From aba206452fc0f81d303feb456668147ca4634c30 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Tue, 14 Oct 2025 14:56:47 +0100 Subject: [PATCH 085/197] feat: improving gallery slider --- .../game-details/gallery-slider/gallery-slider.scss | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/renderer/src/pages/game-details/gallery-slider/gallery-slider.scss b/src/renderer/src/pages/game-details/gallery-slider/gallery-slider.scss index d1ae2481..6f9e753c 100644 --- a/src/renderer/src/pages/game-details/gallery-slider/gallery-slider.scss +++ b/src/renderer/src/pages/game-details/gallery-slider/gallery-slider.scss @@ -25,11 +25,6 @@ overflow: hidden; border-radius: 8px; - @media (min-width: 1024px) { - width: 80%; - max-height: 400px; - } - @media (min-width: 1280px) { width: 60%; max-height: 500px; @@ -72,10 +67,6 @@ overflow-y: hidden; gap: calc(globals.$spacing-unit / 2); - @media (min-width: 1024px) { - width: 80%; - } - @media (min-width: 1280px) { width: 60%; } From a4cc35fc200e2ca147fd3a3ed8cfa1e976ed4910 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Tue, 14 Oct 2025 11:27:40 -0300 Subject: [PATCH 086/197] chore: update i18n strings --- src/locales/en/translation.json | 4 ++-- src/locales/pt-BR/translation.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index daf6feed..d8a3df70 100755 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -248,11 +248,11 @@ "review_deletion_failed": "Failed to delete review. Please try again.", "loading_reviews": "Loading reviews...", "loading_more_reviews": "Loading more reviews...", - "load_more_reviews": "Load More Reviews", + "load_more_reviews": "Load more reviews", "you_seemed_to_enjoy_this_game": "You've seemed to enjoy this game", "would_you_recommend_this_game": "Would you like to leave a review to this game?", "yes": "Yes", - "maybe_later": "Maybe Later", + "maybe_later": "Maybe later", "cloud_save": "Cloud save", "cloud_save_description": "Save your progress in the cloud and continue playing on any device", "backups": "Backups", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 91a10149..90328a28 100755 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -334,11 +334,11 @@ "review_deletion_failed": "Falha ao excluir avaliação. Por favor, tente novamente.", "loading_reviews": "Carregando avaliações...", "loading_more_reviews": "Carregando mais avaliações...", - "load_more_reviews": "Carregar Mais Avaliações", + "load_more_reviews": "Carregar mais avaliações", "you_seemed_to_enjoy_this_game": "Parece que você gostou deste jogo", "would_you_recommend_this_game": "Gostaria de deixar uma avaliação para este jogo?", "yes": "Sim", - "maybe_later": "Talvez Mais Tarde", + "maybe_later": "Talvez mais tarde", "delete_review": "Excluir avaliação", "remove_review": "Remover Avaliação", "delete_review_modal_title": "Tem certeza de que deseja excluir sua avaliação?", From 311555386e87a0e9131c3b014cac90a9055147fb Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Tue, 14 Oct 2025 15:49:02 +0100 Subject: [PATCH 087/197] fix: fixing bug with sources --- .../delete-all-download-sources.ts | 5 + .../delete-download-source.ts | 5 + src/main/events/download-sources/helpers.ts | 68 +++++++- .../download-sources/sync-download-sources.ts | 149 ++++++------------ 4 files changed, 122 insertions(+), 105 deletions(-) diff --git a/src/main/events/download-sources/delete-all-download-sources.ts b/src/main/events/download-sources/delete-all-download-sources.ts index ec333e20..cdb781b7 100644 --- a/src/main/events/download-sources/delete-all-download-sources.ts +++ b/src/main/events/download-sources/delete-all-download-sources.ts @@ -1,10 +1,15 @@ import { registerEvent } from "../register-event"; import { downloadSourcesSublevel, repacksSublevel } from "@main/level"; +import { invalidateDownloadSourcesCache, invalidateIdCaches } from "./helpers"; const deleteAllDownloadSources = async ( _event: Electron.IpcMainInvokeEvent ) => { await Promise.all([repacksSublevel.clear(), downloadSourcesSublevel.clear()]); + + // Invalidate caches after clearing all sources + invalidateDownloadSourcesCache(); + invalidateIdCaches(); }; registerEvent("deleteAllDownloadSources", deleteAllDownloadSources); diff --git a/src/main/events/download-sources/delete-download-source.ts b/src/main/events/download-sources/delete-download-source.ts index d6fc4fda..72de8746 100644 --- a/src/main/events/download-sources/delete-download-source.ts +++ b/src/main/events/download-sources/delete-download-source.ts @@ -1,5 +1,6 @@ import { registerEvent } from "../register-event"; import { downloadSourcesSublevel, repacksSublevel } from "@main/level"; +import { invalidateDownloadSourcesCache, invalidateIdCaches } from "./helpers"; const deleteDownloadSource = async ( _event: Electron.IpcMainInvokeEvent, @@ -20,6 +21,10 @@ const deleteDownloadSource = async ( await batch.write(); await downloadSourcesSublevel.del(`${id}`); + + // Invalidate caches after deletion + invalidateDownloadSourcesCache(); + invalidateIdCaches(); }; registerEvent("deleteDownloadSource", deleteDownloadSource); diff --git a/src/main/events/download-sources/helpers.ts b/src/main/events/download-sources/helpers.ts index b0c6841d..95fdebe3 100644 --- a/src/main/events/download-sources/helpers.ts +++ b/src/main/events/download-sources/helpers.ts @@ -163,19 +163,52 @@ const addNewDownloads = async ( for (const download of downloads) { const formattedTitle = formatRepackName(download.title); - const [firstLetter] = formattedTitle; - const games = steamGames[firstLetter] || []; + let gamesInSteam: FormattedSteamGame[] = []; - const gamesInSteam = games.filter((game) => - formattedTitle.startsWith(game.formattedName) - ); + // Only try to match if we have a valid formatted title + if (formattedTitle && formattedTitle.length > 0) { + const [firstLetter] = formattedTitle; + const games = steamGames[firstLetter] || []; - if (gamesInSteam.length === 0) continue; + // Try exact prefix match first + gamesInSteam = games.filter((game) => + formattedTitle.startsWith(game.formattedName) + ); + // If no exact prefix match, try contains match (more lenient) + if (gamesInSteam.length === 0) { + gamesInSteam = games.filter( + (game) => + formattedTitle.includes(game.formattedName) || + game.formattedName.includes(formattedTitle) + ); + } + + // If still no match, try checking all letters (not just first letter) + // This helps with repacks that use abbreviations or alternate naming + if (gamesInSteam.length === 0) { + for (const letter of Object.keys(steamGames)) { + const letterGames = steamGames[letter] || []; + const matches = letterGames.filter( + (game) => + formattedTitle.includes(game.formattedName) || + game.formattedName.includes(formattedTitle) + ); + if (matches.length > 0) { + gamesInSteam = matches; + break; + } + } + } + } + + // Add matched game IDs to source tracking for (const game of gamesInSteam) { objectIdsOnSource.add(String(game.id)); } + // Create the repack even if no games matched + // This ensures all repacks from sources are imported const repack = { id: nextRepackId++, objectIds: gamesInSteam.map((game) => String(game.id)), @@ -254,3 +287,26 @@ export const importDownloadSourceToLocal = async ( objectIds, }; }; + +export const updateDownloadSourcePreservingTimestamp = async ( + existingSource: DownloadSource, + url: string +) => { + const response = await axios.get>(url); + + const updatedSource = { + ...existingSource, + name: response.data.name, + etag: response.headers["etag"] || null, + status: DownloadSourceStatus.UpToDate, + downloadCount: response.data.downloads.length, + updatedAt: new Date(), + // Preserve the original createdAt timestamp + }; + + await downloadSourcesSublevel.put(`${existingSource.id}`, updatedSource); + + invalidateDownloadSourcesCache(); + + return updatedSource; +}; diff --git a/src/main/events/download-sources/sync-download-sources.ts b/src/main/events/download-sources/sync-download-sources.ts index 19ab8079..4e3837ba 100644 --- a/src/main/events/download-sources/sync-download-sources.ts +++ b/src/main/events/download-sources/sync-download-sources.ts @@ -3,11 +3,7 @@ import axios, { AxiosError } from "axios"; import { z } from "zod"; import { downloadSourcesSublevel, repacksSublevel } from "@main/level"; import { DownloadSourceStatus } from "@shared"; -import { - checkUrlExists, - invalidateDownloadSourcesCache, - invalidateIdCaches, -} from "./helpers"; +import { invalidateDownloadSourcesCache } from "./helpers"; const downloadSourceSchema = z.object({ name: z.string().max(255), @@ -157,60 +153,6 @@ const addNewDownloads = async ( } }; -const deleteDownloadSource = async (id: number) => { - const repacksToDelete: string[] = []; - - for await (const [key, repack] of repacksSublevel.iterator()) { - if (repack.downloadSourceId === id) { - repacksToDelete.push(key); - } - } - - const batch = repacksSublevel.batch(); - for (const key of repacksToDelete) { - batch.del(key); - } - await batch.write(); - - await downloadSourcesSublevel.del(`${id}`); - - invalidateDownloadSourcesCache(); - invalidateIdCaches(); -}; - -const importDownloadSource = async (url: string) => { - const urlExists = await checkUrlExists(url); - if (urlExists) { - return; - } - - const response = await axios.get>(url); - - const steamGames = await getSteamGames(); - - const now = new Date(); - - const nextId = await getNextId(downloadSourcesSublevel); - - const downloadSource = { - id: nextId, - url, - name: response.data.name, - etag: response.headers["etag"] || null, - status: DownloadSourceStatus.UpToDate, - downloadCount: response.data.downloads.length, - objectIds: [], - createdAt: now, - updatedAt: now, - }; - - await downloadSourcesSublevel.put(`${downloadSource.id}`, downloadSource); - - invalidateDownloadSourcesCache(); - - await addNewDownloads(downloadSource, response.data.downloads, steamGames); -}; - const syncDownloadSources = async ( _event: Electron.IpcMainInvokeEvent ): Promise => { @@ -249,57 +191,66 @@ const syncDownloadSources = async ( existingRepacks.push(repack); } - if (downloadSources.some((source) => !source.fingerprint)) { - await Promise.all( - downloadSources.map(async (source) => { - await deleteDownloadSource(source.id); - await importDownloadSource(source.url); - }) - ); - } else { - for (const downloadSource of downloadSources) { - const headers: Record = {}; + // Handle sources with missing fingerprints individually, don't delete all sources + const sourcesWithFingerprints = downloadSources.filter( + (source) => source.fingerprint + ); + const sourcesWithoutFingerprints = downloadSources.filter( + (source) => !source.fingerprint + ); - if (downloadSource.etag) { - headers["If-None-Match"] = downloadSource.etag; - } + // For sources without fingerprints, just continue with normal sync + // They will get fingerprints updated later by updateMissingFingerprints + const allSourcesToSync = [ + ...sourcesWithFingerprints, + ...sourcesWithoutFingerprints, + ]; - try { - const response = await axios.get(downloadSource.url, { - headers, - }); + for (const downloadSource of allSourcesToSync) { + const headers: Record = {}; - const source = downloadSourceSchema.parse(response.data); - const steamGames = await getSteamGames(); + if (downloadSource.etag) { + headers["If-None-Match"] = downloadSource.etag; + } - const repacks = source.downloads.filter( - (download) => - !existingRepacks.some((repack) => repack.title === download.title) - ); + try { + const response = await axios.get(downloadSource.url, { + headers, + }); - await downloadSourcesSublevel.put(`${downloadSource.id}`, { - ...downloadSource, - etag: response.headers["etag"] || null, - downloadCount: source.downloads.length, - status: DownloadSourceStatus.UpToDate, - }); + const source = downloadSourceSchema.parse(response.data); + const steamGames = await getSteamGames(); - await addNewDownloads(downloadSource, repacks, steamGames); + const repacks = source.downloads.filter( + (download) => + !existingRepacks.some((repack) => repack.title === download.title) + ); - newRepacksCount += repacks.length; - } catch (err: unknown) { - const isNotModified = (err as AxiosError).response?.status === 304; + await downloadSourcesSublevel.put(`${downloadSource.id}`, { + ...downloadSource, + etag: response.headers["etag"] || null, + downloadCount: source.downloads.length, + status: DownloadSourceStatus.UpToDate, + }); - await downloadSourcesSublevel.put(`${downloadSource.id}`, { - ...downloadSource, - status: isNotModified - ? DownloadSourceStatus.UpToDate - : DownloadSourceStatus.Errored, - }); - } + await addNewDownloads(downloadSource, repacks, steamGames); + + newRepacksCount += repacks.length; + } catch (err: unknown) { + const isNotModified = (err as AxiosError).response?.status === 304; + + await downloadSourcesSublevel.put(`${downloadSource.id}`, { + ...downloadSource, + status: isNotModified + ? DownloadSourceStatus.UpToDate + : DownloadSourceStatus.Errored, + }); } } + // Invalidate cache after all sync operations complete + invalidateDownloadSourcesCache(); + return newRepacksCount; } catch (err) { return -1; From 41227b125e18f94e0d09840fc9199dc2a8081725 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Tue, 14 Oct 2025 17:42:59 +0100 Subject: [PATCH 088/197] feat: improving performance on sources --- .../download-sources/add-download-source.ts | 57 ++++++++++++++++++- src/main/events/download-sources/helpers.ts | 3 + .../download-sources/sync-download-sources.ts | 50 +++++++++++++--- 3 files changed, 100 insertions(+), 10 deletions(-) diff --git a/src/main/events/download-sources/add-download-source.ts b/src/main/events/download-sources/add-download-source.ts index 81d40cd7..801b0635 100644 --- a/src/main/events/download-sources/add-download-source.ts +++ b/src/main/events/download-sources/add-download-source.ts @@ -1,7 +1,10 @@ import { registerEvent } from "../register-event"; -import { downloadSourcesSublevel } from "@main/level"; +import { downloadSourcesSublevel, repacksSublevel } from "@main/level"; import { HydraApi } from "@main/services"; -import { importDownloadSourceToLocal } from "./helpers"; +import { + importDownloadSourceToLocal, + invalidateDownloadSourcesCache, +} from "./helpers"; const addDownloadSource = async ( _event: Electron.IpcMainInvokeEvent, @@ -12,6 +15,28 @@ const addDownloadSource = async ( throw new Error("Failed to import download source"); } + // Verify that repacks were actually written to the database (read-after-write) + // This ensures all async operations are complete before proceeding + let repackCount = 0; + const repackIds: number[] = []; + for await (const [, repack] of repacksSublevel.iterator()) { + if (repack.downloadSourceId === result.id) { + repackCount++; + repackIds.push(repack.id); + } + } + + // Log for debugging - helps identify if repacks are being created + console.log( + `✅ Download source ${result.id} (${result.name}) created with ${repackCount} repacks` + ); + console.log( + ` Repack IDs: [${repackIds.slice(0, 5).join(", ")}${repackIds.length > 5 ? "..." : ""}]` + ); + console.log( + ` Object IDs: [${result.objectIds.slice(0, 5).join(", ")}${result.objectIds.length > 5 ? "..." : ""}]` + ); + await HydraApi.post("/profile/download-sources", { urls: [url], }); @@ -24,6 +49,7 @@ const addDownloadSource = async ( { needsAuth: false } ); + // Update the source with fingerprint const updatedSource = await downloadSourcesSublevel.get(`${result.id}`); if (updatedSource) { await downloadSourcesSublevel.put(`${result.id}`, { @@ -33,6 +59,33 @@ const addDownloadSource = async ( }); } + // Final verification: ensure the source with fingerprint is persisted + const finalSource = await downloadSourcesSublevel.get(`${result.id}`); + if (!finalSource || !finalSource.fingerprint) { + throw new Error("Failed to persist download source with fingerprint"); + } + + // Verify repacks still exist after fingerprint update + let finalRepackCount = 0; + for await (const [, repack] of repacksSublevel.iterator()) { + if (repack.downloadSourceId === result.id) { + finalRepackCount++; + } + } + + if (finalRepackCount !== repackCount) { + console.warn( + `⚠️ Repack count mismatch! Before: ${repackCount}, After: ${finalRepackCount}` + ); + } else { + console.log( + `✅ Final verification passed: ${finalRepackCount} repacks confirmed` + ); + } + + // Invalidate cache to ensure fresh data on next read + invalidateDownloadSourcesCache(); + return { ...result, fingerprint, diff --git a/src/main/events/download-sources/helpers.ts b/src/main/events/download-sources/helpers.ts index 95fdebe3..7f346fc0 100644 --- a/src/main/events/download-sources/helpers.ts +++ b/src/main/events/download-sources/helpers.ts @@ -282,6 +282,9 @@ export const importDownloadSourceToLocal = async ( steamGames ); + // Invalidate ID caches after creating new repacks to prevent ID collisions + invalidateIdCaches(); + return { ...downloadSource, objectIds, diff --git a/src/main/events/download-sources/sync-download-sources.ts b/src/main/events/download-sources/sync-download-sources.ts index 4e3837ba..b567ca63 100644 --- a/src/main/events/download-sources/sync-download-sources.ts +++ b/src/main/events/download-sources/sync-download-sources.ts @@ -3,7 +3,7 @@ import axios, { AxiosError } from "axios"; import { z } from "zod"; import { downloadSourcesSublevel, repacksSublevel } from "@main/level"; import { DownloadSourceStatus } from "@shared"; -import { invalidateDownloadSourcesCache } from "./helpers"; +import { invalidateDownloadSourcesCache, invalidateIdCaches } from "./helpers"; const downloadSourceSchema = z.object({ name: z.string().max(255), @@ -111,19 +111,52 @@ const addNewDownloads = async ( for (const download of downloads) { const formattedTitle = formatRepackName(download.title); - const [firstLetter] = formattedTitle; - const games = steamGames[firstLetter] || []; + let gamesInSteam: FormattedSteamGame[] = []; - const gamesInSteam = games.filter((game) => - formattedTitle.startsWith(game.formattedName) - ); + // Only try to match if we have a valid formatted title + if (formattedTitle && formattedTitle.length > 0) { + const [firstLetter] = formattedTitle; + const games = steamGames[firstLetter] || []; - if (gamesInSteam.length === 0) continue; + // Try exact prefix match first + gamesInSteam = games.filter((game) => + formattedTitle.startsWith(game.formattedName) + ); + // If no exact prefix match, try contains match (more lenient) + if (gamesInSteam.length === 0) { + gamesInSteam = games.filter( + (game) => + formattedTitle.includes(game.formattedName) || + game.formattedName.includes(formattedTitle) + ); + } + + // If still no match, try checking all letters (not just first letter) + // This helps with repacks that use abbreviations or alternate naming + if (gamesInSteam.length === 0) { + for (const letter of Object.keys(steamGames)) { + const letterGames = steamGames[letter] || []; + const matches = letterGames.filter( + (game) => + formattedTitle.includes(game.formattedName) || + game.formattedName.includes(formattedTitle) + ); + if (matches.length > 0) { + gamesInSteam = matches; + break; + } + } + } + } + + // Add matched game IDs to source tracking for (const game of gamesInSteam) { objectIdsOnSource.add(String(game.id)); } + // Create the repack even if no games matched + // This ensures all repacks from sources are imported const repack = { id: nextRepackId++, objectIds: gamesInSteam.map((game) => String(game.id)), @@ -248,8 +281,9 @@ const syncDownloadSources = async ( } } - // Invalidate cache after all sync operations complete + // Invalidate caches after all sync operations complete invalidateDownloadSourcesCache(); + invalidateIdCaches(); return newRepacksCount; } catch (err) { From 136a44473f94b2c7038aaa295750107034b72b78 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Tue, 14 Oct 2025 19:26:39 +0100 Subject: [PATCH 089/197] feat: adding o1 cache --- src/locales/en/translation.json | 1 + src/locales/pt-BR/translation.json | 8 ++ src/locales/pt-PT/translation.json | 8 ++ src/main/events/download-sources/helpers.ts | 136 ++++++++++++++---- .../download-sources/sync-download-sources.ts | 135 +++++++++++++---- .../sidebar-adding-custom-game-modal.scss | 5 +- .../profile/profile-content/sort-options.tsx | 2 +- 7 files changed, 232 insertions(+), 63 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index c91a1296..066fd8a8 100755 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -599,6 +599,7 @@ "activity": "Recent Activity", "library": "Library", "pinned": "Pinned", + "sort_by": "Sort by:", "achievements_earned": "Achievements earned", "played_recently": "Played recently", "playtime": "Playtime", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index ef34ee6f..c578a91c 100755 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -592,10 +592,18 @@ "user_profile": { "amount_hours": "{{amount}} horas", "amount_minutes": "{{amount}} minutos", + "amount_hours_short": "{{amount}}h", + "amount_minutes_short": "{{amount}}m", "last_time_played": "Última sessão {{period}}", "activity": "Atividades recentes", "library": "Biblioteca", + "pinned": "Fixados", + "sort_by": "Ordenar por:", + "achievements_earned": "Conquistas obtidas", + "played_recently": "Jogados recentemente", + "playtime": "Tempo de jogo", "total_play_time": "Tempo total de jogo", + "manual_playtime_tooltip": "Este tempo de jogo foi atualizado manualmente", "no_recent_activity_title": "Hmmm… nada por aqui", "no_recent_activity_description": "Parece que você não jogou nada recentemente. Que tal começar agora?", "display_name": "Nome de exibição", diff --git a/src/locales/pt-PT/translation.json b/src/locales/pt-PT/translation.json index 7c244b6e..962504d4 100644 --- a/src/locales/pt-PT/translation.json +++ b/src/locales/pt-PT/translation.json @@ -377,10 +377,18 @@ "user_profile": { "amount_hours": "{{amount}} horas", "amount_minutes": "{{amount}} minutos", + "amount_hours_short": "{{amount}}h", + "amount_minutes_short": "{{amount}}m", "last_time_played": "Última sessão {{period}}", "activity": "Atividade recente", "library": "Biblioteca", + "pinned": "Fixados", + "sort_by": "Ordenar por:", + "achievements_earned": "Conquistas obtidas", + "played_recently": "Jogados recentemente", + "playtime": "Tempo de jogo", "total_play_time": "Tempo total de jogo", + "manual_playtime_tooltip": "Este tempo de jogo foi atualizado manualmente", "no_recent_activity_title": "Hmmm… não há nada por aqui", "no_recent_activity_description": "Parece que não jogaste nada recentemente. Que tal começar agora?", "display_name": "Nome de apresentação", diff --git a/src/main/events/download-sources/helpers.ts b/src/main/events/download-sources/helpers.ts index 7f346fc0..7f49499d 100644 --- a/src/main/events/download-sources/helpers.ts +++ b/src/main/events/download-sources/helpers.ts @@ -2,6 +2,7 @@ import axios from "axios"; import { z } from "zod"; import { downloadSourcesSublevel, repacksSublevel } from "@main/level"; import { DownloadSourceStatus } from "@shared"; +import crypto from "crypto"; const downloadSourceSchema = z.object({ name: z.string().max(255), @@ -15,6 +16,46 @@ const downloadSourceSchema = z.object({ ), }); +// Pre-computed title-to-Steam-ID mapping +type TitleHashMapping = Record; +let titleHashMappingCache: TitleHashMapping | null = null; +let titleHashMappingCacheTime = 0; +const TITLE_HASH_MAPPING_TTL = 86400000; // 24 hours + +const getTitleHashMapping = async (): Promise => { + const now = Date.now(); + if ( + titleHashMappingCache && + now - titleHashMappingCacheTime < TITLE_HASH_MAPPING_TTL + ) { + return titleHashMappingCache; + } + + try { + const response = await axios.get( + "https://cdn.losbroxas.org/results_a4c50f70c2.json", + { + timeout: 10000, + } + ); + + titleHashMappingCache = response.data; + titleHashMappingCacheTime = now; + console.log( + `✅ Loaded title hash mapping with ${Object.keys(response.data).length} entries` + ); + return response.data; + } catch (error) { + console.error("Failed to fetch title hash mapping:", error); + // Return empty mapping on error - will fall back to fuzzy matching + return {}; + } +}; + +const hashTitle = (title: string): string => { + return crypto.createHash("sha256").update(title).digest("hex"); +}; + type SteamGamesByLetter = Record; type FormattedSteamGame = { id: string; name: string; formattedName: string }; type FormattedSteamGamesByLetter = Record; @@ -161,57 +202,89 @@ const addNewDownloads = async ( const batch = repacksSublevel.batch(); + // Fetch the pre-computed hash mapping + const titleHashMapping = await getTitleHashMapping(); + let hashMatchCount = 0; + let fuzzyMatchCount = 0; + let noMatchCount = 0; + for (const download of downloads) { - const formattedTitle = formatRepackName(download.title); - let gamesInSteam: FormattedSteamGame[] = []; + let objectIds: string[] = []; + let usedHashMatch = false; - // Only try to match if we have a valid formatted title - if (formattedTitle && formattedTitle.length > 0) { - const [firstLetter] = formattedTitle; - const games = steamGames[firstLetter] || []; + // FIRST: Try hash-based lookup (fast and accurate) + const titleHash = hashTitle(download.title); + const steamIdsFromHash = titleHashMapping[titleHash]; - // Try exact prefix match first - gamesInSteam = games.filter((game) => - formattedTitle.startsWith(game.formattedName) - ); + if (steamIdsFromHash && steamIdsFromHash.length > 0) { + // Found in hash mapping - trust it completely + hashMatchCount++; + usedHashMatch = true; - // If no exact prefix match, try contains match (more lenient) - if (gamesInSteam.length === 0) { - gamesInSteam = games.filter( - (game) => - formattedTitle.includes(game.formattedName) || - game.formattedName.includes(formattedTitle) + // Use the Steam IDs directly as strings (trust the hash mapping) + objectIds = steamIdsFromHash.map(String); + } + + // FALLBACK: Use fuzzy matching ONLY if hash lookup found nothing + if (!usedHashMatch) { + let gamesInSteam: FormattedSteamGame[] = []; + const formattedTitle = formatRepackName(download.title); + + if (formattedTitle && formattedTitle.length > 0) { + const [firstLetter] = formattedTitle; + const games = steamGames[firstLetter] || []; + + // Try exact prefix match first + gamesInSteam = games.filter((game) => + formattedTitle.startsWith(game.formattedName) ); - } - // If still no match, try checking all letters (not just first letter) - // This helps with repacks that use abbreviations or alternate naming - if (gamesInSteam.length === 0) { - for (const letter of Object.keys(steamGames)) { - const letterGames = steamGames[letter] || []; - const matches = letterGames.filter( + // If no exact prefix match, try contains match (more lenient) + if (gamesInSteam.length === 0) { + gamesInSteam = games.filter( (game) => formattedTitle.includes(game.formattedName) || game.formattedName.includes(formattedTitle) ); - if (matches.length > 0) { - gamesInSteam = matches; - break; + } + + // If still no match, try checking all letters (not just first letter) + if (gamesInSteam.length === 0) { + for (const letter of Object.keys(steamGames)) { + const letterGames = steamGames[letter] || []; + const matches = letterGames.filter( + (game) => + formattedTitle.includes(game.formattedName) || + game.formattedName.includes(formattedTitle) + ); + if (matches.length > 0) { + gamesInSteam = matches; + break; + } } } + + if (gamesInSteam.length > 0) { + fuzzyMatchCount++; + objectIds = gamesInSteam.map((game) => String(game.id)); + } else { + noMatchCount++; + } + } else { + noMatchCount++; } } // Add matched game IDs to source tracking - for (const game of gamesInSteam) { - objectIdsOnSource.add(String(game.id)); + for (const id of objectIds) { + objectIdsOnSource.add(id); } // Create the repack even if no games matched // This ensures all repacks from sources are imported const repack = { id: nextRepackId++, - objectIds: gamesInSteam.map((game) => String(game.id)), + objectIds: objectIds, title: download.title, uris: download.uris, fileSize: download.fileSize, @@ -227,6 +300,11 @@ const addNewDownloads = async ( await batch.write(); + // Log matching statistics + console.log( + `📊 Matching stats for ${downloadSource.name}: Hash=${hashMatchCount}, Fuzzy=${fuzzyMatchCount}, None=${noMatchCount}` + ); + const existingSource = await downloadSourcesSublevel.get( `${downloadSource.id}` ); diff --git a/src/main/events/download-sources/sync-download-sources.ts b/src/main/events/download-sources/sync-download-sources.ts index b567ca63..75a50459 100644 --- a/src/main/events/download-sources/sync-download-sources.ts +++ b/src/main/events/download-sources/sync-download-sources.ts @@ -4,6 +4,7 @@ import { z } from "zod"; import { downloadSourcesSublevel, repacksSublevel } from "@main/level"; import { DownloadSourceStatus } from "@shared"; import { invalidateDownloadSourcesCache, invalidateIdCaches } from "./helpers"; +import crypto from "crypto"; const downloadSourceSchema = z.object({ name: z.string().max(255), @@ -17,6 +18,45 @@ const downloadSourceSchema = z.object({ ), }); +// Pre-computed title-to-Steam-ID mapping (shared with helpers.ts) +type TitleHashMapping = Record; +let titleHashMappingCache: TitleHashMapping | null = null; +let titleHashMappingCacheTime = 0; +const TITLE_HASH_MAPPING_TTL = 86400000; // 24 hours + +const getTitleHashMapping = async (): Promise => { + const now = Date.now(); + if ( + titleHashMappingCache && + now - titleHashMappingCacheTime < TITLE_HASH_MAPPING_TTL + ) { + return titleHashMappingCache; + } + + try { + const response = await axios.get( + "https://cdn.losbroxas.org/results_a4c50f70c2.json", + { + timeout: 10000, + } + ); + + titleHashMappingCache = response.data; + titleHashMappingCacheTime = now; + console.log( + `✅ Loaded title hash mapping with ${Object.keys(response.data).length} entries` + ); + return response.data; + } catch (error) { + console.error("Failed to fetch title hash mapping:", error); + return {}; + } +}; + +const hashTitle = (title: string): string => { + return crypto.createHash("sha256").update(title).digest("hex"); +}; + type SteamGamesByLetter = Record; type FormattedSteamGame = { id: string; name: string; formattedName: string }; type FormattedSteamGamesByLetter = Record; @@ -109,57 +149,89 @@ const addNewDownloads = async ( const batch = repacksSublevel.batch(); + // Fetch the pre-computed hash mapping + const titleHashMapping = await getTitleHashMapping(); + let hashMatchCount = 0; + let fuzzyMatchCount = 0; + let noMatchCount = 0; + for (const download of downloads) { - const formattedTitle = formatRepackName(download.title); - let gamesInSteam: FormattedSteamGame[] = []; + let objectIds: string[] = []; + let usedHashMatch = false; - // Only try to match if we have a valid formatted title - if (formattedTitle && formattedTitle.length > 0) { - const [firstLetter] = formattedTitle; - const games = steamGames[firstLetter] || []; + // FIRST: Try hash-based lookup (fast and accurate) + const titleHash = hashTitle(download.title); + const steamIdsFromHash = titleHashMapping[titleHash]; - // Try exact prefix match first - gamesInSteam = games.filter((game) => - formattedTitle.startsWith(game.formattedName) - ); + if (steamIdsFromHash && steamIdsFromHash.length > 0) { + // Found in hash mapping - trust it completely + hashMatchCount++; + usedHashMatch = true; - // If no exact prefix match, try contains match (more lenient) - if (gamesInSteam.length === 0) { - gamesInSteam = games.filter( - (game) => - formattedTitle.includes(game.formattedName) || - game.formattedName.includes(formattedTitle) + // Use the Steam IDs directly as strings (trust the hash mapping) + objectIds = steamIdsFromHash.map(String); + } + + // FALLBACK: Use fuzzy matching ONLY if hash lookup found nothing + if (!usedHashMatch) { + let gamesInSteam: FormattedSteamGame[] = []; + const formattedTitle = formatRepackName(download.title); + + if (formattedTitle && formattedTitle.length > 0) { + const [firstLetter] = formattedTitle; + const games = steamGames[firstLetter] || []; + + // Try exact prefix match first + gamesInSteam = games.filter((game) => + formattedTitle.startsWith(game.formattedName) ); - } - // If still no match, try checking all letters (not just first letter) - // This helps with repacks that use abbreviations or alternate naming - if (gamesInSteam.length === 0) { - for (const letter of Object.keys(steamGames)) { - const letterGames = steamGames[letter] || []; - const matches = letterGames.filter( + // If no exact prefix match, try contains match (more lenient) + if (gamesInSteam.length === 0) { + gamesInSteam = games.filter( (game) => formattedTitle.includes(game.formattedName) || game.formattedName.includes(formattedTitle) ); - if (matches.length > 0) { - gamesInSteam = matches; - break; + } + + // If still no match, try checking all letters (not just first letter) + if (gamesInSteam.length === 0) { + for (const letter of Object.keys(steamGames)) { + const letterGames = steamGames[letter] || []; + const matches = letterGames.filter( + (game) => + formattedTitle.includes(game.formattedName) || + game.formattedName.includes(formattedTitle) + ); + if (matches.length > 0) { + gamesInSteam = matches; + break; + } } } + + if (gamesInSteam.length > 0) { + fuzzyMatchCount++; + objectIds = gamesInSteam.map((game) => String(game.id)); + } else { + noMatchCount++; + } + } else { + noMatchCount++; } } // Add matched game IDs to source tracking - for (const game of gamesInSteam) { - objectIdsOnSource.add(String(game.id)); + for (const id of objectIds) { + objectIdsOnSource.add(id); } // Create the repack even if no games matched // This ensures all repacks from sources are imported const repack = { id: nextRepackId++, - objectIds: gamesInSteam.map((game) => String(game.id)), + objectIds: objectIds, title: download.title, uris: download.uris, fileSize: download.fileSize, @@ -175,6 +247,11 @@ const addNewDownloads = async ( await batch.write(); + // Log matching statistics + console.log( + `📊 Matching stats for ${downloadSource.name}: Hash=${hashMatchCount}, Fuzzy=${fuzzyMatchCount}, None=${noMatchCount}` + ); + const existingSource = await downloadSourcesSublevel.get( `${downloadSource.id}` ); diff --git a/src/renderer/src/components/sidebar/sidebar-adding-custom-game-modal.scss b/src/renderer/src/components/sidebar/sidebar-adding-custom-game-modal.scss index 942384fe..1a8ca315 100644 --- a/src/renderer/src/components/sidebar/sidebar-adding-custom-game-modal.scss +++ b/src/renderer/src/components/sidebar/sidebar-adding-custom-game-modal.scss @@ -5,10 +5,7 @@ display: flex; flex-direction: column; gap: calc(globals.$spacing-unit * 3); - width: 100%; - max-width: 500px; - margin: 0 auto; - text-align: center; + min-width: 500px; } &__form { diff --git a/src/renderer/src/pages/profile/profile-content/sort-options.tsx b/src/renderer/src/pages/profile/profile-content/sort-options.tsx index 53da8e40..607e54b9 100644 --- a/src/renderer/src/pages/profile/profile-content/sort-options.tsx +++ b/src/renderer/src/pages/profile/profile-content/sort-options.tsx @@ -14,7 +14,7 @@ export function SortOptions({ sortBy, onSortChange }: SortOptionsProps) { return (
    - Sort by: + {t("sort_by")}
    - } - placeholder="API Key" - hint={ - - - - } - /> - )} - - ); -} diff --git a/src/renderer/src/pages/settings/settings-debrid.tsx b/src/renderer/src/pages/settings/settings-debrid.tsx index f4ac183a..a2214698 100644 --- a/src/renderer/src/pages/settings/settings-debrid.tsx +++ b/src/renderer/src/pages/settings/settings-debrid.tsx @@ -2,7 +2,6 @@ import { useState, useCallback, useMemo } from "react"; import { useFeature, useAppSelector } from "@renderer/hooks"; import { SettingsTorBox } from "./settings-torbox"; import { SettingsRealDebrid } from "./settings-real-debrid"; -import { SettingsAllDebrid } from "./settings-all-debrid"; import { motion, AnimatePresence } from "framer-motion"; import { ChevronRightIcon, CheckCircleFillIcon } from "@primer/octicons-react"; import { useTranslation } from "react-i18next"; @@ -11,7 +10,6 @@ import "./settings-debrid.scss"; interface CollapseState { torbox: boolean; realDebrid: boolean; - allDebrid: boolean; } const sectionVariants = { @@ -71,7 +69,6 @@ export function SettingsDebrid() { return { torbox: !userPreferences?.torBoxApiToken, realDebrid: !userPreferences?.realDebridApiToken, - allDebrid: !userPreferences?.allDebridApiKey, }; }, [userPreferences]); @@ -178,51 +175,6 @@ export function SettingsDebrid() {
    )} - -
    -
    - -

    All-Debrid

    - BETA - {userPreferences?.allDebridApiKey && ( - - )} -
    - - - {!collapseState.allDebrid && ( - - - - )} - -
    ); } diff --git a/src/shared/constants.ts b/src/shared/constants.ts index 352ffe12..851aec49 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -1,6 +1,5 @@ export enum Downloader { RealDebrid, - AllDebrid, Torrent, Gofile, PixelDrain, @@ -56,7 +55,6 @@ export enum AuthPage { export enum DownloadError { NotCachedOnRealDebrid = "download_error_not_cached_on_real_debrid", - NotCachedInAllDebrid = "download_error_not_cached_in_alldebrid", NotCachedOnTorBox = "download_error_not_cached_on_torbox", GofileQuotaExceeded = "download_error_gofile_quota_exceeded", RealDebridAccountNotAuthorized = "download_error_real_debrid_account_not_authorized", diff --git a/src/shared/index.ts b/src/shared/index.ts index 9a4b4516..615fa36e 100644 --- a/src/shared/index.ts +++ b/src/shared/index.ts @@ -124,7 +124,6 @@ export const getDownloadersForUri = (uri: string) => { Downloader.Hydra, Downloader.TorBox, Downloader.RealDebrid, - Downloader.AllDebrid, ]; } @@ -142,19 +141,6 @@ export const getDownloadersForUris = (uris: string[]) => { return Array.from(downloadersSet); }; -export const steamUrlBuilder = { - library: (objectId: string) => - `https://steamcdn-a.akamaihd.net/steam/apps/${objectId}/header.jpg`, - libraryHero: (objectId: string) => - `https://steamcdn-a.akamaihd.net/steam/apps/${objectId}/library_hero.jpg`, - logo: (objectId: string) => - `https://cdn.cloudflare.steamstatic.com/steam/apps/${objectId}/logo.png`, - cover: (objectId: string) => - `https://cdn.cloudflare.steamstatic.com/steam/apps/${objectId}/library_600x900.jpg`, - icon: (objectId: string, clientIcon: string) => - `https://cdn.cloudflare.steamstatic.com/steamcommunity/public/images/apps/${objectId}/${clientIcon}.ico`, -}; - export const getDateLocale = (language: string) => { if (language.startsWith("pt")) return ptBR; if (language.startsWith("es")) return es; diff --git a/src/types/download.types.ts b/src/types/download.types.ts index d19a3b83..004d8f27 100644 --- a/src/types/download.types.ts +++ b/src/types/download.types.ts @@ -175,11 +175,3 @@ export interface SeedingStatus { status: DownloadStatus; uploadSpeed: number; } - -/* All-Debrid */ -export interface AllDebridUser { - username: string; - email: string; - isPremium: boolean; - premiumUntil: string; -} diff --git a/src/types/level.types.ts b/src/types/level.types.ts index 93925068..053bd218 100644 --- a/src/types/level.types.ts +++ b/src/types/level.types.ts @@ -99,7 +99,6 @@ export interface UserPreferences { language?: string; realDebridApiToken?: string | null; torBoxApiToken?: string | null; - allDebridApiKey?: string | null; preferQuitInsteadOfHiding?: boolean; runAtStartup?: boolean; startMinimized?: boolean; From 074d9d4fe2112520dcfd43ba3536adece9072d8d Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Wed, 15 Oct 2025 15:59:03 +0100 Subject: [PATCH 091/197] feat: improving caching --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ad479c7f..342b078a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hydralauncher", - "version": "3.7.0", + "version": "3.7.1", "description": "Hydra", "main": "./out/main/index.js", "author": "Los Broxas", From 5639c09c2289d0cedd4a74e2f7f726183d703a7b Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Wed, 15 Oct 2025 16:02:50 +0100 Subject: [PATCH 092/197] feat: improving caching --- src/main/events/download-sources/add-download-source.ts | 2 -- src/main/events/download-sources/helpers.ts | 6 +++--- src/main/events/library/add-custom-game-to-library.ts | 2 +- src/main/events/library/copy-custom-game-asset.ts | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/main/events/download-sources/add-download-source.ts b/src/main/events/download-sources/add-download-source.ts index c3e30488..e51cae3e 100644 --- a/src/main/events/download-sources/add-download-source.ts +++ b/src/main/events/download-sources/add-download-source.ts @@ -15,11 +15,9 @@ const addDownloadSource = async ( // Verify that repacks were actually written to the database (read-after-write) // This ensures all async operations are complete before proceeding let repackCount = 0; - const repackIds: number[] = []; for await (const [, repack] of repacksSublevel.iterator()) { if (repack.downloadSourceId === result.id) { repackCount++; - repackIds.push(repack.id); } } diff --git a/src/main/events/download-sources/helpers.ts b/src/main/events/download-sources/helpers.ts index ca765c3f..2e7489fd 100644 --- a/src/main/events/download-sources/helpers.ts +++ b/src/main/events/download-sources/helpers.ts @@ -2,7 +2,7 @@ import axios from "axios"; import { z } from "zod"; import { downloadSourcesSublevel, repacksSublevel } from "@main/level"; import { DownloadSourceStatus } from "@shared"; -import crypto from "crypto"; +import crypto from "node:crypto"; import { logger, ResourceCache } from "@main/services"; export const downloadSourceSchema = z.object({ @@ -62,9 +62,9 @@ export type FormattedSteamGamesByLetter = Record; export const formatName = (name: string) => { return name .normalize("NFD") - .replace(/[\u0300-\u036f]/g, "") + .replaceAll(/[\u0300-\u036f]/g, "") .toLowerCase() - .replace(/[^a-z0-9]/g, ""); + .replaceAll(/[^a-z0-9]/g, ""); }; export const formatRepackName = (name: string) => { diff --git a/src/main/events/library/add-custom-game-to-library.ts b/src/main/events/library/add-custom-game-to-library.ts index f85c008b..f2f2dd40 100644 --- a/src/main/events/library/add-custom-game-to-library.ts +++ b/src/main/events/library/add-custom-game-to-library.ts @@ -1,6 +1,6 @@ import { registerEvent } from "../register-event"; import { gamesSublevel, gamesShopAssetsSublevel, levelKeys } from "@main/level"; -import { randomUUID } from "crypto"; +import { randomUUID } from "node:crypto"; import type { GameShop } from "@types"; const addCustomGameToLibrary = async ( diff --git a/src/main/events/library/copy-custom-game-asset.ts b/src/main/events/library/copy-custom-game-asset.ts index aef74da9..1f5aea0f 100644 --- a/src/main/events/library/copy-custom-game-asset.ts +++ b/src/main/events/library/copy-custom-game-asset.ts @@ -1,7 +1,7 @@ import { registerEvent } from "../register-event"; import fs from "node:fs"; import path from "node:path"; -import { randomUUID } from "crypto"; +import { randomUUID } from "node:crypto"; import { ASSETS_PATH } from "@main/constants"; const copyCustomGameAsset = async ( From e7ee049df559b1c214821b0c5118ec13e53dd85a Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Wed, 15 Oct 2025 16:39:59 +0100 Subject: [PATCH 093/197] ci: fixing env var --- src/main/services/resource-cache.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/services/resource-cache.ts b/src/main/services/resource-cache.ts index 0c44af81..c59f873d 100644 --- a/src/main/services/resource-cache.ts +++ b/src/main/services/resource-cache.ts @@ -134,7 +134,7 @@ export class ResourceCache { const resources = [ { name: "steam-games-by-letter", - url: `${process.env.MAIN_VITE_EXTERNAL_RESOURCES_URL}/steam-games-by-letter.json`, + url: `${import.meta.env.MAIN_VITE_EXTERNAL_RESOURCES_URL}/steam-games-by-letter.json`, }, { name: "sources-manifest", From 3dc71a8d1fb203d1403c07abcd0fa4e1806f80bb Mon Sep 17 00:00:00 2001 From: whintersnow0 Date: Wed, 15 Oct 2025 19:19:08 +0200 Subject: [PATCH 094/197] refactor: remove unnecessary useMemo hooks --- .../src/components/text-field/text-field.tsx | 52 +++++-------------- 1 file changed, 14 insertions(+), 38 deletions(-) diff --git a/src/renderer/src/components/text-field/text-field.tsx b/src/renderer/src/components/text-field/text-field.tsx index 1c4d54af..7c0cbb58 100644 --- a/src/renderer/src/components/text-field/text-field.tsx +++ b/src/renderer/src/components/text-field/text-field.tsx @@ -1,16 +1,13 @@ -import React, { useId, useMemo, useState } from "react"; +import React, { useId, useState } from "react"; import { EyeClosedIcon, EyeIcon } from "@primer/octicons-react"; import { useTranslation } from "react-i18next"; - import cn from "classnames"; - import "./text-field.scss"; -export interface TextFieldProps - extends React.DetailedHTMLProps< - React.InputHTMLAttributes, - HTMLInputElement - > { +export interface TextFieldProps extends React.DetailedHTMLProps< + React.InputHTMLAttributes, + HTMLInputElement +> { theme?: "primary" | "dark"; label?: string | React.ReactNode; hint?: string | React.ReactNode; @@ -42,44 +39,27 @@ export const TextField = React.forwardRef( ) => { const id = useId(); const [isFocused, setIsFocused] = useState(false); - const [isPasswordVisible, setIsPasswordVisible] = useState(false); - const { t } = useTranslation("forms"); - const showPasswordToggleButton = props.type === "password"; - - const inputType = useMemo(() => { - if (props.type === "password" && isPasswordVisible) return "text"; - return props.type ?? "text"; - }, [props.type, isPasswordVisible]); - - const hintContent = useMemo(() => { - if (error) - return ( - {error} - ); - - if (hint) return {hint}; - return null; - }, [hint, error]); - + const inputType = props.type === "password" && isPasswordVisible ? "text" : props.type ?? "text"; + const hintContent = error ? ( + {error} + ) : hint ? ( + {hint} + ) : null; const handleFocus: React.FocusEventHandler = (event) => { setIsFocused(true); - if (props.onFocus) props.onFocus(event); + props.onFocus?.(event); }; - const handleBlur: React.FocusEventHandler = (event) => { setIsFocused(false); - if (props.onBlur) props.onBlur(event); + props.onBlur?.(event); }; - const hasError = !!error; - return (
    {label && } -
    ( onBlur={handleBlur} type={inputType} /> - {showPasswordToggleButton && ( )}
    - {rightContent}
    - {hintContent}
    ); } ); - -TextField.displayName = "TextField"; +TextField.displayName = "TextField"; \ No newline at end of file From 97b27a17853bb13a62f0988042c99ae7b5e81d0c Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 16 Oct 2025 11:01:20 -0300 Subject: [PATCH 095/197] fix: handle user not found on user-profile.context --- .../user-profile/user-profile.context.tsx | 30 ++++++++----------- yarn.lock | 4 +-- 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/src/renderer/src/context/user-profile/user-profile.context.tsx b/src/renderer/src/context/user-profile/user-profile.context.tsx index cb656be2..87e2a669 100644 --- a/src/renderer/src/context/user-profile/user-profile.context.tsx +++ b/src/renderer/src/context/user-profile/user-profile.context.tsx @@ -66,10 +66,7 @@ export function UserProfileContextProvider({ const isMe = userDetails?.id === userProfile?.id; const getHeroBackgroundFromImageUrl = async (imageUrl: string) => { - const output = await average(imageUrl, { - amount: 1, - format: "hex", - }); + const output = await average(imageUrl, { amount: 1, format: "hex" }); return `linear-gradient(135deg, ${darkenColor(output as string, 0.5)}, ${darkenColor(output as string, 0.6, 0.5)})`; }; @@ -135,28 +132,25 @@ export function UserProfileContextProvider({ getUserLibraryGames(); return window.electron.hydraApi - .get(`/users/${userId}`) + .get(`/users/${userId}`) .then((userProfile) => { - if (userProfile) { - setUserProfile(userProfile); + setUserProfile(userProfile); - if (userProfile.profileImageUrl) { - getHeroBackgroundFromImageUrl(userProfile.profileImageUrl).then( - (color) => setHeroBackground(color) - ); - } - } else { - showErrorToast(t("user_not_found")); - navigate(-1); + if (userProfile.profileImageUrl) { + getHeroBackgroundFromImageUrl(userProfile.profileImageUrl).then( + (color) => setHeroBackground(color) + ); } + }) + .catch(() => { + showErrorToast(t("user_not_found")); + navigate(-1); }); }, [navigate, getUserStats, getUserLibraryGames, showErrorToast, userId, t]); const getBadges = useCallback(async () => { const language = i18n.language.split("-")[0]; - const params = new URLSearchParams({ - locale: language, - }); + const params = new URLSearchParams({ locale: language }); const badges = await window.electron.hydraApi.get( `/badges?${params.toString()}`, diff --git a/yarn.lock b/yarn.lock index ebd10beb..0337a77b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1047,9 +1047,9 @@ optionalDependencies: global-agent "^3.0.0" -"@electron/node-gyp@git+https://github.com/electron/node-gyp.git#06b29aafb7708acef8b3669835c8a7857ebc92d2": +"@electron/node-gyp@https://github.com/electron/node-gyp#06b29aafb7708acef8b3669835c8a7857ebc92d2": version "10.2.0-electron.1" - resolved "git+https://github.com/electron/node-gyp.git#06b29aafb7708acef8b3669835c8a7857ebc92d2" + resolved "https://github.com/electron/node-gyp#06b29aafb7708acef8b3669835c8a7857ebc92d2" dependencies: env-paths "^2.2.0" exponential-backoff "^3.1.1" From 24f7ecb7957f43d83af1576a7ee44eb24bae4865 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Fri, 17 Oct 2025 15:04:42 +0100 Subject: [PATCH 096/197] feat: adding translations --- src/locales/en/translation.json | 6 +- src/locales/pt-BR/translation.json | 4 + src/locales/ru/translation.json | 4 + .../src/pages/game-details/game-reviews.tsx | 13 +-- .../src/pages/game-details/review-item.scss | 23 +++++ .../src/pages/game-details/review-item.tsx | 84 +++++++++++++++++-- src/types/index.ts | 4 + 7 files changed, 124 insertions(+), 14 deletions(-) create mode 100644 src/renderer/src/pages/game-details/review-item.scss diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index bf2793c5..46bdb28c 100755 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -357,7 +357,11 @@ "delete_review_modal_description": "This action cannot be undone.", "delete_review_modal_delete_button": "Delete", "delete_review_modal_cancel_button": "Cancel", - "vote_failed": "Failed to register your vote. Please try again." + "vote_failed": "Failed to register your vote. Please try again.", + "show_original": "Show original", + "show_translation": "Show translation", + "show_original_translated_from": "Show original (translated from {{language}})", + "hide_original": "Hide original" }, "activation": { "title": "Activate Hydra", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index c969e3bf..4ea77015 100755 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -345,6 +345,10 @@ "delete_review_modal_description": "Esta ação não pode ser desfeita.", "delete_review_modal_delete_button": "Excluir", "delete_review_modal_cancel_button": "Cancelar", + "show_original": "Mostrar original", + "show_translation": "Mostrar tradução", + "show_original_translated_from": "Mostrar original (traduzido do {{language}})", + "hide_original": "Ocultar original", "rating_count": "Avaliação" }, "activation": { diff --git a/src/locales/ru/translation.json b/src/locales/ru/translation.json index 895db29d..886c7d07 100644 --- a/src/locales/ru/translation.json +++ b/src/locales/ru/translation.json @@ -259,6 +259,10 @@ "delete_review_modal_description": "Это действие нельзя отменить.", "delete_review_modal_delete_button": "Удалить", "delete_review_modal_cancel_button": "Отмена", + "show_original": "Показать оригинал", + "show_translation": "Показать перевод", + "show_original_translated_from": "Показать оригинал (переведено с {{language}})", + "hide_original": "Скрыть оригинал", "cloud_save": "Облачное сохранение", "cloud_save_description": "Сохраняйте ваш прогресс в облаке и продолжайте играть на любом устройстве", "backups": "Резервные копии", diff --git a/src/renderer/src/pages/game-details/game-reviews.tsx b/src/renderer/src/pages/game-details/game-reviews.tsx index 851852b2..f8117f43 100644 --- a/src/renderer/src/pages/game-details/game-reviews.tsx +++ b/src/renderer/src/pages/game-details/game-reviews.tsx @@ -39,7 +39,7 @@ export function GameReviews({ hasUserReviewed, onUserReviewedChange, }: Readonly) { - const { t } = useTranslation("game_details"); + const { t, i18n } = useTranslation("game_details"); const { showSuccessToast, showErrorToast } = useToast(); const [reviews, setReviews] = useState([]); @@ -129,9 +129,7 @@ export function GameReviews({ const twoHoursInMilliseconds = 2 * 60 * 60 * 1000; const hasEnoughPlaytime = - game && - game.playTimeInMilliseconds >= twoHoursInMilliseconds && - !game.hasManuallyUpdatedPlaytime; + game && game.playTimeInMilliseconds >= twoHoursInMilliseconds; if ( !hasReviewed && @@ -146,6 +144,8 @@ export function GameReviews({ } }, [objectId, userDetailsId, shop, game, onUserReviewedChange]); + console.log("reviews", reviews); + const loadReviews = useCallback( async (reset = false) => { if (!objectId) return; @@ -164,6 +164,7 @@ export function GameReviews({ take: "20", skip: skip.toString(), sortBy: reviewsSortBy, + language: i18n.language, }); const response = await window.electron.hydraApi.get( @@ -200,7 +201,7 @@ export function GameReviews({ } } }, - [objectId, shop, reviewsPage, reviewsSortBy] + [objectId, shop, reviewsPage, reviewsSortBy, i18n.language] ); const handleVoteReview = async ( @@ -439,6 +440,8 @@ export function GameReviews({ }); }, [reviews]); + console.log("reviews", reviews); + return (
    {showReviewPrompt && diff --git a/src/renderer/src/pages/game-details/review-item.scss b/src/renderer/src/pages/game-details/review-item.scss new file mode 100644 index 00000000..e1651ef5 --- /dev/null +++ b/src/renderer/src/pages/game-details/review-item.scss @@ -0,0 +1,23 @@ +@use "../../scss/globals.scss"; + +.game-details { + &__review-translation-toggle { + display: inline-flex; + align-items: center; + gap: calc(globals.$spacing-unit * 1); + margin-top: calc(globals.$spacing-unit * 1.5); + padding: 0; + background: none; + border: none; + color: rgba(255, 255, 255, 0.6); + font-size: 0.875rem; + cursor: pointer; + text-decoration: none; + transition: all 0.2s ease; + + &:hover { + text-decoration: underline; + color: rgba(255, 255, 255, 0.9); + } + } +} diff --git a/src/renderer/src/pages/game-details/review-item.tsx b/src/renderer/src/pages/game-details/review-item.tsx index 85c81e70..f5e3528a 100644 --- a/src/renderer/src/pages/game-details/review-item.tsx +++ b/src/renderer/src/pages/game-details/review-item.tsx @@ -1,8 +1,9 @@ import { TrashIcon, ClockIcon } from "@primer/octicons-react"; -import { ThumbsUp, ThumbsDown, Star } from "lucide-react"; +import { ThumbsUp, ThumbsDown, Star, Languages } from "lucide-react"; import { useNavigate } from "react-router-dom"; import { motion, AnimatePresence } from "framer-motion"; import { useTranslation } from "react-i18next"; +import { useState } from "react"; import type { GameReview } from "@types"; import { sanitizeHtml } from "@shared"; @@ -10,6 +11,8 @@ import { useDate } from "@renderer/hooks"; import { formatNumber } from "@renderer/helpers"; import { Avatar } from "@renderer/components"; +import "./review-item.scss"; + interface ReviewItemProps { review: GameReview; userDetailsId?: string; @@ -63,9 +66,45 @@ export function ReviewItem({ onAnimationComplete, }: Readonly) { const navigate = useNavigate(); - const { t } = useTranslation("game_details"); + const { t, i18n } = useTranslation("game_details"); const { formatDistance } = useDate(); + const [showOriginal, setShowOriginal] = useState(false); + + // Check if this is the user's own review + const isOwnReview = userDetailsId === review.user.id; + + // Helper to get base language code (e.g., "pt" from "pt-BR") + const getBaseLanguage = (lang: string) => lang.split("-")[0]; + + // Check if the review is in a different language (comparing base language codes) + const isDifferentLanguage = + getBaseLanguage(review.detectedLanguage) !== getBaseLanguage(i18n.language); + + // Check if translation is available and needed (but not for own reviews) + const needsTranslation = + !isOwnReview && + isDifferentLanguage && + review.translations && + review.translations[i18n.language]; + + // Get the full language name using Intl.DisplayNames + const getLanguageName = (languageCode: string) => { + try { + const displayNames = new Intl.DisplayNames([i18n.language], { + type: "language", + }); + return displayNames.of(languageCode) || languageCode.toUpperCase(); + } catch { + return languageCode.toUpperCase(); + } + }; + + // Determine which content to show - always show original for own reviews + const displayContent = needsTranslation + ? review.translations[i18n.language] + : review.reviewHtml; + if (isBlocked && !isVisible) { return (
    @@ -135,12 +174,41 @@ export function ReviewItem({ ))}
    -
    +
    +
    + {needsTranslation && ( + <> + + {showOriginal && ( +
    + )} + + )} +
    Date: Fri, 17 Oct 2025 15:33:10 +0100 Subject: [PATCH 097/197] feat: removing pre and code --- src/shared/html-sanitizer.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/shared/html-sanitizer.ts b/src/shared/html-sanitizer.ts index 8f3ae932..0969564d 100644 --- a/src/shared/html-sanitizer.ts +++ b/src/shared/html-sanitizer.ts @@ -39,6 +39,12 @@ export function sanitizeHtml(html: string): string { } } + // Remove code and pre tags but keep their text content + for (const el of tempDiv.querySelectorAll("code, pre")) { + const textNode = document.createTextNode(el.textContent || ""); + el.replaceWith(textNode); + } + for (const el of tempDiv.querySelectorAll("*")) { for (const attr of Array.from(el.attributes)) { const name = attr.name.toLowerCase(); From c2273dbf712842ef7f6241d8a68b154816bbb64a Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sat, 18 Oct 2025 14:07:44 +0100 Subject: [PATCH 098/197] feat: moving sources to worker --- src/main/events/download-sources/helpers.ts | 85 ++-------- .../download-sources/sync-download-sources.ts | 20 +-- src/main/main.ts | 4 + .../services/game-matcher-worker-manager.ts | 145 ++++++++++++++++ src/main/services/index.ts | 1 + src/main/workers/game-matcher-worker.ts | 158 ++++++++++++++++++ 6 files changed, 329 insertions(+), 84 deletions(-) create mode 100644 src/main/services/game-matcher-worker-manager.ts create mode 100644 src/main/workers/game-matcher-worker.ts diff --git a/src/main/events/download-sources/helpers.ts b/src/main/events/download-sources/helpers.ts index 2e7489fd..edd3878e 100644 --- a/src/main/events/download-sources/helpers.ts +++ b/src/main/events/download-sources/helpers.ts @@ -192,83 +192,30 @@ export const addNewDownloads = async ( const batch = repacksSublevel.batch(); + // Get title hash mapping and perform matching in worker thread const titleHashMapping = await getTitleHashMapping(); - let hashMatchCount = 0; - let fuzzyMatchCount = 0; - let noMatchCount = 0; - for (const download of downloads) { - let objectIds: string[] = []; - let usedHashMatch = false; + const { GameMatcherWorkerManager } = await import("@main/services"); + const matchResult = await GameMatcherWorkerManager.matchDownloads( + downloads, + steamGames, + titleHashMapping + ); - const titleHash = hashTitle(download.title); - const steamIdsFromHash = titleHashMapping[titleHash]; - - if (steamIdsFromHash && steamIdsFromHash.length > 0) { - hashMatchCount++; - usedHashMatch = true; - - objectIds = steamIdsFromHash.map(String); - } - - if (!usedHashMatch) { - let gamesInSteam: FormattedSteamGame[] = []; - const formattedTitle = formatRepackName(download.title); - - if (formattedTitle && formattedTitle.length > 0) { - const [firstLetter] = formattedTitle; - const games = steamGames[firstLetter] || []; - - gamesInSteam = games.filter((game) => - formattedTitle.startsWith(game.formattedName) - ); - - if (gamesInSteam.length === 0) { - gamesInSteam = games.filter( - (game) => - formattedTitle.includes(game.formattedName) || - game.formattedName.includes(formattedTitle) - ); - } - - if (gamesInSteam.length === 0) { - for (const letter of Object.keys(steamGames)) { - const letterGames = steamGames[letter] || []; - const matches = letterGames.filter( - (game) => - formattedTitle.includes(game.formattedName) || - game.formattedName.includes(formattedTitle) - ); - if (matches.length > 0) { - gamesInSteam = matches; - break; - } - } - } - - if (gamesInSteam.length > 0) { - fuzzyMatchCount++; - objectIds = gamesInSteam.map((game) => String(game.id)); - } else { - noMatchCount++; - } - } else { - noMatchCount++; - } - } - - for (const id of objectIds) { + // Process matched results and write to database + for (const matchedDownload of matchResult.matchedDownloads) { + for (const id of matchedDownload.objectIds) { objectIdsOnSource.add(id); } const repack = { id: nextRepackId++, - objectIds: objectIds, - title: download.title, - uris: download.uris, - fileSize: download.fileSize, + objectIds: matchedDownload.objectIds, + title: matchedDownload.title, + uris: matchedDownload.uris, + fileSize: matchedDownload.fileSize, repacker: downloadSource.name, - uploadDate: download.uploadDate, + uploadDate: matchedDownload.uploadDate, downloadSourceId: downloadSource.id, createdAt: now, updatedAt: now, @@ -280,7 +227,7 @@ export const addNewDownloads = async ( await batch.write(); logger.info( - `Matching stats for ${downloadSource.name}: Hash=${hashMatchCount}, Fuzzy=${fuzzyMatchCount}, None=${noMatchCount}` + `Matching stats for ${downloadSource.name}: Hash=${matchResult.stats.hashMatchCount}, Fuzzy=${matchResult.stats.fuzzyMatchCount}, None=${matchResult.stats.noMatchCount}` ); const existingSource = await downloadSourcesSublevel.get( diff --git a/src/main/events/download-sources/sync-download-sources.ts b/src/main/events/download-sources/sync-download-sources.ts index 88861074..3bb78f22 100644 --- a/src/main/events/download-sources/sync-download-sources.ts +++ b/src/main/events/download-sources/sync-download-sources.ts @@ -31,20 +31,10 @@ const syncDownloadSources = async ( downloadSources.push(source); } - const existingRepacks: Array<{ - id: number; - title: string; - uris: string[]; - repacker: string; - fileSize: string | null; - objectIds: string[]; - uploadDate: Date | string | null; - downloadSourceId: number; - createdAt: Date; - updatedAt: Date; - }> = []; + // Use a Set for O(1) lookups instead of O(n) with array.some() + const existingRepackTitles = new Set(); for await (const [, repack] of repacksSublevel.iterator()) { - existingRepacks.push(repack); + existingRepackTitles.add(repack.title); } // Handle sources with missing fingerprints individually, don't delete all sources @@ -77,9 +67,9 @@ const syncDownloadSources = async ( const source = downloadSourceSchema.parse(response.data); const steamGames = await getSteamGames(); + // O(1) lookup instead of O(n) - massive performance improvement const repacks = source.downloads.filter( - (download) => - !existingRepacks.some((repack) => repack.title === download.title) + (download) => !existingRepackTitles.has(download.title) ); await downloadSourcesSublevel.put(`${downloadSource.id}`, { diff --git a/src/main/main.ts b/src/main/main.ts index 5eecb101..e9b6187c 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -17,6 +17,7 @@ import { Lock, DeckyPlugin, ResourceCache, + GameMatcherWorkerManager, } from "@main/services"; export const loadState = async () => { @@ -25,6 +26,9 @@ export const loadState = async () => { ResourceCache.initialize(); await ResourceCache.updateResourcesOnStartup(); + // Initialize game matcher worker thread + GameMatcherWorkerManager.initialize(); + const userPreferences = await db.get( levelKeys.userPreferences, { diff --git a/src/main/services/game-matcher-worker-manager.ts b/src/main/services/game-matcher-worker-manager.ts new file mode 100644 index 00000000..b5d306c7 --- /dev/null +++ b/src/main/services/game-matcher-worker-manager.ts @@ -0,0 +1,145 @@ +import { Worker } from "worker_threads"; +import workerPath from "../workers/game-matcher-worker?modulePath"; + +interface WorkerMessage { + id: string; + data: unknown; +} + +interface WorkerResponse { + id: string; + success: boolean; + result?: unknown; + error?: string; +} + +export type TitleHashMapping = Record; + +export type FormattedSteamGame = { + id: string; + name: string; + formattedName: string; +}; +export type FormattedSteamGamesByLetter = Record; + +interface DownloadToMatch { + title: string; + uris: string[]; + uploadDate: string; + fileSize: string; +} + +interface MatchedDownload { + title: string; + uris: string[]; + uploadDate: string; + fileSize: string; + objectIds: string[]; + usedHashMatch: boolean; +} + +interface MatchResponse { + matchedDownloads: MatchedDownload[]; + stats: { + hashMatchCount: number; + fuzzyMatchCount: number; + noMatchCount: number; + }; +} + +export class GameMatcherWorkerManager { + private static worker: Worker | null = null; + private static messageId = 0; + private static pendingMessages = new Map< + string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + { resolve: (value: any) => void; reject: (error: Error) => void } + >(); + + public static initialize() { + if (this.worker) { + return; + } + + try { + console.log( + "[GameMatcherWorker] Initializing worker with path:", + workerPath + ); + + this.worker = new Worker(workerPath); + + this.worker.on("message", (response: WorkerResponse) => { + const pending = this.pendingMessages.get(response.id); + if (pending) { + if (response.success) { + pending.resolve(response.result); + } else { + pending.reject(new Error(response.error || "Unknown error")); + } + this.pendingMessages.delete(response.id); + } + }); + + this.worker.on("error", (error) => { + console.error("[GameMatcherWorker] Worker error:", error); + for (const [id, pending] of this.pendingMessages.entries()) { + pending.reject(error); + this.pendingMessages.delete(id); + } + }); + + this.worker.on("exit", (code) => { + if (code !== 0) { + console.error( + `[GameMatcherWorker] Worker stopped with exit code ${code}` + ); + } + this.worker = null; + for (const [id, pending] of this.pendingMessages.entries()) { + pending.reject(new Error("Worker exited unexpectedly")); + this.pendingMessages.delete(id); + } + }); + + console.log("[GameMatcherWorker] Worker initialized successfully"); + } catch (error) { + console.error("[GameMatcherWorker] Failed to initialize worker:", error); + throw error; + } + } + + private static sendMessage(data: unknown): Promise { + if (!this.worker) { + return Promise.reject(new Error("Worker not initialized")); + } + + const id = `msg_${++this.messageId}`; + const message: WorkerMessage = { id, data }; + + return new Promise((resolve, reject) => { + this.pendingMessages.set(id, { resolve, reject }); + this.worker!.postMessage(message); + }); + } + + public static async matchDownloads( + downloads: DownloadToMatch[], + steamGames: FormattedSteamGamesByLetter, + titleHashMapping: TitleHashMapping + ): Promise { + return this.sendMessage({ + downloads, + steamGames, + titleHashMapping, + }); + } + + public static terminate() { + if (this.worker) { + this.worker.terminate(); + this.worker = null; + this.pendingMessages.clear(); + } + } +} diff --git a/src/main/services/index.ts b/src/main/services/index.ts index c98f09e1..0853859f 100644 --- a/src/main/services/index.ts +++ b/src/main/services/index.ts @@ -19,3 +19,4 @@ export * from "./wine"; export * from "./lock"; export * from "./decky-plugin"; export * from "./resource-cache"; +export * from "./game-matcher-worker-manager"; diff --git a/src/main/workers/game-matcher-worker.ts b/src/main/workers/game-matcher-worker.ts new file mode 100644 index 00000000..4930ada0 --- /dev/null +++ b/src/main/workers/game-matcher-worker.ts @@ -0,0 +1,158 @@ +import { parentPort } from "worker_threads"; +import crypto from "node:crypto"; + +export type TitleHashMapping = Record; + +export type FormattedSteamGame = { + id: string; + name: string; + formattedName: string; +}; +export type FormattedSteamGamesByLetter = Record; + +interface DownloadToMatch { + title: string; + uris: string[]; + uploadDate: string; + fileSize: string; +} + +interface MatchedDownload { + title: string; + uris: string[]; + uploadDate: string; + fileSize: string; + objectIds: string[]; + usedHashMatch: boolean; +} + +interface MatchRequest { + downloads: DownloadToMatch[]; + steamGames: FormattedSteamGamesByLetter; + titleHashMapping: TitleHashMapping; +} + +interface MatchResponse { + matchedDownloads: MatchedDownload[]; + stats: { + hashMatchCount: number; + fuzzyMatchCount: number; + noMatchCount: number; + }; +} + +const hashTitle = (title: string): string => { + return crypto.createHash("sha256").update(title).digest("hex"); +}; + +const formatName = (name: string) => { + return name + .normalize("NFD") + .replaceAll(/[\u0300-\u036f]/g, "") + .toLowerCase() + .replaceAll(/[^a-z0-9]/g, ""); +}; + +const formatRepackName = (name: string) => { + return formatName(name.replace("[DL]", "")); +}; + +const matchDownloads = (request: MatchRequest): MatchResponse => { + const { downloads, steamGames, titleHashMapping } = request; + const matchedDownloads: MatchedDownload[] = []; + + let hashMatchCount = 0; + let fuzzyMatchCount = 0; + let noMatchCount = 0; + + for (const download of downloads) { + let objectIds: string[] = []; + let usedHashMatch = false; + + const titleHash = hashTitle(download.title); + const steamIdsFromHash = titleHashMapping[titleHash]; + + if (steamIdsFromHash && steamIdsFromHash.length > 0) { + hashMatchCount++; + usedHashMatch = true; + objectIds = steamIdsFromHash.map(String); + } + + if (!usedHashMatch) { + let gamesInSteam: FormattedSteamGame[] = []; + const formattedTitle = formatRepackName(download.title); + + if (formattedTitle && formattedTitle.length > 0) { + const [firstLetter] = formattedTitle; + const games = steamGames[firstLetter] || []; + + gamesInSteam = games.filter((game) => + formattedTitle.startsWith(game.formattedName) + ); + + if (gamesInSteam.length === 0) { + gamesInSteam = games.filter( + (game) => + formattedTitle.includes(game.formattedName) || + game.formattedName.includes(formattedTitle) + ); + } + + if (gamesInSteam.length === 0) { + for (const letter of Object.keys(steamGames)) { + const letterGames = steamGames[letter] || []; + const matches = letterGames.filter( + (game) => + formattedTitle.includes(game.formattedName) || + game.formattedName.includes(formattedTitle) + ); + if (matches.length > 0) { + gamesInSteam = matches; + break; + } + } + } + + if (gamesInSteam.length > 0) { + fuzzyMatchCount++; + objectIds = gamesInSteam.map((game) => String(game.id)); + } else { + noMatchCount++; + } + } else { + noMatchCount++; + } + } + + matchedDownloads.push({ + ...download, + objectIds, + usedHashMatch, + }); + } + + return { + matchedDownloads, + stats: { + hashMatchCount, + fuzzyMatchCount, + noMatchCount, + }, + }; +}; + +// Message handler +if (parentPort) { + parentPort.on("message", (message: { id: string; data: MatchRequest }) => { + try { + const result = matchDownloads(message.data); + parentPort!.postMessage({ id: message.id, success: true, result }); + } catch (error) { + parentPort!.postMessage({ + id: message.id, + success: false, + error: error instanceof Error ? error.message : String(error), + }); + } + }); +} From 19a57cb1e012cf7a18aecc9b49a083385b872109 Mon Sep 17 00:00:00 2001 From: Domas Linkis <159886328+slakgosh@users.noreply.github.com> Date: Sun, 19 Oct 2025 13:30:10 +0500 Subject: [PATCH 099/197] Added ukrainian lang --- src/locales/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/locales/index.ts b/src/locales/index.ts index f71e8f0e..3ce1592c 100644 --- a/src/locales/index.ts +++ b/src/locales/index.ts @@ -26,6 +26,7 @@ import nb from "./nb/translation.json"; import et from "./et/translation.json"; import bg from "./bg/translation.json"; import uz from "./uz/translation.json"; +import ua from "./ua/translation.json"; import sv from "./sv/translation.json"; export default { @@ -42,6 +43,7 @@ export default { ru, tr, be, + ua, uk, zh, id, From 2f1185bbf9a1a9486a0a3470fb32ac54ab3101d5 Mon Sep 17 00:00:00 2001 From: Domas Linkis <159886328+slakgosh@users.noreply.github.com> Date: Sun, 19 Oct 2025 13:38:04 +0500 Subject: [PATCH 100/197] Finnish Language --- src/locales/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/locales/index.ts b/src/locales/index.ts index 3ce1592c..a44480e6 100644 --- a/src/locales/index.ts +++ b/src/locales/index.ts @@ -26,7 +26,7 @@ import nb from "./nb/translation.json"; import et from "./et/translation.json"; import bg from "./bg/translation.json"; import uz from "./uz/translation.json"; -import ua from "./ua/translation.json"; +import fi from "./fi/translation.json"; import sv from "./sv/translation.json"; export default { @@ -43,7 +43,6 @@ export default { ru, tr, be, - ua, uk, zh, id, @@ -51,6 +50,7 @@ export default { da, ar, fa, + fi, ro, ca, bg, From 5ff15b30b2e15561866c31640860b16f703b3467 Mon Sep 17 00:00:00 2001 From: Domas Linkis <159886328+slakgosh@users.noreply.github.com> Date: Sun, 19 Oct 2025 13:47:42 +0500 Subject: [PATCH 101/197] Added finnish lang Worstest 2 hours of my life --- src/locales/fi/translation.json | 708 ++++++++++++++++++++++++++++++++ 1 file changed, 708 insertions(+) create mode 100644 src/locales/fi/translation.json diff --git a/src/locales/fi/translation.json b/src/locales/fi/translation.json new file mode 100644 index 00000000..fee3ff22 --- /dev/null +++ b/src/locales/fi/translation.json @@ -0,0 +1,708 @@ +{ + "language_name": "Suomi", + "app": { + "successfully_signed_in": "Kirjautuminen onnistui" + }, + "home": { + "surprise_me": "Yllätä minut", + "no_results": "Ei tuloksia", + "start_typing": "Aloitan kirjoittamisen...", + "hot": "Suosittua nyt", + "weekly": "📅 Viikon parhaat pelit", + "achievements": "🏆 Pelit saavutuksilla" + }, + "sidebar": { + "catalogue": "Katalogi", + "downloads": "Lataukset", + "settings": "Asetukset", + "my_library": "Kirjasto", + "downloading_metadata": "{{title}} (Metatietojen lataus…)", + "paused": "{{title}} (Keskeytetty)", + "downloading": "{{title}} ({{percentage}} - Lataa…)", + "filter": "Hae", + "home": "Koti", + "queued": "{{title}} (Jonossa)", + "game_has_no_executable": "Pelin käynnistystiedostoa ei ole valittu", + "sign_in": "Kirjaudu sisään", + "friends": "Kaverit", + "need_help": "Tarvitsetko apua?", + "favorites": "Suosikit", + "playable_button_title": "Näytä vain asennetut pelit.", + "add_custom_game_tooltip": "Lisää mukautettu peli", + "show_playable_only_tooltip": "Näytä vain pelattavissa olevat", + "custom_game_modal": "Lisää mukautettu peli", + "custom_game_modal_description": "Lisää mukautettu peli kirjastoon valitsemalla suoritettava tiedosto", + "custom_game_modal_executable_path": "Suoritettavan tiedoston polku", + "custom_game_modal_select_executable": "Valitse suoritettava tiedosto", + "custom_game_modal_title": "Pelin nimi", + "custom_game_modal_enter_title": "Syötä pelin nimi", + "custom_game_modal_browse": "Selaa", + "custom_game_modal_cancel": "Peruuta", + "custom_game_modal_add": "Lisää peli", + "custom_game_modal_adding": "Lisätään peliä...", + "custom_game_modal_success": "Mukautettu peli lisätty onnistuneesti", + "custom_game_modal_failed": "Mukautetun pelin lisääminen epäonnistui", + "custom_game_modal_executable": "Suoritettava tiedosto", + "edit_game_modal": "Mukauta resursseja", + "edit_game_modal_description": "Mukauta pelin resursseja ja tietoja", + "edit_game_modal_title": "Nimi", + "edit_game_modal_enter_title": "Syötä nimi", + "edit_game_modal_image": "Kuva", + "edit_game_modal_select_image": "Valitse kuva", + "edit_game_modal_browse": "Selaa", + "edit_game_modal_image_preview": "Kuvan esikatselu", + "edit_game_modal_icon": "Kuvake", + "edit_game_modal_select_icon": "Valitse kuvake", + "edit_game_modal_icon_preview": "Kuvakkeen esikatselu", + "edit_game_modal_logo": "Logo", + "edit_game_modal_select_logo": "Valitse logo", + "edit_game_modal_logo_preview": "Logon esikatselu", + "edit_game_modal_hero": "Pelin kansikuva", + "edit_game_modal_select_hero": "Valitse pelin kansikuva", + "edit_game_modal_hero_preview": "Kansikuvan esikatselu", + "edit_game_modal_cancel": "Peruuta", + "edit_game_modal_update": "Päivitä", + "edit_game_modal_updating": "Päivitetään...", + "edit_game_modal_fill_required": "Täytä kaikki pakolliset kentät", + "edit_game_modal_success": "Resurssit päivitetty onnistuneesti", + "edit_game_modal_failed": "Resurssien päivitys epäonnistui", + "edit_game_modal_image_filter": "Kuva", + "edit_game_modal_icon_resolution": "Suositeltu resoluutio: 256x256px", + "edit_game_modal_logo_resolution": "Suositeltu resoluutio: 640x360px", + "edit_game_modal_hero_resolution": "Suositeltu resoluutio: 1920x620px", + "edit_game_modal_assets": "Resurssit", + "edit_game_modal_drop_icon_image_here": "Pudota kuvakkeen kuva tähän", + "edit_game_modal_drop_logo_image_here": "Pudota logon kuva tähän", + "edit_game_modal_drop_hero_image_here": "Pudota kansikuvan kuva tähän", + "edit_game_modal_drop_to_replace_icon": "Pudota korvataksesi kuvake", + "edit_game_modal_drop_to_replace_logo": "Pudota korvataksesi logo", + "edit_game_modal_drop_to_replace_hero": "Pudota korvataksesi kansikuva", + "install_decky_plugin": "Asenna Decky-lisäosa", + "update_decky_plugin": "Päivitä Decky-lisäosa", + "decky_plugin_installed_version": "Decky-lisäosa (v{{version}})", + "install_decky_plugin_title": "Asenna Hydra Decky -lisäosa", + "install_decky_plugin_message": "Tämä lataa ja asentaa Hydra-lisäosan Decky Loaderiin. Saattaa vaatia korotetut oikeudet. Jatketaanko?", + "update_decky_plugin_title": "Päivitä Hydra Decky -lisäosa", + "update_decky_plugin_message": "Uusi Hydra Decky -lisäosan versio on saatavilla. Haluatko päivittää sen nyt?", + "decky_plugin_installed": "Decky-lisäosa v{{version}} asennettu onnistuneesti", + "decky_plugin_installation_failed": "Decky-lisäosan asennus epäonnistui: {{error}}", + "decky_plugin_installation_error": "Decky-lisäosan asennusvirhe: {{error}}", + "confirm": "Vahvista", + "cancel": "Peruuta" + }, + "header": { + "search": "Hae", + "home": "Koti", + "catalogue": "Katalogi", + "downloads": "Lataukset", + "search_results": "Hakutulokset", + "settings": "Asetukset", + "version_available_install": "Versio {{version}} saatavilla. Asentaaksesi napsauta tästä.", + "version_available_download": "Versio {{version}} saatavilla. Ladataaksesi napsauta tästä." + }, + "bottom_panel": { + "no_downloads_in_progress": "Ei meneillään olevia latauksia", + "downloading_metadata": "Ladataan metatietoja {{title}}…", + "downloading": "Ladataan {{title}}… ({{percentage}} valmis) - Lopetus {{eta}} - {{speed}}", + "calculating_eta": "Ladataan {{title}}… ({{percentage}} valmis) - Lasketaan jäljellä olevaa aikaa…", + "checking_files": "Tarkistetaan tiedostoja {{title}}… ({{percentage}} valmis)", + "installing_common_redist": "{{log}}…", + "installation_complete": "Asennus valmis", + "installation_complete_message": "Kirjastot asennettu onnistuneesti" + }, + "catalogue": { + "search": "Suodatin…", + "developers": "Kehittäjät", + "genres": "Genret", + "tags": "Tagit", + "publishers": "Julkaisijat", + "download_sources": "Latauslähteet", + "result_count": "{{resultCount}} tulosta", + "filter_count": "{{filterCount}} saatavilla", + "clear_filters": "Tyhjennä {{filterCount}} valittua" + }, + "game_details": { + "open_download_options": "Avaa lähteet", + "download_options_zero": "Ei lähteitä", + "download_options_one": "{{count}} lähde", + "download_options_other": "{{count}} lähdettä", + "updated_at": "Päivitetty {{updated_at}}", + "install": "Asenna", + "resume": "Jatka", + "pause": "Keskeytä", + "cancel": "Peruuta", + "remove": "Poista", + "space_left_on_disk": "{{space}} vapaana levyltä", + "eta": "Lopetus {{eta}}", + "calculating_eta": "Lasketaan jäljellä olevaa aikaa…", + "downloading_metadata": "Ladataan metatietoja…", + "filter": "Hae repackeja", + "requirements": "Järjestelmävaatimukset", + "minimum": "Minimi", + "recommended": "Suositeltu", + "paused": "Keskeytetty", + "release_date": "Julkaistu {{date}}", + "publisher": "Julkaisija {{publisher}}", + "hours": "tuntia", + "minutes": "minuuttia", + "amount_hours": "{{amount}} tuntia", + "amount_minutes": "{{amount}} minuuttia", + "accuracy": "tarkkuus {{accuracy}}%", + "add_to_library": "Lisää kirjastoon", + "already_in_library": "Jo kirjastossa", + "remove_from_library": "Poista kirjastosta", + "no_downloads": "Ei saatavilla olevia lähteitä", + "play_time": "Pelattu {{amount}}", + "last_time_played": "Viimeksi pelattu {{period}}", + "not_played_yet": "Et ole vielä pelannut {{title}}", + "next_suggestion": "Seuraava ehdotus", + "play": "Pelaa", + "deleting": "Poistetaan asennustiedostoa…", + "close": "Sulje", + "playing_now": "Käynnissä", + "change": "Vaihda", + "repacks_modal_description": "Valitse repack ladattavaksi", + "select_folder_hint": "Vaihtaaksesi oletuslatauskansiota, avaa <0>Asetukset", + "download_now": "Lataa nyt", + "no_shop_details": "Kuvausta ei saatu", + "download_options": "Lähteet", + "download_path": "Latauspolku", + "previous_screenshot": "Edellinen kuvakaappaus", + "next_screenshot": "Seuraava kuvakaappaus", + "screenshot": "Kuvakaappaus {{number}}", + "open_screenshot": "Avaa kuvakaappaus {{number}}", + "download_settings": "Latausasetukset", + "downloader": "Lataaja", + "select_executable": "Valitse", + "no_executable_selected": "Tiedostoa ei valittu", + "open_folder": "Avaa kansio", + "open_download_location": "Selaa latauskansio", + "create_shortcut": "Luo työpöydän pikakuvake", + "create_shortcut_simple": "Luo pikakuvake", + "clear": "Tyhjennä", + "remove_files": "Poista tiedostot", + "remove_from_library_title": "Oletko varma?", + "remove_from_library_description": "{{game}} poistetaan kirjastostasi.", + "options": "Asetukset", + "properties": "Ominaisuudet", + "executable_section_title": "Tiedosto", + "executable_section_description": "Polku tiedostoon, joka käynnistetään kun painat \"Pelaa\"", + "downloads_section_title": "Lataukset", + "downloads_section_description": "Tarkista päivitysten tai muiden peliversioiden saatavuus", + "danger_zone_section_title": "Vaaravyöhyke", + "danger_zone_section_description": "Voit poistaa tämän pelin kirjastostasi tai Hydrasta ladatut tiedostot", + "download_in_progress": "Lataus käynnissä", + "download_paused": "Lataus keskeytetty", + "last_downloaded_option": "Viimeisin latausvaihtoehto", + "create_steam_shortcut": "Luo Steam-pikakuvake", + "create_shortcut_success": "Pikakuvake luotu", + "you_might_need_to_restart_steam": "Saattaa olla, että sinun on käynnistettävä Steam uudelleen nähdäksesi muutokset", + "create_shortcut_error": "Pikakuvakkeen luonti epäonnistui", + "add_to_favorites": "Lisää suosikkeihin", + "remove_from_favorites": "Poista suosikeista", + "failed_update_favorites": "Suosikkien päivitys epäonnistui", + "game_removed_from_library": "Peli poistettu kirjastosta", + "failed_remove_from_library": "Poistaminen kirjastosta epäonnistui", + "files_removed_success": "Tiedostot poistettu onnistuneesti", + "failed_remove_files": "Tiedostojen poisto epäonnistui", + "nsfw_content_title": "Tämä peli sisältää sopimatonta sisältöä", + "nsfw_content_description": "{{title}} sisältää sisältöä, joka ei välttämättä sovellu kaikenikäisille. \nOletko varma, että haluat jatkaa?", + "allow_nsfw_content": "Jatka", + "refuse_nsfw_content": "Takaisin", + "stats": "Tilastot", + "download_count": "Lataukset", + "player_count": "Aktiiviset pelaajat", + "download_error": "Tämä latausvaihtoehto ei ole saatavilla", + "download": "Lataa", + "executable_path_in_use": "Suoritettavaa tiedostoa käyttää jo \"{{game}}\"", + "warning": "Varoitus:", + "hydra_needs_to_remain_open": "Tämän latauksen aikana Hydran on pysyttävä auki, kunnes se on valmis. Jos Hydra sulkeutuu ennen valmistumista, menetät edistymisen.", + "achievements": "Saavutukset", + "achievements_count": "Saavutukset {{unlockedCount}}/{{achievementsCount}}", + "show_more": "Näytä enemmän", + "show_less": "Näytä vähemmän", + "reviews": "Arvostelut", + "leave_a_review": "Jätä arvostelu", + "write_review_placeholder": "Jaa ajatuksesi tästä pelistä...", + "sort_newest": "Uusimmat ensin", + "no_reviews_yet": "Ei vielä arvosteluja", + "be_first_to_review": "Ole ensimmäinen, joka jakaa ajatuksensa tästä pelistä!", + "sort_oldest": "Vanhimmat ensin", + "sort_highest_score": "Korkein pistemäärä", + "sort_lowest_score": "Matalin pistemäärä", + "sort_most_voted": "Eniten äänestetyt", + "rating": "Arvio", + "rating_stats": "Arvio", + "rating_very_negative": "Erittäin negatiivinen", + "rating_negative": "Negatiivinen", + "rating_neutral": "Neutraali", + "rating_positive": "Positiivinen", + "rating_very_positive": "Erittäin positiivinen", + "submit_review": "Lähetä", + "submitting": "Lähetetään...", + "review_submitted_successfully": "Arvostelu lähetetty onnistuneesti!", + "review_submission_failed": "Arvostelun lähettäminen epäonnistui. Yritä uudelleen.", + "review_cannot_be_empty": "Arvostelun tekstikenttä ei voi olla tyhjä.", + "review_deleted_successfully": "Arvostelu poistettu onnistuneesti.", + "review_deletion_failed": "Arvostelun poisto epäonnistui. Yritä uudelleen.", + "loading_reviews": "Ladataan arvosteluja...", + "loading_more_reviews": "Ladataan lisää arvosteluja...", + "load_more_reviews": "Lataa lisää arvosteluja", + "you_seemed_to_enjoy_this_game": "Näyttää siltä, että nautit tästä pelistä", + "would_you_recommend_this_game": "Haluatko jättää arvion tästä pelistä?", + "yes": "Kyllä", + "maybe_later": "Ehkä myöhemmin", + "rating_count": "Arvio", + "delete_review": "Poista arvostelu", + "remove_review": "Poista arvostelu", + "delete_review_modal_title": "Haluatko varmasti poistaa arvostelusi?", + "delete_review_modal_description": "Tätä toimintoa ei voi peruuttaa.", + "delete_review_modal_delete_button": "Poista", + "delete_review_modal_cancel_button": "Peruuta", + "show_original": "Näytä alkuperäinen", + "show_translation": "Näytä käännös", + "show_original_translated_from": "Näytä alkuperäinen (käännös kielestä {{language}})", + "hide_original": "Piilota alkuperäinen", + "cloud_save": "Pilvitallennus", + "cloud_save_description": "Tallenna edistymisesi pilveen ja jatka pelaamista millä tahansa laitteella", + "backups": "Varmuuskopiot", + "install_backup": "Asenna", + "delete_backup": "Poista", + "create_backup": "Luo uusi varmuuskopio", + "last_backup_date": "Viimeisin varmuuskopio {{date}}", + "no_backup_preview": "Tallennuksia ei löytynyt tälle otsikolle", + "restoring_backup": "Palautetaan varmuuskopiota ({{progress}} valmis)…", + "uploading_backup": "Ladataan varmuuskopiota…", + "no_backups": "Et ole vielä luonut varmuuskopioita tästä pelistä", + "backup_uploaded": "Varmuuskopio ladattu", + "backup_failed": "Varmuuskopiointi epäonnistui", + "backup_deleted": "Varmuuskopio poistettu", + "backup_restored": "Varmuuskopio palautettu", + "see_all_achievements": "Näytä kaikki saavutukset", + "sign_in_to_see_achievements": "Kirjaudu sisään nähdäksesi saavutukset", + "mapping_method_automatic": "Automaattinen", + "mapping_method_manual": "Manuaalinen", + "mapping_method_label": "Kartoitusmenetelmä", + "files_automatically_mapped": "Tiedostot kartoitetu automaattisesti", + "no_backups_created": "Tälle pelille ei ole luotu varmuuskopioita", + "manage_files": "Hallitse tiedostoja", + "loading_save_preview": "Etsitään tallennuksia…", + "wine_prefix": "Wine-etuliite", + "wine_prefix_description": "Tässä pelissä käytettävä Wine-etuliite", + "launch_options": "Käynnistysvalinnat", + "launch_options_description": "Edistyneet käyttäjät voivat tehdä muutoksia käynnistysvalintoihin", + "launch_options_placeholder": "Valintaa ei määritetty", + "no_download_option_info": "Tietoja ei saatavilla", + "backup_deletion_failed": "Varmuuskopion poisto epäonnistui", + "max_number_of_artifacts_reached": "Tämän pelin enimmäismäärä varmuuskopioita saavutettu", + "achievements_not_sync": "Saavutuksesi eivät ole synkronoidut", + "manage_files_description": "Hallitse tallennettavia ja palautettavia tiedostoja", + "select_folder": "Valitse kansio", + "backup_from": "Varmuuskopio {{date}}", + "automatic_backup_from": "Automaattinen varmuuskopio {{date}}", + "enable_automatic_cloud_sync": "Ota automaattinen pilvisynkronointi käyttöön", + "custom_backup_location_set": "Mukautettu varmuuskopiosijainti asetettu", + "no_directory_selected": "Hakemistoa ei valittu", + "no_write_permission": "Ei voi ladata tähän hakemistoon. Napsauta tästä saadaksesi lisätietoja.", + "reset_achievements": "Nollaa saavutukset", + "reset_achievements_description": "Tämä nollaa kaikki saavutukset pelille {{game}}", + "reset_achievements_title": "Oletko varma?", + "reset_achievements_success": "Saavutukset nollattu onnistuneesti", + "reset_achievements_error": "Saavutusten nollaus epäonnistui", + "download_error_gofile_quota_exceeded": "Olet ylittänyt Gofilen kuukausikiintiön. Odota, kunnes kiintiö palautuu.", + "download_error_real_debrid_account_not_authorized": "Real-Debrid -tilisi ei ole valtuutettu suorittamaan uusia latauksia. Tarkista tilin asetukset ja yritä uudelleen.", + "download_error_not_cached_on_real_debrid": "Tämä lataus ei ole saatavilla Real-Debridissä, eikä lataustilan hakeminen Real-Debridistä ole toistaiseksi mahdollista.", + "update_playtime_title": "Päivitä peliaika", + "update_playtime_description": "Päivitä pelin {{game}} peliaika manuaalisesti", + "update_playtime": "Päivitä peliaika", + "update_playtime_success": "Peliaika päivitetty onnistuneesti", + "update_playtime_error": "Peliajan päivitys epäonnistui", + "update_game_playtime": "Päivitä peliaika", + "manual_playtime_warning": "Pelituntisi merkitään manuaalisesti päivitetyiksi. Tätä toimintoa ei voi peruuttaa.", + "manual_playtime_tooltip": "Tämä peliaika on päivitetty manuaalisesti", + "download_error_not_cached_on_torbox": "Tämä lataus ei ole saatavilla TorBoxissa, eikä lataustilan hakeminen TorBoxista ole toistaiseksi mahdollista.", + "download_error_not_cached_on_hydra": "Tämä lataus ei ole saatavilla Nimbuksessa.", + "game_removed_from_favorites": "Peli poistettu suosikeista", + "game_added_to_favorites": "Peli lisätty suosikkeihin", + "game_removed_from_pinned": "Peli poistettu kiinnitetyistä", + "game_added_to_pinned": "Peli lisätty kiinnitettyihin", + "automatically_extract_downloaded_files": "Pura ladatut tiedostot automaattisesti", + "create_start_menu_shortcut": "Luo Käynnistä-valikon pikakuvake", + "invalid_wine_prefix_path": "Virheellinen Wine-etuliitteen polku", + "invalid_wine_prefix_path_description": "Wine-etuliitteen polku on virheellinen. Tarkista polku ja yritä uudelleen.", + "missing_wine_prefix": "Wine-etuliite vaaditaan varmuuskopiointiin Linuxissa", + "artifact_renamed": "Varmuuskopio nimettiin uudelleen onnistuneesti", + "rename_artifact": "Nimeä varmuuskopio uudelleen", + "rename_artifact_description": "Anna varmuuskopiolle kuvaavampi nimi.", + "artifact_name_label": "Varmuuskopion nimi", + "artifact_name_placeholder": "Syötä nimi varmuuskopiolle", + "save_changes": "Tallenna muutokset", + "required_field": "Tämä kenttä on pakollinen", + "max_length_field": "Tämän kentän on oltava alle {{length}} merkkiä", + "freeze_backup": "Kiinnitä, jotta sitä ei ylikirjoiteta automaattisilla varmuuskopioilla", + "unfreeze_backup": "Poista kiinnitys", + "backup_frozen": "Varmuuskopio kiinnitetty", + "backup_unfrozen": "Varmuuskopion kiinnitys poistettu", + "backup_freeze_failed": "Varmuuskopion kiinnitys epäonnistui", + "backup_freeze_failed_description": "Sinun on jätettävä vähintään yksi paikka vapaaksi automaattisille varmuuskopioille", + "edit_game_modal_button": "Muokkaa pelin tietoja", + "game_details": "Pelin tiedot", + "currency_symbol": "€", + "currency_country": "fi", + "prices": "Hinnat", + "no_prices_found": "Hintoja ei löytynyt", + "view_all_prices": "Napsauta nähdäksesi kaikki hinnat", + "retail_price": "Vähittäishinta", + "keyshop_price": "Keyshop-hinta", + "historical_retail": "Historialliset vähittäishinnat", + "historical_keyshop": "Historialliset keyshop-hinnat", + "language": "Kieli", + "caption": "Tekstitys", + "audio": "Ääni", + "filter_by_source": "Suodata lähteen mukaan", + "no_repacks_found": "Tämän pelin lähteitä ei löytynyt" + }, + "activation": { + "title": "Aktivoi Hydra", + "installation_id": "Asennustunnus:", + "enter_activation_code": "Syötä aktivointikoodisi", + "message": "Jos et tiedä mistä sitä pyytää, sinun ei pitäisi sitä olla.", + "activate": "Aktivoi", + "loading": "Ladataan…" + }, + "downloads": { + "resume": "Jatka", + "pause": "Keskeytä", + "eta": "Lopetus {{eta}}", + "paused": "Keskeytetty", + "verifying": "Tarkistetaan…", + "completed": "Valmis", + "removed": "Ei ladattu", + "cancel": "Peruuta", + "filter": "Hae ladattuja pelejä", + "remove": "Poista", + "downloading_metadata": "Ladataan metatietoja…", + "deleting": "Poistetaan asennustiedostoa…", + "delete": "Poista asennustiedosto", + "delete_modal_title": "Oletko varma?", + "delete_modal_description": "Tämä poistaa kaikki asennustiedostot tietokoneeltasi", + "install": "Asenna", + "download_in_progress": "Käynnissä", + "queued_downloads": "Jonossa olevat lataukset", + "downloads_completed": "Valmiit", + "queued": "Jonossa", + "no_downloads_title": "Täällä on niin tyhjää...", + "no_downloads_description": "Et ole vielä ladannut mitään Hydran kautta, mutta ei ole koskaan liian myöhäistä aloittaa.", + "checking_files": "Tarkistetaan tiedostoja…", + "seeding": "Jakaminen", + "stop_seeding": "Lopeta jakaminen", + "resume_seeding": "Jatka jakamista", + "options": "Hallinnoi", + "extract": "Pura tiedostot", + "extracting": "Puretaan tiedostoja…" + }, + "settings": { + "downloads_path": "Latausten polku", + "change": "Vaihda", + "notifications": "Ilmoitukset", + "enable_download_notifications": "Latauksen valmistuessa", + "enable_repack_list_notifications": "Kun uusi repack lisätään", + "real_debrid_api_token_label": "Real-Debrid API-tunnus", + "quit_app_instead_hiding": "Sovellus sulkeutuu system tray -alueelle sijasta", + "launch_with_system": "Käynnistä Hydra järjestelmän mukana", + "general": "Yleiset", + "behavior": "Käyttäytyminen", + "download_sources": "Latauslähteet", + "language": "Kieli", + "api_token": "API-avain", + "enable_real_debrid": "Ota Real-Debrid käyttöön", + "real_debrid_description": "Real-Debrid on rajoittamaton lataaja, jonka avulla voit ladata nopeasti verkossa olevia tiedostoja tai striimata ne välittömästi soittimeen yksityisen verkon kautta, joka kiertää kaikki estot.", + "debrid_invalid_token": "Virheellinen API-avain", + "debrid_api_token_hint": "API-avain voidaan hankkia <0>täältä", + "real_debrid_free_account_error": "Tili \"{{username}}\" - ei ole tilaus. Ota Real-Debrid-tilaus", + "debrid_linked_message": "Tili \"{{username}}\" linkitetty", + "save_changes": "Tallenna muutokset", + "changes_saved": "Muutokset tallennettu onnistuneesti", + "download_sources_description": "Hydra hakee latauslinkit näistä lähteistä. URL-osoitteen on sisällettävä suora linkki .json-tiedostoon, joka sisältää latauslinkit.", + "validate_download_source": "Vahvista", + "remove_download_source": "Poista", + "add_download_source": "Lisää lähde", + "download_count_zero": "Ei latauksia listassa", + "download_count_one": "{{countFormatted}} lataus listassa", + "download_count_other": "{{countFormatted}} latausta listassa", + "download_source_url": "Lähteen URL-osoite", + "add_download_source_description": "Liitä linkki .json-tiedostoon", + "download_source_up_to_date": "Ajan tasalla", + "download_source_errored": "Virhe", + "sync_download_sources": "Päivitä lähteet", + "removed_download_source": "Lähde poistettu", + "removed_download_sources": "Lähteet poistettu", + "cancel_button_confirmation_delete_all_sources": "Ei", + "confirm_button_confirmation_delete_all_sources": "Kyllä, poista kaikki", + "title_confirmation_delete_all_sources": "Poista kaikki lähteet", + "description_confirmation_delete_all_sources": "Poistat kaikki lähteet", + "button_delete_all_sources": "Poista kaikki lähteet", + "added_download_source": "Lähde lisätty", + "download_sources_synced": "Kaikki lähteet päivitetty", + "insert_valid_json_url": "Liitä kelvollinen JSON-tiedoston URL-osoite", + "found_download_option_zero": "Ei latausvaihtoehtoja löytynyt", + "found_download_option_one": "Löytyi {{countFormatted}} latausvaihtoehto", + "found_download_option_other": "Löytyi {{countFormatted}} latausvaihtoehtoa", + "import": "Tuo", + "importing": "Tuodaan...", + "public": "Julkinen", + "private": "Yksityinen", + "friends_only": "Vain kavereille", + "privacy": "Yksityisyys", + "profile_visibility": "Profiilin näkyvyys", + "profile_visibility_description": "Valitse, kuka voi nähdä profiilisi ja kirjastosi", + "required_field": "Tämä kenttä on pakollinen", + "source_already_exists": "Tämä lähde on jo lisätty", + "must_be_valid_url": "Lähteen on oltava kelvollinen URL-osoite", + "blocked_users": "Estetyt käyttäjät", + "user_unblocked": "Käyttäjä estäminen poistettu", + "enable_achievement_notifications": "Kun saavutus avataan", + "launch_minimized": "Käynnistä Hydra pienennettynä", + "disable_nsfw_alert": "Poista sopimattoman sisällön varoitus käytöstä", + "seed_after_download_complete": "Jaa latauksen valmistumisen jälkeen", + "show_hidden_achievement_description": "Näytä piilotettujen saavutusten kuvaukset ennen niiden ansaitsemista", + "account": "Tili", + "no_users_blocked": "Sinulla ei ole estettyjä käyttäjiä", + "subscription_active_until": "Hydra Cloud -tilisi on voimassa {{date}} asti", + "manage_subscription": "Hallinnoi tilausta", + "update_email": "Päivitä sähköposti", + "update_password": "Päivitä salasana", + "current_email": "Nykyinen sähköposti:", + "no_email_account": "Et ole vielä asettanut sähköpostiosoitetta", + "account_data_updated_successfully": "Tilitiedot päivitetty onnistuneesti", + "renew_subscription": "Uusi Hydra Cloud -tilaus", + "subscription_expired_at": "Tilauksesi vanheni {{date}}", + "no_subscription": "Nauti Hydrasta täysin rinnoin", + "become_subscriber": "Tule Hydra Cloud -tilaajaksi", + "subscription_renew_cancelled": "Automaattinen uusinta peruutettu", + "subscription_renews_on": "Tilauksesi uusiutuu {{date}}", + "bill_sent_until": "Seuraava laskusi lähetetään ennen tätä päivää", + "no_themes": "Näyttää siltä, että sinulla ei vielä ole teemoja, mutta älä huoli, napsauta tästä luodaksesi ensimmäisen mestariteoksesi", + "editor_tab_code": "Koodi", + "editor_tab_info": "Tiedot", + "editor_tab_save": "Tallenna", + "web_store": "Verkkokauppa", + "clear_themes": "Tyhjennä", + "create_theme": "Luo", + "create_theme_modal_title": "Luo mukautettu teema", + "create_theme_modal_description": "Luo uusi teema Hydran ulkoasun mukauttamiseksi", + "theme_name": "Nimi", + "insert_theme_name": "Syötä teeman nimi", + "set_theme": "Aseta teema", + "unset_theme": "Poista teema", + "delete_theme": "Poista teema", + "edit_theme": "Muokkaa teemaa", + "delete_all_themes": "Poista kaikki teemat", + "delete_all_themes_description": "Tämä poistaa kaikki mukautetut teemasi", + "delete_theme_description": "Tämä poistaa teeman {{theme}}", + "cancel": "Peruuta", + "appearance": "Ulkoasu", + "debrid": "Debrid", + "debrid_description": "Debrid-palvelut ovat premium-lataajia ilman rajoituksia, joiden avulla voit ladata tiedostoja nopeasti useista tiedostonjakopalveluista, vain internet-yhteytesi nopeuden rajoittamina.", + "enable_torbox": "Ota TorBox käyttöön", + "torbox_description": "TorBox on premium-palvelusi, joka kilpailee jopa parhaimpien markkinoiden palvelimien kanssa.", + "torbox_account_linked": "TorBox-tili linkitetty", + "create_real_debrid_account": "Napsauta tästä, jos sinulla ei vielä ole Real-Debrid-tiliä", + "create_torbox_account": "Napsauta tästä, jos sinulla ei vielä ole TorBox-tiliä", + "real_debrid_account_linked": "Real-Debrid-tili linkitetty", + "name_min_length": "Teeman nimen on oltava vähintään 3 merkkiä", + "import_theme": "Tuo teema", + "import_theme_description": "Tuot teeman {{theme}} teemakaupasta", + "error_importing_theme": "Virhe teemaa tuotaessa", + "theme_imported": "Teema tuotu onnistuneesti", + "enable_friend_request_notifications": "Kun kaveripyyntö vastaanotetaan", + "enable_auto_install": "Lataa päivitykset automaattisesti", + "common_redist": "Kirjastot", + "common_redist_description": "Joidenkin pelien käyttö vaatii kirjastoja. Ongelmien välttämiseksi on suositeltavaa asentaa ne.", + "install_common_redist": "Asenna", + "installing_common_redist": "Asennetaan…", + "show_download_speed_in_megabytes": "Näytä latausnopeus megatavuina sekunnissa", + "extract_files_by_default": "Pura tiedostot oletusarvoisesti latauksen jälkeen", + "enable_steam_achievements": "Ota Steam-saavutusten haku käyttöön", + "achievement_custom_notification_position": "Saavutusilmoitusten sijainti", + "top-left": "Vasemmalla ylhäällä", + "top-center": "Yläkeskellä", + "top-right": "Oikealla ylhäällä", + "bottom-left": "Vasemmalla alhaalla", + "bottom-center": "Alakeskellä", + "bottom-right": "Oikealla alhaalla", + "enable_achievement_custom_notifications": "Ota saavutusilmoitukset käyttöön", + "alignment": "Tasaus", + "variation": "Muunnelma", + "default": "Oletus", + "rare": "Harvinainen", + "platinum": "Platina", + "hidden": "Piilotettu", + "test_notification": "Testi-ilmoitus", + "notification_preview": "Saavutusilmoituksen esikatselu", + "enable_friend_start_game_notifications": "Kun kaveri aloittaa pelin pelaamisen" + }, + "notifications": { + "download_complete": "Lataus valmis", + "game_ready_to_install": "{{title}} valmis asennettavaksi", + "repack_list_updated": "Repack-lista päivitetty", + "repack_count_one": "{{count}} repack lisätty", + "repack_count_other": "{{count}} repackia lisätty", + "new_update_available": "Uusi versio {{version}} saatavilla", + "restart_to_install_update": "Käynnistä Hydra uudelleen asentaaksesi päivityksen", + "notification_achievement_unlocked_title": "Saavutus avattu pelille {{game}}", + "notification_achievement_unlocked_body": "{{achievement}} ja muut {{count}} avattiin", + "new_friend_request_description": "{{displayName}} lähetti sinulle kaveripyynnön", + "new_friend_request_title": "Uusi kaveripyyntö", + "extraction_complete": "Purkaminen valmis", + "game_extracted": "{{title}} purettu onnistuneesti", + "friend_started_playing_game": "{{displayName}} aloitti pelin pelaamisen", + "test_achievement_notification_title": "Tämä on testi-ilmoitus", + "test_achievement_notification_description": "Aika siistiä, eikö?" + }, + "system_tray": { + "open": "Avaa Hydra", + "quit": "Lopeta" + }, + "game_card": { + "available_one": "Saatavilla", + "available_other": "Saatavilla", + "no_downloads": "Ei saatavilla olevia lähteitä", + "calculating": "Lasketaan" + }, + "binary_not_found_modal": { + "title": "Ohjelmia ei asennettu", + "description": "Wine tai Lutris ei löytynyt", + "instructions": "Opi oikea tapa asentaa kumpi tahansa Linux-jakelullesi, jotta peli toimii kunnolla" + }, + "modal": { + "close": "Sulje" + }, + "forms": { + "toggle_password_visibility": "Näytä salasana" + }, + "user_profile": { + "amount_hours": "{{amount}} tuntia", + "amount_minutes": "{{amount}} minuuttia", + "amount_hours_short": "{{amount}}t", + "amount_minutes_short": "{{amount}}min", + "last_time_played": "Viimeisin peli {{period}}", + "activity": "Viimeisin toiminta", + "library": "Kirjasto", + "pinned": "Kiinnitetyt", + "achievements_earned": "Ansaittu saavutukset", + "played_recently": "Äskettäin pelatut", + "playtime": "Peliaika", + "total_play_time": "Yhteensä pelattu", + "manual_playtime_tooltip": "Peliaika on päivitetty manuaalisesti", + "no_recent_activity_title": "Hmm... Täällä ei ole mitään", + "no_recent_activity_description": "Et ole pelannut mitään vähään aikaan. On aika muuttaa se!", + "display_name": "Näyttönimi", + "saving": "Tallennetaan", + "save": "Tallenna", + "edit_profile": "Muokkaa profiilia", + "saved_successfully": "Tallennettu onnistuneesti", + "try_again": "Yritä uudelleen", + "sign_out_modal_title": "Oletko varma?", + "cancel": "Peruuta", + "successfully_signed_out": "Kirjauduttu ulos onnistuneesti", + "sign_out": "Kirjaudu ulos", + "playing_for": "Pelattu {{amount}}", + "sign_out_modal_text": "Kirjastosi on linkitetty nykyiseen tiliisi. Kirjautumalla ulos kirjastosi ei ole käytettävissä, eikä edistymistä tallenneta. Kirjaudu ulos?", + "add_friends": "Lisää kavereita", + "add": "Lisää", + "friend_code": "Kaverikoodi", + "see_profile": "Näytä profiili", + "sending": "Lähetetään", + "friend_request_sent": "Kaveripyyntö lähetetty", + "friends": "Kaverit", + "friends_list": "Kaverilista", + "user_not_found": "Käyttäjää ei löytynyt", + "block_user": "Estä käyttäjä", + "add_friend": "Lisää kaveriksi", + "request_sent": "Pyyntö lähetetty", + "request_received": "Pyyntö vastaanotettu", + "accept_request": "Hyväksy pyyntö", + "ignore_request": "Ohita pyyntö", + "cancel_request": "Peruuta pyyntö", + "undo_friendship": "Poista kaveri", + "request_accepted": "Pyyntö hyväksytty", + "user_blocked_successfully": "Käyttäjä estetty onnistuneesti", + "user_block_modal_text": "{{displayName}} estetään", + "blocked_users": "Estetyt käyttäjät", + "unblock": "Poista esto", + "no_friends_added": "Et ole vielä lisännyt yhtään kaveria", + "pending": "Odottaa", + "no_pending_invites": "Sinulla ei ole vasteita odottavia pyyntöjä", + "no_blocked_users": "Et ole estänyt yhtään käyttäjää", + "friend_code_copied": "Kaverikoodi kopioitu", + "undo_friendship_modal_text": "Tämä purkaa kaverisuhteen käyttäjän {{displayName}} kanssa.", + "privacy_hint": "Määrittääksesi kuka voi nähdä tämän, siirry <0>Asetuksiin.", + "locked_profile": "Tämä profiili on yksityinen", + "image_process_failure": "Kuvan käsittely epäonnistui", + "required_field": "Tämä kenttä on pakollinen", + "displayname_min_length": "Näyttönimen on oltava vähintään 3 merkkiä.", + "displayname_max_length": "Näyttönimen on oltava enintään 50 merkkiä.", + "report_profile": "Ilmianna tämä profiili", + "report_reason": "Miksi ilmiannat tämän profiilin?", + "report_description": "Lisätietoja", + "report_description_placeholder": "Lisätietoja", + "report": "Ilmianna", + "report_reason_hate": "Vihapuhe", + "report_reason_sexual_content": "Seksuaalinen sisältö", + "report_reason_violence": "Väkivalta", + "report_reason_spam": "Roskaposti", + "report_reason_other": "Muu", + "profile_reported": "Profiili-ilmoitus lähetetty", + "your_friend_code": "Kaverikoodisi:", + "upload_banner": "Lataa banneri", + "uploading_banner": "Ladataan banneria...", + "background_image_updated": "Taustakuva päivitetty", + "stats": "Tilastot", + "achievements": "Saavutukset", + "games": "Pelit", + "top_percentile": "Top {{percentile}}%", + "ranking_updated_weekly": "Sijoitus päivitetään viikoittain", + "playing": "Pelaamassa {{game}}", + "achievements_unlocked": "Saavutukset avattu", + "earned_points": "Ansaitut pisteet:", + "show_achievements_on_profile": "Näytä saavutuksesi profiilissasi", + "show_points_on_profile": "Näytä ansaitut pisteet profiilissasi", + "error_adding_friend": "Kaveripyynnön lähettäminen epäonnistui. Tarkista kaverikoodi", + "friend_code_length_error": "Kaverikoodin on oltava 8 merkkiä", + "game_removed_from_pinned": "Peli poistettu kiinnitetyistä", + "game_added_to_pinned": "Peli lisätty kiinnitettyihin", + "karma": "Karma", + "karma_count": "karmaa", + "karma_description": "Ansittu positiivisilla arvosteluäänillä" + }, + "achievement": { + "achievement_unlocked": "Saavutus avattu", + "user_achievements": "Käyttäjän {{displayName}} saavutukset", + "your_achievements": "Sinun saavutuksesi", + "unlocked_at": "Avattu: {{date}}", + "subscription_needed": "Hydra Cloud -tilaus tarvitaan tämän sisällön katsomiseen", + "new_achievements_unlocked": "{{achievementCount}} uutta saavutusta avattu {{gameCount}} pelistä", + "achievement_progress": "{{unlockedCount}}/{{totalCount}} saavutusta", + "achievements_unlocked_for_game": "{{achievementCount}} uutta saavutusta avattu pelille {{gameTitle}}", + "hidden_achievement_tooltip": "Tämä on piilotettu saavutus", + "achievement_earn_points": "Ansaitse {{points}} pistettä tällä saavutuksella", + "earned_points": "Ansaitut pisteet:", + "available_points": "Saatavilla olevat pisteet:", + "how_to_earn_achievements_points": "Kuinka ansaita saavutuspisteitä?" + }, + "hydra_cloud": { + "subscription_tour_title": "Hydra Cloud -tilaus", + "subscribe_now": "Tilaa nyt", + "cloud_saving": "Pilvitallennus", + "cloud_achievements": "Tallenna saavutuksesi pilveen", + "animated_profile_picture": "Animaoidut profiilikuvat", + "premium_support": "Premium-tuki", + "show_and_compare_achievements": "Näytä ja vertaile saavutuksiasi muiden käyttäjien saavutuksiin", + "animated_profile_banner": "Animoitu profiilin banneri", + "hydra_cloud": "Hydra Cloud", + "hydra_cloud_feature_found": "Olet juuri löytänyt Hydra Cloud -toiminnon!", + "learn_more": "Lue lisää", + "debrid_description": "Lataa 4 kertaa nopeammin Nimbuksella" + } +} From 0575e837c8f01d095fad323111623e540b41a179 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Mon, 20 Oct 2025 08:24:04 -0300 Subject: [PATCH 102/197] fix: game achievement cache --- .../achievements/get-game-achievement-data.ts | 22 +++++++++---------- .../achievements/merge-achievements.ts | 1 + src/types/level.types.ts | 1 + 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/services/achievements/get-game-achievement-data.ts b/src/main/services/achievements/get-game-achievement-data.ts index 72b49bc5..ffbfac1a 100644 --- a/src/main/services/achievements/get-game-achievement-data.ts +++ b/src/main/services/achievements/get-game-achievement-data.ts @@ -5,15 +5,18 @@ import { logger } from "../logger"; import { db, gameAchievementsSublevel, levelKeys } from "@main/level"; import { AxiosError } from "axios"; -const LOCAL_CACHE_EXPIRATION = 1000 * 60 * 60; // 1 hour - const getModifiedSinceHeader = ( - cachedAchievements: GameAchievement | undefined + cachedAchievements: GameAchievement | undefined, + userLanguage: string ): Date | undefined => { if (!cachedAchievements) { return undefined; } + if (userLanguage != cachedAchievements.language) { + return undefined; + } + return cachedAchievements.updatedAt ? new Date(cachedAchievements.updatedAt) : undefined; @@ -28,13 +31,7 @@ export const getGameAchievementData = async ( const cachedAchievements = await gameAchievementsSublevel.get(gameKey); - if (cachedAchievements?.achievements && useCachedData) - return cachedAchievements.achievements; - - if ( - cachedAchievements?.achievements && - Date.now() < (cachedAchievements.updatedAt ?? 0) + LOCAL_CACHE_EXPIRATION - ) { + if (cachedAchievements?.achievements && useCachedData) { return cachedAchievements.achievements; } @@ -50,14 +47,15 @@ export const getGameAchievementData = async ( language, }, { - ifModifiedSince: getModifiedSinceHeader(cachedAchievements), + ifModifiedSince: getModifiedSinceHeader(cachedAchievements, language), } ) .then(async (achievements) => { await gameAchievementsSublevel.put(gameKey, { unlockedAchievements: cachedAchievements?.unlockedAchievements ?? [], achievements, - updatedAt: Date.now() + LOCAL_CACHE_EXPIRATION, + updatedAt: Date.now(), + language, }); return achievements; diff --git a/src/main/services/achievements/merge-achievements.ts b/src/main/services/achievements/merge-achievements.ts index f2ea03ac..804f5933 100644 --- a/src/main/services/achievements/merge-achievements.ts +++ b/src/main/services/achievements/merge-achievements.ts @@ -37,6 +37,7 @@ const saveAchievementsOnLocal = async ( achievements: gameAchievement?.achievements ?? [], unlockedAchievements: unlockedAchievements, updatedAt: gameAchievement?.updatedAt, + language: gameAchievement?.language, }); if (!sendUpdateEvent) return; diff --git a/src/types/level.types.ts b/src/types/level.types.ts index 053bd218..ee2b2918 100644 --- a/src/types/level.types.ts +++ b/src/types/level.types.ts @@ -83,6 +83,7 @@ export interface GameAchievement { achievements: SteamAchievement[]; unlockedAchievements: UnlockedAchievement[]; updatedAt: number | undefined; + language: string | undefined; } export type AchievementCustomNotificationPosition = From 48ce9a247640347f6667546ad8f60dcc75feaa08 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Tue, 21 Oct 2025 04:18:11 +0100 Subject: [PATCH 103/197] feat: using api download sources --- .../download-sources/add-download-source.ts | 99 ++---- .../check-download-source-exists.ts | 17 - .../delete-all-download-sources.ts | 13 - .../delete-download-source.ts | 28 -- .../get-download-sources-list.ts | 19 -- .../download-sources/get-download-sources.ts | 4 +- src/main/events/download-sources/helpers.ts | 314 ------------------ .../remove-download-source.ts | 17 +- .../sync-download-sources-from-api.ts | 19 -- .../download-sources/sync-download-sources.ts | 113 +------ .../update-missing-fingerprints.ts | 67 ---- .../validate-download-source.ts | 32 -- src/main/events/index.ts | 7 - src/main/events/repacks/get-all-repacks.ts | 16 - src/main/level/sublevels/download-sources.ts | 14 +- src/main/level/sublevels/index.ts | 1 - src/main/level/sublevels/keys.ts | 1 - src/main/level/sublevels/repacks.ts | 22 -- src/main/main.ts | 8 - .../services/game-matcher-worker-manager.ts | 145 -------- src/main/services/hydra-api.ts | 5 - src/main/services/index.ts | 2 - src/main/services/resource-cache.ts | 157 --------- src/main/workers/game-matcher-worker.ts | 158 --------- src/preload/index.ts | 12 - src/renderer/src/app.tsx | 34 -- .../src/components/game-card/game-card.tsx | 40 +-- .../game-details/game-details.context.tsx | 61 ++-- src/renderer/src/declaration.d.ts | 22 +- .../src/features/download-sources-slice.ts | 21 -- src/renderer/src/features/index.ts | 2 - src/renderer/src/hooks/index.ts | 1 - src/renderer/src/hooks/use-catalogue.ts | 12 +- src/renderer/src/hooks/use-repacks.ts | 26 -- .../src/pages/catalogue/catalogue.tsx | 73 ++-- .../src/pages/catalogue/game-item.tsx | 14 +- .../src/pages/game-details/game-reviews.tsx | 4 - .../game-details/modals/repacks-modal.tsx | 27 +- src/renderer/src/pages/home/home.tsx | 18 +- .../settings/add-download-source-modal.scss | 7 + .../settings/add-download-source-modal.tsx | 128 ++----- .../settings/settings-download-sources.tsx | 102 +++--- src/renderer/src/store.ts | 4 - src/shared/constants.ts | 6 +- src/types/index.ts | 28 +- 45 files changed, 295 insertions(+), 1625 deletions(-) delete mode 100644 src/main/events/download-sources/check-download-source-exists.ts delete mode 100644 src/main/events/download-sources/delete-all-download-sources.ts delete mode 100644 src/main/events/download-sources/delete-download-source.ts delete mode 100644 src/main/events/download-sources/get-download-sources-list.ts delete mode 100644 src/main/events/download-sources/helpers.ts delete mode 100644 src/main/events/download-sources/sync-download-sources-from-api.ts delete mode 100644 src/main/events/download-sources/update-missing-fingerprints.ts delete mode 100644 src/main/events/download-sources/validate-download-source.ts delete mode 100644 src/main/events/repacks/get-all-repacks.ts delete mode 100644 src/main/level/sublevels/repacks.ts delete mode 100644 src/main/services/game-matcher-worker-manager.ts delete mode 100644 src/main/services/resource-cache.ts delete mode 100644 src/main/workers/game-matcher-worker.ts delete mode 100644 src/renderer/src/features/download-sources-slice.ts delete mode 100644 src/renderer/src/hooks/use-repacks.ts diff --git a/src/main/events/download-sources/add-download-source.ts b/src/main/events/download-sources/add-download-source.ts index e51cae3e..45bcd27c 100644 --- a/src/main/events/download-sources/add-download-source.ts +++ b/src/main/events/download-sources/add-download-source.ts @@ -1,76 +1,45 @@ import { registerEvent } from "../register-event"; -import { downloadSourcesSublevel, repacksSublevel } from "@main/level"; -import { HydraApi, logger } from "@main/services"; -import { importDownloadSourceToLocal } from "./helpers"; +import { HydraApi } from "@main/services/hydra-api"; +import { downloadSourcesSublevel } from "@main/level"; +import type { DownloadSource } from "@types"; const addDownloadSource = async ( _event: Electron.IpcMainInvokeEvent, url: string ) => { - const result = await importDownloadSourceToLocal(url, true); - if (!result) { - throw new Error("Failed to import download source"); - } - - // Verify that repacks were actually written to the database (read-after-write) - // This ensures all async operations are complete before proceeding - let repackCount = 0; - for await (const [, repack] of repacksSublevel.iterator()) { - if (repack.downloadSourceId === result.id) { - repackCount++; - } - } - - await HydraApi.post("/profile/download-sources", { - urls: [url], - }); - - const { fingerprint } = await HydraApi.put<{ fingerprint: string }>( - "/download-sources", - { - objectIds: result.objectIds, - }, - { needsAuth: false } - ); - - // Update the source with fingerprint - const updatedSource = await downloadSourcesSublevel.get(`${result.id}`); - if (updatedSource) { - await downloadSourcesSublevel.put(`${result.id}`, { - ...updatedSource, - fingerprint, - updatedAt: new Date(), - }); - } - - // Final verification: ensure the source with fingerprint is persisted - const finalSource = await downloadSourcesSublevel.get(`${result.id}`); - if (!finalSource || !finalSource.fingerprint) { - throw new Error("Failed to persist download source with fingerprint"); - } - - // Verify repacks still exist after fingerprint update - let finalRepackCount = 0; - for await (const [, repack] of repacksSublevel.iterator()) { - if (repack.downloadSourceId === result.id) { - finalRepackCount++; - } - } - - if (finalRepackCount !== repackCount) { - logger.warn( - `Repack count mismatch! Before: ${repackCount}, After: ${finalRepackCount}` + try { + const downloadSource = await HydraApi.post( + "/download-sources", + { + url, + }, + { needsAuth: false } ); - } else { - logger.info( - `Final verification passed: ${finalRepackCount} repacks confirmed` - ); - } - return { - ...result, - fingerprint, - }; + if (HydraApi.isLoggedIn()) { + try { + await HydraApi.post("/profile/download-sources", { + urls: [url], + }); + } catch (error) { + console.error("Failed to add download source to profile:", error); + } + } + + const downloadSourceForStorage = { + ...downloadSource, + fingerprint: downloadSource.fingerprint || "", + }; + await downloadSourcesSublevel.put( + downloadSource.id, + downloadSourceForStorage + ); + + return downloadSource; + } catch (error) { + console.error("Failed to add download source:", error); + throw error; + } }; registerEvent("addDownloadSource", addDownloadSource); diff --git a/src/main/events/download-sources/check-download-source-exists.ts b/src/main/events/download-sources/check-download-source-exists.ts deleted file mode 100644 index 36dd88ce..00000000 --- a/src/main/events/download-sources/check-download-source-exists.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { registerEvent } from "../register-event"; -import { downloadSourcesSublevel } from "@main/level"; - -const checkDownloadSourceExists = async ( - _event: Electron.IpcMainInvokeEvent, - url: string -): Promise => { - for await (const [, source] of downloadSourcesSublevel.iterator()) { - if (source.url === url) { - return true; - } - } - - return false; -}; - -registerEvent("checkDownloadSourceExists", checkDownloadSourceExists); diff --git a/src/main/events/download-sources/delete-all-download-sources.ts b/src/main/events/download-sources/delete-all-download-sources.ts deleted file mode 100644 index cbf3958f..00000000 --- a/src/main/events/download-sources/delete-all-download-sources.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { registerEvent } from "../register-event"; -import { downloadSourcesSublevel, repacksSublevel } from "@main/level"; -import { invalidateIdCaches } from "./helpers"; - -const deleteAllDownloadSources = async ( - _event: Electron.IpcMainInvokeEvent -) => { - await Promise.all([repacksSublevel.clear(), downloadSourcesSublevel.clear()]); - - invalidateIdCaches(); -}; - -registerEvent("deleteAllDownloadSources", deleteAllDownloadSources); diff --git a/src/main/events/download-sources/delete-download-source.ts b/src/main/events/download-sources/delete-download-source.ts deleted file mode 100644 index 5322b96c..00000000 --- a/src/main/events/download-sources/delete-download-source.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { registerEvent } from "../register-event"; -import { downloadSourcesSublevel, repacksSublevel } from "@main/level"; -import { invalidateIdCaches } from "./helpers"; - -const deleteDownloadSource = async ( - _event: Electron.IpcMainInvokeEvent, - id: number -) => { - const repacksToDelete: string[] = []; - - for await (const [key, repack] of repacksSublevel.iterator()) { - if (repack.downloadSourceId === id) { - repacksToDelete.push(key); - } - } - - const batch = repacksSublevel.batch(); - for (const key of repacksToDelete) { - batch.del(key); - } - await batch.write(); - - await downloadSourcesSublevel.del(`${id}`); - - invalidateIdCaches(); -}; - -registerEvent("deleteDownloadSource", deleteDownloadSource); diff --git a/src/main/events/download-sources/get-download-sources-list.ts b/src/main/events/download-sources/get-download-sources-list.ts deleted file mode 100644 index db26ad01..00000000 --- a/src/main/events/download-sources/get-download-sources-list.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { registerEvent } from "../register-event"; -import { downloadSourcesSublevel, DownloadSource } from "@main/level"; - -const getDownloadSourcesList = async (_event: Electron.IpcMainInvokeEvent) => { - const sources: DownloadSource[] = []; - - for await (const [, source] of downloadSourcesSublevel.iterator()) { - sources.push(source); - } - - // Sort by createdAt descending - sources.sort( - (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() - ); - - return sources; -}; - -registerEvent("getDownloadSourcesList", getDownloadSourcesList); diff --git a/src/main/events/download-sources/get-download-sources.ts b/src/main/events/download-sources/get-download-sources.ts index bbebd06c..cf7cd4d7 100644 --- a/src/main/events/download-sources/get-download-sources.ts +++ b/src/main/events/download-sources/get-download-sources.ts @@ -1,8 +1,8 @@ -import { HydraApi } from "@main/services"; +import { downloadSourcesSublevel } from "@main/level"; import { registerEvent } from "../register-event"; const getDownloadSources = async (_event: Electron.IpcMainInvokeEvent) => { - return HydraApi.get("/profile/download-sources"); + return downloadSourcesSublevel.values().all(); }; registerEvent("getDownloadSources", getDownloadSources); diff --git a/src/main/events/download-sources/helpers.ts b/src/main/events/download-sources/helpers.ts deleted file mode 100644 index edd3878e..00000000 --- a/src/main/events/download-sources/helpers.ts +++ /dev/null @@ -1,314 +0,0 @@ -import axios from "axios"; -import { z } from "zod"; -import { downloadSourcesSublevel, repacksSublevel } from "@main/level"; -import { DownloadSourceStatus } from "@shared"; -import crypto from "node:crypto"; -import { logger, ResourceCache } from "@main/services"; - -export const downloadSourceSchema = z.object({ - name: z.string().max(255), - downloads: z.array( - z.object({ - title: z.string().max(255), - uris: z.array(z.string()), - uploadDate: z.string().max(255), - fileSize: z.string().max(255), - }) - ), -}); - -export type TitleHashMapping = Record; - -let titleHashMappingCache: TitleHashMapping | null = null; - -export const getTitleHashMapping = async (): Promise => { - if (titleHashMappingCache) { - return titleHashMappingCache; - } - - try { - const cached = - ResourceCache.getCachedData("sources-manifest"); - if (cached) { - titleHashMappingCache = cached; - return cached; - } - - const fetched = await ResourceCache.fetchAndCache( - "sources-manifest", - "https://cdn.losbroxas.org/sources-manifest.json", - 10000 - ); - titleHashMappingCache = fetched; - return fetched; - } catch (error) { - logger.error("Failed to fetch title hash mapping:", error); - return {} as TitleHashMapping; - } -}; - -export const hashTitle = (title: string): string => { - return crypto.createHash("sha256").update(title).digest("hex"); -}; - -export type SteamGamesByLetter = Record; -export type FormattedSteamGame = { - id: string; - name: string; - formattedName: string; -}; -export type FormattedSteamGamesByLetter = Record; - -export const formatName = (name: string) => { - return name - .normalize("NFD") - .replaceAll(/[\u0300-\u036f]/g, "") - .toLowerCase() - .replaceAll(/[^a-z0-9]/g, ""); -}; - -export const formatRepackName = (name: string) => { - return formatName(name.replace("[DL]", "")); -}; - -interface DownloadSource { - id: number; - url: string; - name: string; - etag: string | null; - status: number; - downloadCount: number; - objectIds: string[]; - fingerprint?: string; - createdAt: Date; - updatedAt: Date; -} - -const getDownloadSourcesMap = async (): Promise< - Map -> => { - const map = new Map(); - for await (const [key, source] of downloadSourcesSublevel.iterator()) { - map.set(key, source); - } - - return map; -}; - -export const checkUrlExists = async (url: string): Promise => { - const sources = await getDownloadSourcesMap(); - for (const source of sources.values()) { - if (source.url === url) { - return true; - } - } - return false; -}; - -let steamGamesFormattedCache: FormattedSteamGamesByLetter | null = null; - -export const getSteamGames = async (): Promise => { - if (steamGamesFormattedCache) { - return steamGamesFormattedCache; - } - - let steamGames: SteamGamesByLetter; - - const cached = ResourceCache.getCachedData( - "steam-games-by-letter" - ); - if (cached) { - steamGames = cached; - } else { - steamGames = await ResourceCache.fetchAndCache( - "steam-games-by-letter", - `${import.meta.env.MAIN_VITE_EXTERNAL_RESOURCES_URL}/steam-games-by-letter.json` - ); - } - - const formattedData: FormattedSteamGamesByLetter = {}; - for (const [letter, games] of Object.entries(steamGames)) { - formattedData[letter] = games.map((game) => ({ - ...game, - formattedName: formatName(game.name), - })); - } - - steamGamesFormattedCache = formattedData; - return formattedData; -}; - -export type SublevelIterator = AsyncIterable<[string, { id: number }]>; - -export interface SublevelWithId { - iterator: () => SublevelIterator; -} - -let maxRepackId: number | null = null; -let maxDownloadSourceId: number | null = null; - -export const getNextId = async (sublevel: SublevelWithId): Promise => { - const isRepackSublevel = sublevel === repacksSublevel; - const isDownloadSourceSublevel = sublevel === downloadSourcesSublevel; - - if (isRepackSublevel && maxRepackId !== null) { - return ++maxRepackId; - } - - if (isDownloadSourceSublevel && maxDownloadSourceId !== null) { - return ++maxDownloadSourceId; - } - - let maxId = 0; - for await (const [, value] of sublevel.iterator()) { - if (value.id > maxId) { - maxId = value.id; - } - } - - if (isRepackSublevel) { - maxRepackId = maxId; - } else if (isDownloadSourceSublevel) { - maxDownloadSourceId = maxId; - } - - return maxId + 1; -}; - -export const invalidateIdCaches = () => { - maxRepackId = null; - maxDownloadSourceId = null; -}; - -export const addNewDownloads = async ( - downloadSource: { id: number; name: string }, - downloads: z.infer["downloads"], - steamGames: FormattedSteamGamesByLetter -) => { - const now = new Date(); - const objectIdsOnSource = new Set(); - - let nextRepackId = await getNextId(repacksSublevel); - - const batch = repacksSublevel.batch(); - - // Get title hash mapping and perform matching in worker thread - const titleHashMapping = await getTitleHashMapping(); - - const { GameMatcherWorkerManager } = await import("@main/services"); - const matchResult = await GameMatcherWorkerManager.matchDownloads( - downloads, - steamGames, - titleHashMapping - ); - - // Process matched results and write to database - for (const matchedDownload of matchResult.matchedDownloads) { - for (const id of matchedDownload.objectIds) { - objectIdsOnSource.add(id); - } - - const repack = { - id: nextRepackId++, - objectIds: matchedDownload.objectIds, - title: matchedDownload.title, - uris: matchedDownload.uris, - fileSize: matchedDownload.fileSize, - repacker: downloadSource.name, - uploadDate: matchedDownload.uploadDate, - downloadSourceId: downloadSource.id, - createdAt: now, - updatedAt: now, - }; - - batch.put(`${repack.id}`, repack); - } - - await batch.write(); - - logger.info( - `Matching stats for ${downloadSource.name}: Hash=${matchResult.stats.hashMatchCount}, Fuzzy=${matchResult.stats.fuzzyMatchCount}, None=${matchResult.stats.noMatchCount}` - ); - - const existingSource = await downloadSourcesSublevel.get( - `${downloadSource.id}` - ); - if (existingSource) { - await downloadSourcesSublevel.put(`${downloadSource.id}`, { - ...existingSource, - objectIds: Array.from(objectIdsOnSource), - }); - } - - return Array.from(objectIdsOnSource); -}; - -export const importDownloadSourceToLocal = async ( - url: string, - throwOnDuplicate = false -) => { - const urlExists = await checkUrlExists(url); - if (urlExists) { - if (throwOnDuplicate) { - throw new Error("Download source with this URL already exists"); - } - return null; - } - - const response = await axios.get>(url); - - const steamGames = await getSteamGames(); - - const now = new Date(); - - const nextId = await getNextId(downloadSourcesSublevel); - - const downloadSource = { - id: nextId, - url, - name: response.data.name, - etag: response.headers["etag"] || null, - status: DownloadSourceStatus.UpToDate, - downloadCount: response.data.downloads.length, - objectIds: [], - createdAt: now, - updatedAt: now, - }; - - await downloadSourcesSublevel.put(`${downloadSource.id}`, downloadSource); - - const objectIds = await addNewDownloads( - downloadSource, - response.data.downloads, - steamGames - ); - - // Invalidate ID caches after creating new repacks to prevent ID collisions - invalidateIdCaches(); - - return { - ...downloadSource, - objectIds, - }; -}; - -export const updateDownloadSourcePreservingTimestamp = async ( - existingSource: DownloadSource, - url: string -) => { - const response = await axios.get>(url); - - const updatedSource = { - ...existingSource, - name: response.data.name, - etag: response.headers["etag"] || null, - status: DownloadSourceStatus.UpToDate, - downloadCount: response.data.downloads.length, - updatedAt: new Date(), - // Preserve the original createdAt timestamp - }; - - await downloadSourcesSublevel.put(`${existingSource.id}`, updatedSource); - - return updatedSource; -}; diff --git a/src/main/events/download-sources/remove-download-source.ts b/src/main/events/download-sources/remove-download-source.ts index bcc66998..8efe0072 100644 --- a/src/main/events/download-sources/remove-download-source.ts +++ b/src/main/events/download-sources/remove-download-source.ts @@ -1,18 +1,27 @@ import { HydraApi } from "@main/services"; +import { downloadSourcesSublevel } from "@main/level"; import { registerEvent } from "../register-event"; const removeDownloadSource = async ( _event: Electron.IpcMainInvokeEvent, - url?: string, - removeAll = false + removeAll = false, + downloadSourceId?: string ) => { const params = new URLSearchParams({ all: removeAll.toString(), }); - if (url) params.set("url", url); + if (downloadSourceId) params.set("downloadSourceId", downloadSourceId); - return HydraApi.delete(`/profile/download-sources?${params.toString()}`); + if (HydraApi.isLoggedIn()) { + void HydraApi.delete(`/profile/download-sources?${params.toString()}`); + } + + if (removeAll) { + await downloadSourcesSublevel.clear(); + } else if (downloadSourceId) { + await downloadSourcesSublevel.del(downloadSourceId); + } }; registerEvent("removeDownloadSource", removeDownloadSource); diff --git a/src/main/events/download-sources/sync-download-sources-from-api.ts b/src/main/events/download-sources/sync-download-sources-from-api.ts deleted file mode 100644 index 3cac8819..00000000 --- a/src/main/events/download-sources/sync-download-sources-from-api.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { HydraApi, logger } from "@main/services"; -import { importDownloadSourceToLocal, checkUrlExists } from "./helpers"; - -export const syncDownloadSourcesFromApi = async () => { - try { - const apiSources = await HydraApi.get< - { url: string; createdAt: string; updatedAt: string }[] - >("/profile/download-sources"); - - for (const apiSource of apiSources) { - const exists = await checkUrlExists(apiSource.url); - if (!exists) { - await importDownloadSourceToLocal(apiSource.url, false); - } - } - } catch (error) { - logger.error("Failed to sync download sources from API:", error); - } -}; diff --git a/src/main/events/download-sources/sync-download-sources.ts b/src/main/events/download-sources/sync-download-sources.ts index 3bb78f22..987ad1c1 100644 --- a/src/main/events/download-sources/sync-download-sources.ts +++ b/src/main/events/download-sources/sync-download-sources.ts @@ -1,105 +1,24 @@ +import { HydraApi } from "@main/services"; import { registerEvent } from "../register-event"; -import axios, { AxiosError } from "axios"; -import { downloadSourcesSublevel, repacksSublevel } from "@main/level"; -import { DownloadSourceStatus } from "@shared"; -import { - invalidateIdCaches, - downloadSourceSchema, - getSteamGames, - addNewDownloads, -} from "./helpers"; +import { downloadSourcesSublevel } from "@main/level"; +import type { DownloadSource } from "@types"; -const syncDownloadSources = async ( - _event: Electron.IpcMainInvokeEvent -): Promise => { - let newRepacksCount = 0; +const syncDownloadSources = async (_event: Electron.IpcMainInvokeEvent) => { + const downloadSources = await downloadSourcesSublevel.values().all(); - try { - const downloadSources: Array<{ - id: number; - url: string; - name: string; - etag: string | null; - status: number; - downloadCount: number; - objectIds: string[]; - fingerprint?: string; - createdAt: Date; - updatedAt: Date; - }> = []; - for await (const [, source] of downloadSourcesSublevel.iterator()) { - downloadSources.push(source); - } + const response = await HydraApi.post( + "/download-sources/sync", + { + ids: downloadSources.map((downloadSource) => downloadSource.id), + }, + { needsAuth: false } + ); - // Use a Set for O(1) lookups instead of O(n) with array.some() - const existingRepackTitles = new Set(); - for await (const [, repack] of repacksSublevel.iterator()) { - existingRepackTitles.add(repack.title); - } - - // Handle sources with missing fingerprints individually, don't delete all sources - const sourcesWithFingerprints = downloadSources.filter( - (source) => source.fingerprint - ); - const sourcesWithoutFingerprints = downloadSources.filter( - (source) => !source.fingerprint - ); - - // For sources without fingerprints, just continue with normal sync - // They will get fingerprints updated later by updateMissingFingerprints - const allSourcesToSync = [ - ...sourcesWithFingerprints, - ...sourcesWithoutFingerprints, - ]; - - for (const downloadSource of allSourcesToSync) { - const headers: Record = {}; - - if (downloadSource.etag) { - headers["If-None-Match"] = downloadSource.etag; - } - - try { - const response = await axios.get(downloadSource.url, { - headers, - }); - - const source = downloadSourceSchema.parse(response.data); - const steamGames = await getSteamGames(); - - // O(1) lookup instead of O(n) - massive performance improvement - const repacks = source.downloads.filter( - (download) => !existingRepackTitles.has(download.title) - ); - - await downloadSourcesSublevel.put(`${downloadSource.id}`, { - ...downloadSource, - etag: response.headers["etag"] || null, - downloadCount: source.downloads.length, - status: DownloadSourceStatus.UpToDate, - }); - - await addNewDownloads(downloadSource, repacks, steamGames); - - newRepacksCount += repacks.length; - } catch (err: unknown) { - const isNotModified = (err as AxiosError).response?.status === 304; - - await downloadSourcesSublevel.put(`${downloadSource.id}`, { - ...downloadSource, - status: isNotModified - ? DownloadSourceStatus.UpToDate - : DownloadSourceStatus.Errored, - }); - } - } - - invalidateIdCaches(); - - return newRepacksCount; - } catch (err) { - return -1; + for (const downloadSource of response) { + await downloadSourcesSublevel.put(downloadSource.id, downloadSource); } + + return response; }; registerEvent("syncDownloadSources", syncDownloadSources); diff --git a/src/main/events/download-sources/update-missing-fingerprints.ts b/src/main/events/download-sources/update-missing-fingerprints.ts deleted file mode 100644 index 7fd43c63..00000000 --- a/src/main/events/download-sources/update-missing-fingerprints.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { registerEvent } from "../register-event"; -import { downloadSourcesSublevel } from "@main/level"; -import { HydraApi, logger } from "@main/services"; - -const updateMissingFingerprints = async ( - _event: Electron.IpcMainInvokeEvent -): Promise => { - const sourcesNeedingFingerprints: Array<{ - id: number; - objectIds: string[]; - }> = []; - - for await (const [, source] of downloadSourcesSublevel.iterator()) { - if ( - !source.fingerprint && - source.objectIds && - source.objectIds.length > 0 - ) { - sourcesNeedingFingerprints.push({ - id: source.id, - objectIds: source.objectIds, - }); - } - } - - if (sourcesNeedingFingerprints.length === 0) { - return 0; - } - - logger.info( - `Updating fingerprints for ${sourcesNeedingFingerprints.length} sources` - ); - - await Promise.all( - sourcesNeedingFingerprints.map(async (source) => { - try { - const { fingerprint } = await HydraApi.put<{ fingerprint: string }>( - "/download-sources", - { - objectIds: source.objectIds, - }, - { needsAuth: false } - ); - - const existingSource = await downloadSourcesSublevel.get( - `${source.id}` - ); - if (existingSource) { - await downloadSourcesSublevel.put(`${source.id}`, { - ...existingSource, - fingerprint, - updatedAt: new Date(), - }); - } - } catch (error) { - logger.error( - `Failed to update fingerprint for source ${source.id}:`, - error - ); - } - }) - ); - - return sourcesNeedingFingerprints.length; -}; - -registerEvent("updateMissingFingerprints", updateMissingFingerprints); diff --git a/src/main/events/download-sources/validate-download-source.ts b/src/main/events/download-sources/validate-download-source.ts deleted file mode 100644 index 2bc86df7..00000000 --- a/src/main/events/download-sources/validate-download-source.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { registerEvent } from "../register-event"; -import axios from "axios"; -import { z } from "zod"; - -const downloadSourceSchema = z.object({ - name: z.string().max(255), - downloads: z.array( - z.object({ - title: z.string().max(255), - uris: z.array(z.string()), - uploadDate: z.string().max(255), - fileSize: z.string().max(255), - }) - ), -}); - -const validateDownloadSource = async ( - _event: Electron.IpcMainInvokeEvent, - url: string -) => { - const response = await axios.get>(url); - - const { name } = downloadSourceSchema.parse(response.data); - - return { - name, - etag: response.headers["etag"] || null, - downloadCount: response.data.downloads.length, - }; -}; - -registerEvent("validateDownloadSource", validateDownloadSource); diff --git a/src/main/events/index.ts b/src/main/events/index.ts index 8d21aa11..0ab5499a 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -63,14 +63,7 @@ import "./autoupdater/restart-and-install-update"; import "./user-preferences/authenticate-real-debrid"; import "./user-preferences/authenticate-torbox"; import "./download-sources/add-download-source"; -import "./download-sources/update-missing-fingerprints"; -import "./download-sources/delete-download-source"; -import "./download-sources/delete-all-download-sources"; -import "./download-sources/validate-download-source"; import "./download-sources/sync-download-sources"; -import "./download-sources/get-download-sources-list"; -import "./download-sources/check-download-source-exists"; -import "./repacks/get-all-repacks"; import "./auth/sign-out"; import "./auth/open-auth-window"; import "./auth/get-session-hash"; diff --git a/src/main/events/repacks/get-all-repacks.ts b/src/main/events/repacks/get-all-repacks.ts deleted file mode 100644 index 6eb83a39..00000000 --- a/src/main/events/repacks/get-all-repacks.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { registerEvent } from "../register-event"; -import { repacksSublevel, GameRepack } from "@main/level"; - -const getAllRepacks = async (_event: Electron.IpcMainInvokeEvent) => { - const repacks: GameRepack[] = []; - - for await (const [, repack] of repacksSublevel.iterator()) { - if (Array.isArray(repack.objectIds)) { - repacks.push(repack); - } - } - - return repacks; -}; - -registerEvent("getAllRepacks", getAllRepacks); diff --git a/src/main/level/sublevels/download-sources.ts b/src/main/level/sublevels/download-sources.ts index 59104e3c..b6cdad0b 100644 --- a/src/main/level/sublevels/download-sources.ts +++ b/src/main/level/sublevels/download-sources.ts @@ -1,18 +1,6 @@ import { db } from "../level"; import { levelKeys } from "./keys"; - -export interface DownloadSource { - id: number; - name: string; - url: string; - status: number; - objectIds: string[]; - downloadCount: number; - fingerprint?: string; - etag: string | null; - createdAt: Date; - updatedAt: Date; -} +import type { DownloadSource } from "@types"; export const downloadSourcesSublevel = db.sublevel( levelKeys.downloadSources, diff --git a/src/main/level/sublevels/index.ts b/src/main/level/sublevels/index.ts index 7224fc64..3619ae26 100644 --- a/src/main/level/sublevels/index.ts +++ b/src/main/level/sublevels/index.ts @@ -7,4 +7,3 @@ export * from "./game-achievements"; export * from "./keys"; export * from "./themes"; export * from "./download-sources"; -export * from "./repacks"; diff --git a/src/main/level/sublevels/keys.ts b/src/main/level/sublevels/keys.ts index 6faacd52..a28690b2 100644 --- a/src/main/level/sublevels/keys.ts +++ b/src/main/level/sublevels/keys.ts @@ -18,5 +18,4 @@ export const levelKeys = { screenState: "screenState", rpcPassword: "rpcPassword", downloadSources: "downloadSources", - repacks: "repacks", }; diff --git a/src/main/level/sublevels/repacks.ts b/src/main/level/sublevels/repacks.ts deleted file mode 100644 index 6257665b..00000000 --- a/src/main/level/sublevels/repacks.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { db } from "../level"; -import { levelKeys } from "./keys"; - -export interface GameRepack { - id: number; - title: string; - uris: string[]; - repacker: string; - fileSize: string | null; - objectIds: string[]; - uploadDate: Date | string | null; - downloadSourceId: number; - createdAt: Date; - updatedAt: Date; -} - -export const repacksSublevel = db.sublevel( - levelKeys.repacks, - { - valueEncoding: "json", - } -); diff --git a/src/main/main.ts b/src/main/main.ts index e9b6187c..617dd135 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -16,19 +16,11 @@ import { Ludusavi, Lock, DeckyPlugin, - ResourceCache, - GameMatcherWorkerManager, } from "@main/services"; export const loadState = async () => { await Lock.acquireLock(); - ResourceCache.initialize(); - await ResourceCache.updateResourcesOnStartup(); - - // Initialize game matcher worker thread - GameMatcherWorkerManager.initialize(); - const userPreferences = await db.get( levelKeys.userPreferences, { diff --git a/src/main/services/game-matcher-worker-manager.ts b/src/main/services/game-matcher-worker-manager.ts deleted file mode 100644 index b5d306c7..00000000 --- a/src/main/services/game-matcher-worker-manager.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { Worker } from "worker_threads"; -import workerPath from "../workers/game-matcher-worker?modulePath"; - -interface WorkerMessage { - id: string; - data: unknown; -} - -interface WorkerResponse { - id: string; - success: boolean; - result?: unknown; - error?: string; -} - -export type TitleHashMapping = Record; - -export type FormattedSteamGame = { - id: string; - name: string; - formattedName: string; -}; -export type FormattedSteamGamesByLetter = Record; - -interface DownloadToMatch { - title: string; - uris: string[]; - uploadDate: string; - fileSize: string; -} - -interface MatchedDownload { - title: string; - uris: string[]; - uploadDate: string; - fileSize: string; - objectIds: string[]; - usedHashMatch: boolean; -} - -interface MatchResponse { - matchedDownloads: MatchedDownload[]; - stats: { - hashMatchCount: number; - fuzzyMatchCount: number; - noMatchCount: number; - }; -} - -export class GameMatcherWorkerManager { - private static worker: Worker | null = null; - private static messageId = 0; - private static pendingMessages = new Map< - string, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - { resolve: (value: any) => void; reject: (error: Error) => void } - >(); - - public static initialize() { - if (this.worker) { - return; - } - - try { - console.log( - "[GameMatcherWorker] Initializing worker with path:", - workerPath - ); - - this.worker = new Worker(workerPath); - - this.worker.on("message", (response: WorkerResponse) => { - const pending = this.pendingMessages.get(response.id); - if (pending) { - if (response.success) { - pending.resolve(response.result); - } else { - pending.reject(new Error(response.error || "Unknown error")); - } - this.pendingMessages.delete(response.id); - } - }); - - this.worker.on("error", (error) => { - console.error("[GameMatcherWorker] Worker error:", error); - for (const [id, pending] of this.pendingMessages.entries()) { - pending.reject(error); - this.pendingMessages.delete(id); - } - }); - - this.worker.on("exit", (code) => { - if (code !== 0) { - console.error( - `[GameMatcherWorker] Worker stopped with exit code ${code}` - ); - } - this.worker = null; - for (const [id, pending] of this.pendingMessages.entries()) { - pending.reject(new Error("Worker exited unexpectedly")); - this.pendingMessages.delete(id); - } - }); - - console.log("[GameMatcherWorker] Worker initialized successfully"); - } catch (error) { - console.error("[GameMatcherWorker] Failed to initialize worker:", error); - throw error; - } - } - - private static sendMessage(data: unknown): Promise { - if (!this.worker) { - return Promise.reject(new Error("Worker not initialized")); - } - - const id = `msg_${++this.messageId}`; - const message: WorkerMessage = { id, data }; - - return new Promise((resolve, reject) => { - this.pendingMessages.set(id, { resolve, reject }); - this.worker!.postMessage(message); - }); - } - - public static async matchDownloads( - downloads: DownloadToMatch[], - steamGames: FormattedSteamGamesByLetter, - titleHashMapping: TitleHashMapping - ): Promise { - return this.sendMessage({ - downloads, - steamGames, - titleHashMapping, - }); - } - - public static terminate() { - if (this.worker) { - this.worker.terminate(); - this.worker = null; - this.pendingMessages.clear(); - } - } -} diff --git a/src/main/services/hydra-api.ts b/src/main/services/hydra-api.ts index dd26e6f0..ffc5756c 100644 --- a/src/main/services/hydra-api.ts +++ b/src/main/services/hydra-api.ts @@ -105,11 +105,6 @@ export class HydraApi { // WSClient.close(); // WSClient.connect(); - - const { syncDownloadSourcesFromApi } = await import( - "../events/download-sources/sync-download-sources-from-api" - ); - syncDownloadSourcesFromApi(); } } diff --git a/src/main/services/index.ts b/src/main/services/index.ts index 0853859f..88b39d1b 100644 --- a/src/main/services/index.ts +++ b/src/main/services/index.ts @@ -18,5 +18,3 @@ export * from "./library-sync"; export * from "./wine"; export * from "./lock"; export * from "./decky-plugin"; -export * from "./resource-cache"; -export * from "./game-matcher-worker-manager"; diff --git a/src/main/services/resource-cache.ts b/src/main/services/resource-cache.ts deleted file mode 100644 index c59f873d..00000000 --- a/src/main/services/resource-cache.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { app } from "electron"; -import axios from "axios"; -import fs from "node:fs"; -import path from "node:path"; -import { logger } from "./logger"; - -interface CachedResource { - data: T; - etag: string | null; -} - -export class ResourceCache { - private static cacheDir: string; - - static initialize() { - this.cacheDir = path.join(app.getPath("userData"), "resource-cache"); - - if (!fs.existsSync(this.cacheDir)) { - fs.mkdirSync(this.cacheDir, { recursive: true }); - } - } - - private static getCacheFilePath(resourceName: string): string { - return path.join(this.cacheDir, `${resourceName}.json`); - } - - private static getEtagFilePath(resourceName: string): string { - return path.join(this.cacheDir, `${resourceName}.etag`); - } - - private static readCachedResource( - resourceName: string - ): CachedResource | null { - const dataPath = this.getCacheFilePath(resourceName); - const etagPath = this.getEtagFilePath(resourceName); - - if (!fs.existsSync(dataPath)) { - return null; - } - - try { - const data = JSON.parse(fs.readFileSync(dataPath, "utf-8")) as T; - const etag = fs.existsSync(etagPath) - ? fs.readFileSync(etagPath, "utf-8") - : null; - - return { data, etag }; - } catch (error) { - logger.error(`Failed to read cached resource ${resourceName}:`, error); - return null; - } - } - - private static writeCachedResource( - resourceName: string, - data: T, - etag: string | null - ): void { - const dataPath = this.getCacheFilePath(resourceName); - const etagPath = this.getEtagFilePath(resourceName); - - try { - fs.writeFileSync(dataPath, JSON.stringify(data), "utf-8"); - - if (etag) { - fs.writeFileSync(etagPath, etag, "utf-8"); - } - - logger.info( - `Cached resource ${resourceName} with etag: ${etag || "none"}` - ); - } catch (error) { - logger.error(`Failed to write cached resource ${resourceName}:`, error); - } - } - - static async fetchAndCache( - resourceName: string, - url: string, - timeout: number = 10000 - ): Promise { - const cached = this.readCachedResource(resourceName); - const headers: Record = {}; - - if (cached?.etag) { - headers["If-None-Match"] = cached.etag; - } - - try { - const response = await axios.get(url, { - headers, - timeout, - }); - - const newEtag = response.headers["etag"] || null; - this.writeCachedResource(resourceName, response.data, newEtag); - - return response.data; - } catch (error: unknown) { - const axiosError = error as { - response?: { status?: number }; - message?: string; - }; - - if (axiosError.response?.status === 304 && cached) { - logger.info(`Resource ${resourceName} not modified, using cache`); - return cached.data; - } - - if (cached) { - logger.warn( - `Failed to fetch ${resourceName}, using cached version:`, - axiosError.message || "Unknown error" - ); - return cached.data; - } - - logger.error( - `Failed to fetch ${resourceName} and no cache available:`, - error - ); - throw error; - } - } - - static getCachedData(resourceName: string): T | null { - const cached = this.readCachedResource(resourceName); - return cached?.data || null; - } - - static async updateResourcesOnStartup(): Promise { - logger.info("Starting background resource cache update..."); - - const resources = [ - { - name: "steam-games-by-letter", - url: `${import.meta.env.MAIN_VITE_EXTERNAL_RESOURCES_URL}/steam-games-by-letter.json`, - }, - { - name: "sources-manifest", - url: "https://cdn.losbroxas.org/sources-manifest.json", - }, - ]; - - await Promise.allSettled( - resources.map(async (resource) => { - try { - await this.fetchAndCache(resource.name, resource.url); - } catch (error) { - logger.error(`Failed to update ${resource.name} on startup:`, error); - } - }) - ); - - logger.info("Resource cache update complete"); - } -} diff --git a/src/main/workers/game-matcher-worker.ts b/src/main/workers/game-matcher-worker.ts deleted file mode 100644 index 4930ada0..00000000 --- a/src/main/workers/game-matcher-worker.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { parentPort } from "worker_threads"; -import crypto from "node:crypto"; - -export type TitleHashMapping = Record; - -export type FormattedSteamGame = { - id: string; - name: string; - formattedName: string; -}; -export type FormattedSteamGamesByLetter = Record; - -interface DownloadToMatch { - title: string; - uris: string[]; - uploadDate: string; - fileSize: string; -} - -interface MatchedDownload { - title: string; - uris: string[]; - uploadDate: string; - fileSize: string; - objectIds: string[]; - usedHashMatch: boolean; -} - -interface MatchRequest { - downloads: DownloadToMatch[]; - steamGames: FormattedSteamGamesByLetter; - titleHashMapping: TitleHashMapping; -} - -interface MatchResponse { - matchedDownloads: MatchedDownload[]; - stats: { - hashMatchCount: number; - fuzzyMatchCount: number; - noMatchCount: number; - }; -} - -const hashTitle = (title: string): string => { - return crypto.createHash("sha256").update(title).digest("hex"); -}; - -const formatName = (name: string) => { - return name - .normalize("NFD") - .replaceAll(/[\u0300-\u036f]/g, "") - .toLowerCase() - .replaceAll(/[^a-z0-9]/g, ""); -}; - -const formatRepackName = (name: string) => { - return formatName(name.replace("[DL]", "")); -}; - -const matchDownloads = (request: MatchRequest): MatchResponse => { - const { downloads, steamGames, titleHashMapping } = request; - const matchedDownloads: MatchedDownload[] = []; - - let hashMatchCount = 0; - let fuzzyMatchCount = 0; - let noMatchCount = 0; - - for (const download of downloads) { - let objectIds: string[] = []; - let usedHashMatch = false; - - const titleHash = hashTitle(download.title); - const steamIdsFromHash = titleHashMapping[titleHash]; - - if (steamIdsFromHash && steamIdsFromHash.length > 0) { - hashMatchCount++; - usedHashMatch = true; - objectIds = steamIdsFromHash.map(String); - } - - if (!usedHashMatch) { - let gamesInSteam: FormattedSteamGame[] = []; - const formattedTitle = formatRepackName(download.title); - - if (formattedTitle && formattedTitle.length > 0) { - const [firstLetter] = formattedTitle; - const games = steamGames[firstLetter] || []; - - gamesInSteam = games.filter((game) => - formattedTitle.startsWith(game.formattedName) - ); - - if (gamesInSteam.length === 0) { - gamesInSteam = games.filter( - (game) => - formattedTitle.includes(game.formattedName) || - game.formattedName.includes(formattedTitle) - ); - } - - if (gamesInSteam.length === 0) { - for (const letter of Object.keys(steamGames)) { - const letterGames = steamGames[letter] || []; - const matches = letterGames.filter( - (game) => - formattedTitle.includes(game.formattedName) || - game.formattedName.includes(formattedTitle) - ); - if (matches.length > 0) { - gamesInSteam = matches; - break; - } - } - } - - if (gamesInSteam.length > 0) { - fuzzyMatchCount++; - objectIds = gamesInSteam.map((game) => String(game.id)); - } else { - noMatchCount++; - } - } else { - noMatchCount++; - } - } - - matchedDownloads.push({ - ...download, - objectIds, - usedHashMatch, - }); - } - - return { - matchedDownloads, - stats: { - hashMatchCount, - fuzzyMatchCount, - noMatchCount, - }, - }; -}; - -// Message handler -if (parentPort) { - parentPort.on("message", (message: { id: string; data: MatchRequest }) => { - try { - const result = matchDownloads(message.data); - parentPort!.postMessage({ id: message.id, success: true, result }); - } catch (error) { - parentPort!.postMessage({ - id: message.id, - success: false, - error: error instanceof Error ? error.message : String(error), - }); - } - }); -} diff --git a/src/preload/index.ts b/src/preload/index.ts index da914b92..f89ec4db 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -99,22 +99,10 @@ contextBridge.exposeInMainWorld("electron", { /* Download sources */ addDownloadSource: (url: string) => ipcRenderer.invoke("addDownloadSource", url), - updateMissingFingerprints: () => - ipcRenderer.invoke("updateMissingFingerprints"), removeDownloadSource: (url: string, removeAll?: boolean) => ipcRenderer.invoke("removeDownloadSource", url, removeAll), getDownloadSources: () => ipcRenderer.invoke("getDownloadSources"), - deleteDownloadSource: (id: number) => - ipcRenderer.invoke("deleteDownloadSource", id), - deleteAllDownloadSources: () => - ipcRenderer.invoke("deleteAllDownloadSources"), - validateDownloadSource: (url: string) => - ipcRenderer.invoke("validateDownloadSource", url), syncDownloadSources: () => ipcRenderer.invoke("syncDownloadSources"), - getDownloadSourcesList: () => ipcRenderer.invoke("getDownloadSourcesList"), - checkDownloadSourceExists: (url: string) => - ipcRenderer.invoke("checkDownloadSourceExists", url), - getAllRepacks: () => ipcRenderer.invoke("getAllRepacks"), /* Library */ toggleAutomaticCloudSync: ( diff --git a/src/renderer/src/app.tsx b/src/renderer/src/app.tsx index 74a2a97e..168a4435 100644 --- a/src/renderer/src/app.tsx +++ b/src/renderer/src/app.tsx @@ -7,7 +7,6 @@ import { useAppSelector, useDownload, useLibrary, - useRepacks, useToast, useUserDetails, } from "@renderer/hooks"; @@ -20,7 +19,6 @@ import { setUserDetails, setProfileBackground, setGameRunning, - setIsImportingSources, } from "@renderer/features"; import { useTranslation } from "react-i18next"; import { UserFriendModal } from "./pages/shared-modals/user-friend-modal"; @@ -40,8 +38,6 @@ export function App() { const { t } = useTranslation("app"); - const { updateRepacks } = useRepacks(); - const { clearDownload, setLastPacket } = useDownload(); const { @@ -199,36 +195,6 @@ export function App() { }); }, [dispatch, draggingDisabled]); - useEffect(() => { - (async () => { - dispatch(setIsImportingSources(true)); - - try { - // Initial repacks load - await updateRepacks(); - - // Sync all local sources (check for updates) - const newRepacksCount = await window.electron.syncDownloadSources(); - - if (newRepacksCount > 0) { - window.electron.publishNewRepacksNotification(newRepacksCount); - } - - // Update fingerprints for sources that don't have them - await window.electron.updateMissingFingerprints(); - - // Update repacks AFTER all syncing and fingerprint updates are complete - await updateRepacks(); - } catch (error) { - console.error("Error syncing download sources:", error); - // Still update repacks even if sync fails - await updateRepacks(); - } finally { - dispatch(setIsImportingSources(false)); - } - })(); - }, [updateRepacks, dispatch]); - const loadAndApplyTheme = useCallback(async () => { const activeTheme = await window.electron.getActiveCustomTheme(); if (activeTheme?.code) { diff --git a/src/renderer/src/components/game-card/game-card.tsx b/src/renderer/src/components/game-card/game-card.tsx index 5752ba19..598874b5 100644 --- a/src/renderer/src/components/game-card/game-card.tsx +++ b/src/renderer/src/components/game-card/game-card.tsx @@ -1,5 +1,5 @@ import { DownloadIcon, PeopleIcon } from "@primer/octicons-react"; -import type { GameStats } from "@types"; +import type { GameStats, ShopAssets } from "@types"; import SteamLogo from "@renderer/assets/steam-logo.svg?react"; @@ -8,15 +8,15 @@ import "./game-card.scss"; import { useTranslation } from "react-i18next"; import { Badge } from "../badge/badge"; import { StarRating } from "../star-rating/star-rating"; -import { useCallback, useState, useMemo } from "react"; -import { useFormat, useRepacks } from "@renderer/hooks"; +import { useCallback, useState } from "react"; +import { useFormat } from "@renderer/hooks"; export interface GameCardProps extends React.DetailedHTMLProps< React.ButtonHTMLAttributes, HTMLButtonElement > { - game: any; + game: ShopAssets; } const shopIcon = { @@ -28,13 +28,6 @@ export function GameCard({ game, ...props }: GameCardProps) { const [stats, setStats] = useState(null); - const { getRepacksForObjectId } = useRepacks(); - const repacks = getRepacksForObjectId(game.objectId); - - const uniqueRepackers = Array.from( - new Set(repacks.map((repack) => repack.repacker)) - ); - const handleHover = useCallback(() => { if (!stats) { window.electron.getGameStats(game.objectId, game.shop).then((stats) => { @@ -45,14 +38,7 @@ export function GameCard({ game, ...props }: GameCardProps) { const { numberFormatter } = useFormat(); - const firstThreeRepackers = useMemo( - () => uniqueRepackers.slice(0, 3), - [uniqueRepackers] - ); - const remainingCount = useMemo( - () => uniqueRepackers.length - 3, - [uniqueRepackers] - ); + console.log("game", game); return (
    - {uniqueRepackers.length > 0 ? ( + {game.downloadSources.length > 0 ? (
      - {firstThreeRepackers.map((repacker) => ( -
    • - {repacker} + {game.downloadSources.slice(0, 3).map((sourceName) => ( +
    • + {sourceName}
    • ))} - {remainingCount > 0 && ( + {game.downloadSources.length > 3 && (
    • - +{remainingCount}{" "} - {t("game_card:available", { count: remainingCount })} + +{game.downloadSources.length - 3}{" "} + {t("game_card:available", { + count: game.downloadSources.length - 3, + })}
    • )} diff --git a/src/renderer/src/context/game-details/game-details.context.tsx b/src/renderer/src/context/game-details/game-details.context.tsx index 14e5d587..2b8e8bf7 100644 --- a/src/renderer/src/context/game-details/game-details.context.tsx +++ b/src/renderer/src/context/game-details/game-details.context.tsx @@ -1,11 +1,4 @@ -import { - createContext, - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from "react"; +import { createContext, useCallback, useEffect, useRef, useState } from "react"; import { setHeaderTitle } from "@renderer/features"; import { getSteamLanguage } from "@renderer/helpers"; @@ -13,11 +6,11 @@ import { useAppDispatch, useAppSelector, useDownload, - useRepacks, useUserDetails, } from "@renderer/hooks"; import type { + GameRepack, GameShop, GameStats, LibraryGame, @@ -84,12 +77,7 @@ export function GameDetailsContextProvider({ const [isGameRunning, setIsGameRunning] = useState(false); const [showRepacksModal, setShowRepacksModal] = useState(false); const [showGameOptionsModal, setShowGameOptionsModal] = useState(false); - - const { getRepacksForObjectId } = useRepacks(); - - const repacks = useMemo(() => { - return getRepacksForObjectId(objectId); - }, [getRepacksForObjectId, objectId]); + const [repacks, setRepacks] = useState([]); const { i18n } = useTranslation("game_details"); const location = useLocation(); @@ -287,19 +275,6 @@ export function GameDetailsContextProvider({ } }, [location]); - const lastDownloadedOption = useMemo(() => { - if (game?.download) { - const repack = repacks.find((repack) => - repack.uris.some((uri) => uri.includes(game.download!.uri)) - ); - - if (!repack) return null; - return repack; - } - - return null; - }, [game?.download, repacks]); - useEffect(() => { const unsubscribe = window.electron.onUpdateAchievements( objectId, @@ -315,6 +290,34 @@ export function GameDetailsContextProvider({ }; }, [objectId, shop, userDetails]); + useEffect(() => { + const fetchDownloadSources = async () => { + try { + const sources = await window.electron.getDownloadSources(); + + const params = { + take: 100, + skip: 0, + downloadSourceIds: sources.map((source) => source.id), + }; + + const downloads = await window.electron.hydraApi.get( + `/games/${shop}/${objectId}/download-sources`, + { + params, + needsAuth: false, + } + ); + + setRepacks(downloads); + } catch (error) { + console.error("Failed to fetch download sources:", error); + } + }; + + fetchDownloadSources(); + }, [shop, objectId]); + const getDownloadsPath = async () => { if (userPreferences?.downloadsPath) return userPreferences.downloadsPath; return window.electron.getDefaultDownloadsPath(); @@ -359,7 +362,7 @@ export function GameDetailsContextProvider({ stats, achievements, hasNSFWContentBlocked, - lastDownloadedOption, + lastDownloadedOption: null, setHasNSFWContentBlocked, selectGameExecutable, updateGame, diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 9f882aed..4e004e2b 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -31,8 +31,6 @@ import type { Game, DiskUsage, DownloadSource, - DownloadSourceValidationResult, - GameRepack, } from "@types"; import type { AxiosProgressEvent } from "axios"; @@ -210,20 +208,12 @@ declare global { /* Download sources */ addDownloadSource: (url: string) => Promise; - updateMissingFingerprints: () => Promise; - removeDownloadSource: (url: string, removeAll?: boolean) => Promise; - getDownloadSources: () => Promise< - Pick[] - >; - deleteDownloadSource: (id: number) => Promise; - deleteAllDownloadSources: () => Promise; - validateDownloadSource: ( - url: string - ) => Promise; - syncDownloadSources: () => Promise; - getDownloadSourcesList: () => Promise; - checkDownloadSourceExists: (url: string) => Promise; - getAllRepacks: () => Promise; + removeDownloadSource: ( + removeAll = false, + downloadSourceId?: string + ) => Promise; + getDownloadSources: () => Promise; + syncDownloadSources: () => Promise; /* Hardware */ getDiskFreeSpace: (path: string) => Promise; diff --git a/src/renderer/src/features/download-sources-slice.ts b/src/renderer/src/features/download-sources-slice.ts deleted file mode 100644 index 52e58d26..00000000 --- a/src/renderer/src/features/download-sources-slice.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { createSlice } from "@reduxjs/toolkit"; - -export interface DownloadSourcesState { - isImporting: boolean; -} - -const initialState: DownloadSourcesState = { - isImporting: false, -}; - -export const downloadSourcesSlice = createSlice({ - name: "downloadSources", - initialState, - reducers: { - setIsImportingSources: (state, action) => { - state.isImporting = action.payload; - }, - }, -}); - -export const { setIsImportingSources } = downloadSourcesSlice.actions; diff --git a/src/renderer/src/features/index.ts b/src/renderer/src/features/index.ts index 3b602cff..a7e64e1f 100644 --- a/src/renderer/src/features/index.ts +++ b/src/renderer/src/features/index.ts @@ -6,6 +6,4 @@ export * from "./toast-slice"; export * from "./user-details-slice"; export * from "./game-running.slice"; export * from "./subscription-slice"; -export * from "./repacks-slice"; -export * from "./download-sources-slice"; export * from "./catalogue-search"; diff --git a/src/renderer/src/hooks/index.ts b/src/renderer/src/hooks/index.ts index 8140e0cd..73733e2b 100644 --- a/src/renderer/src/hooks/index.ts +++ b/src/renderer/src/hooks/index.ts @@ -5,5 +5,4 @@ export * from "./use-toast"; export * from "./redux"; export * from "./use-user-details"; export * from "./use-format"; -export * from "./use-repacks"; export * from "./use-feature"; diff --git a/src/renderer/src/hooks/use-catalogue.ts b/src/renderer/src/hooks/use-catalogue.ts index 1d0aeb57..675f5013 100644 --- a/src/renderer/src/hooks/use-catalogue.ts +++ b/src/renderer/src/hooks/use-catalogue.ts @@ -2,6 +2,7 @@ import axios from "axios"; import { useCallback, useEffect, useState } from "react"; import { useAppDispatch } from "./redux"; import { setGenres, setTags } from "@renderer/features"; +import type { DownloadSource } from "@types"; export const externalResourcesInstance = axios.create({ baseURL: import.meta.env.RENDERER_VITE_EXTERNAL_RESOURCES_URL, @@ -12,6 +13,7 @@ export function useCatalogue() { const [steamPublishers, setSteamPublishers] = useState([]); const [steamDevelopers, setSteamDevelopers] = useState([]); + const [downloadSources, setDownloadSources] = useState([]); const getSteamUserTags = useCallback(() => { externalResourcesInstance.get("/steam-user-tags.json").then((response) => { @@ -37,17 +39,25 @@ export function useCatalogue() { }); }, []); + const getDownloadSources = useCallback(() => { + window.electron.getDownloadSources().then((results) => { + setDownloadSources(results.filter((source) => !!source.fingerprint)); + }); + }, []); + useEffect(() => { getSteamUserTags(); getSteamGenres(); getSteamPublishers(); getSteamDevelopers(); + getDownloadSources(); }, [ getSteamUserTags, getSteamGenres, getSteamPublishers, getSteamDevelopers, + getDownloadSources, ]); - return { steamPublishers, steamDevelopers }; + return { steamPublishers, downloadSources, steamDevelopers }; } diff --git a/src/renderer/src/hooks/use-repacks.ts b/src/renderer/src/hooks/use-repacks.ts deleted file mode 100644 index c024aaa4..00000000 --- a/src/renderer/src/hooks/use-repacks.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { setRepacks } from "@renderer/features"; -import { useCallback } from "react"; -import { RootState } from "@renderer/store"; -import { useSelector } from "react-redux"; -import { useAppDispatch } from "./redux"; - -export function useRepacks() { - const dispatch = useAppDispatch(); - const repacks = useSelector((state: RootState) => state.repacks.value); - - const getRepacksForObjectId = useCallback( - (objectId: string) => { - return repacks.filter((repack) => repack.objectIds.includes(objectId)); - }, - [repacks] - ); - - const updateRepacks = useCallback(async () => { - const repacks = await window.electron.getAllRepacks(); - dispatch( - setRepacks(repacks.filter((repack) => Array.isArray(repack.objectIds))) - ); - }, [dispatch]); - - return { getRepacksForObjectId, updateRepacks }; -} diff --git a/src/renderer/src/pages/catalogue/catalogue.tsx b/src/renderer/src/pages/catalogue/catalogue.tsx index 07bcf3ff..bbeda906 100644 --- a/src/renderer/src/pages/catalogue/catalogue.tsx +++ b/src/renderer/src/pages/catalogue/catalogue.tsx @@ -1,4 +1,8 @@ -import type { CatalogueSearchResult, DownloadSource } from "@types"; +import type { + CatalogueSearchResult, + CatalogueSearchPayload, + DownloadSource, +} from "@types"; import { useAppDispatch, useAppSelector, useFormat } from "@renderer/hooks"; import { useEffect, useMemo, useRef, useState } from "react"; @@ -29,13 +33,12 @@ export default function Catalogue() { const abortControllerRef = useRef(null); const cataloguePageRef = useRef(null); - const { steamDevelopers, steamPublishers } = useCatalogue(); + const { steamDevelopers, steamPublishers, downloadSources } = useCatalogue(); const { steamGenres, steamUserTags } = useAppSelector( (state) => state.catalogueSearch ); - const [downloadSources, setDownloadSources] = useState([]); const [isLoading, setIsLoading] = useState(true); const [results, setResults] = useState([]); @@ -51,24 +54,41 @@ export default function Catalogue() { const { t, i18n } = useTranslation("catalogue"); const debouncedSearch = useRef( - debounce(async (filters, pageSize, offset) => { - const abortController = new AbortController(); - abortControllerRef.current = abortController; + debounce( + async ( + filters: CatalogueSearchPayload, + downloadSources: DownloadSource[], + pageSize: number, + offset: number + ) => { + const abortController = new AbortController(); + abortControllerRef.current = abortController; - const response = await window.electron.hydraApi.post<{ - edges: CatalogueSearchResult[]; - count: number; - }>("/catalogue/search", { - data: { ...filters, take: pageSize, skip: offset }, - needsAuth: false, - }); + const requestData = { + ...filters, + take: pageSize, + skip: offset, + downloadSourceIds: downloadSources.map( + (downloadSource) => downloadSource.id + ), + }; - if (abortController.signal.aborted) return; + const response = await window.electron.hydraApi.post<{ + edges: CatalogueSearchResult[]; + count: number; + }>("/catalogue/search", { + data: requestData, + needsAuth: false, + }); - setResults(response.edges); - setItemsCount(response.count); - setIsLoading(false); - }, 500) + if (abortController.signal.aborted) return; + + setResults(response.edges); + setItemsCount(response.count); + setIsLoading(false); + }, + 500 + ) ).current; const decodeHTML = (s: string) => @@ -79,18 +99,17 @@ export default function Catalogue() { setIsLoading(true); abortControllerRef.current?.abort(); - debouncedSearch(filters, PAGE_SIZE, (page - 1) * PAGE_SIZE); + debouncedSearch( + filters, + downloadSources, + PAGE_SIZE, + (page - 1) * PAGE_SIZE + ); return () => { debouncedSearch.cancel(); }; - }, [filters, page, debouncedSearch]); - - useEffect(() => { - window.electron.getDownloadSourcesList().then((sources) => { - setDownloadSources(sources.filter((source) => !!source.fingerprint)); - }); - }, []); + }, [filters, downloadSources, page, debouncedSearch]); const language = i18n.language.split("-")[0]; @@ -168,7 +187,7 @@ export default function Catalogue() { value: publisher, })), ]; - }, [filters, steamUserTags, steamGenresMapping, language, downloadSources]); + }, [filters, steamUserTags, downloadSources, steamGenresMapping, language]); const filterSections = useMemo(() => { return [ diff --git a/src/renderer/src/pages/catalogue/game-item.tsx b/src/renderer/src/pages/catalogue/game-item.tsx index ecfe0f73..4583afd3 100644 --- a/src/renderer/src/pages/catalogue/game-item.tsx +++ b/src/renderer/src/pages/catalogue/game-item.tsx @@ -1,6 +1,6 @@ import { Badge } from "@renderer/components"; import { buildGameDetailsPath } from "@renderer/helpers"; -import { useAppSelector, useRepacks, useLibrary } from "@renderer/hooks"; +import { useAppSelector, useLibrary } from "@renderer/hooks"; import { useMemo, useState, useEffect } from "react"; import { useNavigate } from "react-router-dom"; @@ -23,10 +23,6 @@ export function GameItem({ game }: GameItemProps) { const { steamGenres } = useAppSelector((state) => state.catalogueSearch); - const { getRepacksForObjectId } = useRepacks(); - - const repacks = getRepacksForObjectId(game.objectId); - const [isAddingToLibrary, setIsAddingToLibrary] = useState(false); const [added, setAdded] = useState(false); @@ -63,10 +59,6 @@ export function GameItem({ game }: GameItemProps) { } }; - const uniqueRepackers = useMemo(() => { - return Array.from(new Set(repacks.map((repack) => repack.repacker))); - }, [repacks]); - const genres = useMemo(() => { return game.genres?.map((genre) => { const index = steamGenres["en"]?.findIndex( @@ -117,8 +109,8 @@ export function GameItem({ game }: GameItemProps) { {genres.join(", ")}
      - {uniqueRepackers.map((repacker) => ( - {repacker} + {game.downloadSources.map((sourceName) => ( + {sourceName} ))}
    diff --git a/src/renderer/src/pages/game-details/game-reviews.tsx b/src/renderer/src/pages/game-details/game-reviews.tsx index f8117f43..1ce44550 100644 --- a/src/renderer/src/pages/game-details/game-reviews.tsx +++ b/src/renderer/src/pages/game-details/game-reviews.tsx @@ -144,8 +144,6 @@ export function GameReviews({ } }, [objectId, userDetailsId, shop, game, onUserReviewedChange]); - console.log("reviews", reviews); - const loadReviews = useCallback( async (reset = false) => { if (!objectId) return; @@ -440,8 +438,6 @@ export function GameReviews({ }); }, [reviews]); - console.log("reviews", reviews); - return (
    {showReviewPrompt && diff --git a/src/renderer/src/pages/game-details/modals/repacks-modal.tsx b/src/renderer/src/pages/game-details/modals/repacks-modal.tsx index 7551a31e..306e8647 100644 --- a/src/renderer/src/pages/game-details/modals/repacks-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/repacks-modal.tsx @@ -54,7 +54,7 @@ export function RepacksModal({ {} ); - const { repacks, game } = useContext(gameDetailsContext); + const { game, repacks } = useContext(gameDetailsContext); const { t } = useTranslation("game_details"); @@ -88,6 +88,15 @@ export function RepacksModal({ }); }, [repacks, isFeatureEnabled, Feature]); + useEffect(() => { + const fetchDownloadSources = async () => { + const sources = await window.electron.getDownloadSources(); + setDownloadSources(sources); + }; + + fetchDownloadSources(); + }, []); + const sortedRepacks = useMemo(() => { return orderBy( repacks, @@ -103,23 +112,13 @@ export function RepacksModal({ ); }, [repacks, hashesInDebrid]); - useEffect(() => { - window.electron.getDownloadSourcesList().then((sources) => { - const uniqueRepackers = new Set(sortedRepacks.map((r) => r.repacker)); - const filteredSources = sources.filter( - (s) => s.name && uniqueRepackers.has(s.name) && !!s.fingerprint - ); - setDownloadSources(filteredSources); - }); - }, [sortedRepacks]); - useEffect(() => { const term = filterTerm.trim().toLowerCase(); const byTerm = sortedRepacks.filter((repack) => { if (!term) return true; const lowerTitle = repack.title.toLowerCase(); - const lowerRepacker = repack.repacker.toLowerCase(); + const lowerRepacker = repack.downloadSourceName.toLowerCase(); return lowerTitle.includes(term) || lowerRepacker.includes(term); }); @@ -130,7 +129,7 @@ export function RepacksModal({ (src) => src.fingerprint && selectedFingerprints.includes(src.fingerprint) && - src.name === repack.repacker + src.name === repack.downloadSourceName ); }); @@ -281,7 +280,7 @@ export function RepacksModal({ )}

    - {repack.fileSize} - {repack.repacker} -{" "} + {repack.fileSize} - {repack.downloadSourceName} -{" "} {repack.uploadDate ? formatDate(repack.uploadDate) : ""}

    diff --git a/src/renderer/src/pages/home/home.tsx b/src/renderer/src/pages/home/home.tsx index 40bf181d..b8f632a6 100644 --- a/src/renderer/src/pages/home/home.tsx +++ b/src/renderer/src/pages/home/home.tsx @@ -40,14 +40,20 @@ export default function Home() { setCurrentCatalogueCategory(category); setIsLoading(true); - const params = new URLSearchParams({ - take: "12", - skip: "0", - }); + const downloadSources = await window.electron.getDownloadSources(); + + const params = { + take: 12, + skip: 0, + downloadSourceIds: downloadSources.map((source) => source.id), + }; const catalogue = await window.electron.hydraApi.get( - `/catalogue/${category}?${params.toString()}`, - { needsAuth: false } + `/catalogue/${category}`, + { + params, + needsAuth: false, + } ); setCatalogue((prev) => ({ ...prev, [category]: catalogue })); diff --git a/src/renderer/src/pages/settings/add-download-source-modal.scss b/src/renderer/src/pages/settings/add-download-source-modal.scss index ea92ca71..d938f7f0 100644 --- a/src/renderer/src/pages/settings/add-download-source-modal.scss +++ b/src/renderer/src/pages/settings/add-download-source-modal.scss @@ -38,4 +38,11 @@ animation: spin 1s linear infinite; margin-right: calc(globals.$spacing-unit / 2); } + + &__actions { + display: flex; + justify-content: flex-end; + gap: globals.$spacing-unit; + margin-top: calc(globals.$spacing-unit * 2); + } } diff --git a/src/renderer/src/pages/settings/add-download-source-modal.tsx b/src/renderer/src/pages/settings/add-download-source-modal.tsx index c2b47513..2b45ed72 100644 --- a/src/renderer/src/pages/settings/add-download-source-modal.tsx +++ b/src/renderer/src/pages/settings/add-download-source-modal.tsx @@ -1,15 +1,12 @@ -import { useCallback, useContext, useEffect, useState } from "react"; +import { useContext, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { Button, Modal, TextField } from "@renderer/components"; import { settingsContext } from "@renderer/context"; import { useForm } from "react-hook-form"; -import { useAppDispatch } from "@renderer/hooks"; import * as yup from "yup"; import { yupResolver } from "@hookform/resolvers/yup"; -import type { DownloadSourceValidationResult } from "@types"; -import { setIsImportingSources } from "@renderer/features"; import { SyncIcon } from "@primer/octicons-react"; import "./add-download-source-modal.scss"; @@ -28,7 +25,6 @@ export function AddDownloadSourceModal({ onClose, onAddDownloadSource, }: Readonly) { - const [url, setUrl] = useState(""); const [isLoading, setIsLoading] = useState(false); const { t } = useTranslation("settings"); @@ -48,77 +44,38 @@ export function AddDownloadSourceModal({ resolver: yupResolver(schema), }); - const [validationResult, setValidationResult] = - useState(null); - const { sourceUrl } = useContext(settingsContext); - const dispatch = useAppDispatch(); + const onSubmit = async (values: FormValues) => { + setIsLoading(true); - const onSubmit = useCallback( - async (values: FormValues) => { - const exists = await window.electron.checkDownloadSourceExists( - values.url - ); + try { + await window.electron.addDownloadSource(values.url); - if (exists) { - setError("url", { - type: "server", - message: t("source_already_exists"), - }); - - return; - } - - const validationResult = await window.electron.validateDownloadSource( - values.url - ); - - setValidationResult(validationResult); - setUrl(values.url); - }, - [setError, t] - ); + onClose(); + onAddDownloadSource(); + } catch (error) { + console.error("Failed to add download source:", error); + setError("url", { + type: "server", + message: "Failed to add download source. Please try again.", + }); + } finally { + setIsLoading(false); + } + }; useEffect(() => { setValue("url", ""); clearErrors(); setIsLoading(false); - setValidationResult(null); if (sourceUrl) { setValue("url", sourceUrl); - handleSubmit(onSubmit)(); } - }, [visible, clearErrors, handleSubmit, onSubmit, setValue, sourceUrl]); - - const handleAddDownloadSource = async () => { - if (validationResult) { - setIsLoading(true); - dispatch(setIsImportingSources(true)); - - try { - // Single call that handles: import → API sync → fingerprint - await window.electron.addDownloadSource(url); - - // Close modal and update UI - onClose(); - onAddDownloadSource(); - } catch (error) { - console.error("Failed to add download source:", error); - setError("url", { - type: "server", - message: "Failed to import source. Please try again.", - }); - } finally { - setIsLoading(false); - dispatch(setIsImportingSources(false)); - } - } - }; + }, [visible, clearErrors, setValue, sourceUrl]); const handleClose = () => { - // Prevent closing while importing if (isLoading) return; onClose(); }; @@ -132,49 +89,32 @@ export function AddDownloadSourceModal({ clickOutsideToClose={!isLoading} >
    - + + +
    - } - /> - - {validationResult && ( -
    -
    -

    {validationResult?.name}

    - - {t("found_download_option", { - count: validationResult?.downloadCount, - countFormatted: - validationResult?.downloadCount.toLocaleString(), - })} - -
    - - + +
    - )} +
    ); diff --git a/src/renderer/src/pages/settings/settings-download-sources.tsx b/src/renderer/src/pages/settings/settings-download-sources.tsx index f873b321..85c0569a 100644 --- a/src/renderer/src/pages/settings/settings-download-sources.tsx +++ b/src/renderer/src/pages/settings/settings-download-sources.tsx @@ -16,7 +16,7 @@ import { TrashIcon, } from "@primer/octicons-react"; import { AddDownloadSourceModal } from "./add-download-source-modal"; -import { useAppDispatch, useRepacks, useToast } from "@renderer/hooks"; +import { useAppDispatch, useToast } from "@renderer/hooks"; import { DownloadSourceStatus } from "@shared"; import { settingsContext } from "@renderer/context"; import { useNavigate } from "react-router-dom"; @@ -35,7 +35,6 @@ export function SettingsDownloadSources() { useState(false); const [isRemovingDownloadSource, setIsRemovingDownloadSource] = useState(false); - const [isFetchingSources, setIsFetchingSources] = useState(true); const { sourceUrl, clearSourceUrl } = useContext(settingsContext); @@ -46,37 +45,29 @@ export function SettingsDownloadSources() { const navigate = useNavigate(); - const { updateRepacks } = useRepacks(); - - const getDownloadSources = async () => { - await window.electron - .getDownloadSourcesList() - .then((sources) => { - setDownloadSources(sources); - }) - .finally(() => { - setIsFetchingSources(false); - }); - }; - - useEffect(() => { - getDownloadSources(); - }, []); - useEffect(() => { if (sourceUrl) setShowAddDownloadSourceModal(true); }, [sourceUrl]); + useEffect(() => { + const fetchDownloadSources = async () => { + const sources = await window.electron.getDownloadSources(); + setDownloadSources(sources); + }; + + fetchDownloadSources(); + }, []); + const handleRemoveSource = async (downloadSource: DownloadSource) => { setIsRemovingDownloadSource(true); try { - await window.electron.deleteDownloadSource(downloadSource.id); - await window.electron.removeDownloadSource(downloadSource.url); - + await window.electron.removeDownloadSource(false, downloadSource.id); + const sources = await window.electron.getDownloadSources(); + setDownloadSources(sources as DownloadSource[]); showSuccessToast(t("removed_download_source")); - await getDownloadSources(); - updateRepacks(); + } catch (error) { + console.error("Failed to remove download source:", error); } finally { setIsRemovingDownloadSource(false); } @@ -86,53 +77,44 @@ export function SettingsDownloadSources() { setIsRemovingDownloadSource(true); try { - await window.electron.deleteAllDownloadSources(); - await window.electron.removeDownloadSource("", true); - - showSuccessToast(t("removed_download_sources")); - await getDownloadSources(); - setShowConfirmationDeleteAllSourcesModal(false); - updateRepacks(); + await window.electron.removeDownloadSource(true); + const sources = await window.electron.getDownloadSources(); + setDownloadSources(sources as DownloadSource[]); + showSuccessToast(t("removed_all_download_sources")); + } catch (error) { + console.error("Failed to remove all download sources:", error); } finally { setIsRemovingDownloadSource(false); + setShowConfirmationDeleteAllSourcesModal(false); } }; const handleAddDownloadSource = async () => { - // Refresh sources list and repacks after import completes - await getDownloadSources(); - - // Force repacks update to ensure UI reflects new data - await updateRepacks(); - - showSuccessToast(t("added_download_source")); + try { + const sources = await window.electron.getDownloadSources(); + setDownloadSources(sources as DownloadSource[]); + } catch (error) { + console.error("Failed to refresh download sources:", error); + } }; const syncDownloadSources = async () => { setIsSyncingDownloadSources(true); - try { - // Sync local sources (check for updates) - await window.electron.syncDownloadSources(); - - // Refresh sources and repacks AFTER sync completes - await getDownloadSources(); - await updateRepacks(); - - showSuccessToast(t("download_sources_synced")); - } catch (error) { - console.error("Error syncing download sources:", error); - // Still refresh the UI even if sync fails - await getDownloadSources(); - await updateRepacks(); + const sources = await window.electron.syncDownloadSources(); + setDownloadSources(sources); } finally { setIsSyncingDownloadSources(false); } }; const statusTitle = { - [DownloadSourceStatus.UpToDate]: t("download_source_up_to_date"), - [DownloadSourceStatus.Errored]: t("download_source_errored"), + [DownloadSourceStatus.PendingMatching]: t( + "download_source_pending_matching" + ), + [DownloadSourceStatus.Matched]: t("download_source_matched"), + [DownloadSourceStatus.Matching]: t("download_source_matching"), + [DownloadSourceStatus.Failed]: t("download_source_failed"), }; const handleModalClose = () => { @@ -180,8 +162,7 @@ export function SettingsDownloadSources() { disabled={ !downloadSources.length || isSyncingDownloadSources || - isRemovingDownloadSource || - isFetchingSources + isRemovingDownloadSource } onClick={syncDownloadSources} > @@ -197,8 +178,7 @@ export function SettingsDownloadSources() { disabled={ isRemovingDownloadSource || isSyncingDownloadSources || - !downloadSources.length || - isFetchingSources + !downloadSources.length } > @@ -209,11 +189,7 @@ export function SettingsDownloadSources() { type="button" theme="outline" onClick={() => setShowAddDownloadSourceModal(true)} - disabled={ - isSyncingDownloadSources || - isFetchingSources || - isRemovingDownloadSource - } + disabled={isSyncingDownloadSources || isRemovingDownloadSource} > {t("add_download_source")} diff --git a/src/renderer/src/store.ts b/src/renderer/src/store.ts index 264b1296..9903271c 100644 --- a/src/renderer/src/store.ts +++ b/src/renderer/src/store.ts @@ -8,8 +8,6 @@ import { userDetailsSlice, gameRunningSlice, subscriptionSlice, - repacksSlice, - downloadSourcesSlice, catalogueSearchSlice, } from "@renderer/features"; @@ -23,8 +21,6 @@ export const store = configureStore({ userDetails: userDetailsSlice.reducer, gameRunning: gameRunningSlice.reducer, subscription: subscriptionSlice.reducer, - repacks: repacksSlice.reducer, - downloadSources: downloadSourcesSlice.reducer, catalogueSearch: catalogueSearchSlice.reducer, }, }); diff --git a/src/shared/constants.ts b/src/shared/constants.ts index 851aec49..619dca65 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -11,8 +11,10 @@ export enum Downloader { } export enum DownloadSourceStatus { - UpToDate, - Errored, + PendingMatching = "PENDING_MATCHING", + Matched = "MATCHED", + Matching = "MATCHING", + Failed = "FAILED", } export enum CatalogueCategory { diff --git a/src/types/index.ts b/src/types/index.ts index 63b18645..092adaf8 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -16,29 +16,22 @@ export interface DiskUsage { } export interface GameRepack { - id: number; + id: string; title: string; - uris: string[]; - repacker: string; fileSize: string | null; - objectIds: string[]; - uploadDate: Date | string | null; - createdAt: Date; - updatedAt: Date; + uris: string[]; + uploadDate: string | null; + downloadSourceId: string; + downloadSourceName: string; } export interface DownloadSource { - id: number; + id: string; name: string; url: string; - repackCount: number; status: DownloadSourceStatus; - objectIds: string[]; downloadCount: number; fingerprint?: string; - etag: string | null; - createdAt: Date; - updatedAt: Date; } export interface ShopAssets { @@ -51,6 +44,7 @@ export interface ShopAssets { logoImageUrl: string; logoPosition: string | null; coverImageUrl: string | null; + downloadSources: string[]; } export type ShopDetails = SteamAppDetails & { @@ -231,12 +225,6 @@ export interface DownloadSourceDownload { fileSize: string; } -export interface DownloadSourceValidationResult { - name: string; - etag: string; - downloadCount: number; -} - export interface GameStats { downloadCount: number; playerCount: number; @@ -366,7 +354,7 @@ export type CatalogueSearchResult = { title: string; shop: GameShop; genres: string[]; -} & Pick; +} & Pick; export type LibraryGame = Game & Partial & { From e1ce5bc6cb8e6d2cbc14a7e7615c0b95619d9cf2 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Tue, 21 Oct 2025 04:20:11 +0100 Subject: [PATCH 104/197] feat: using api download sources --- src/main/events/library/add-custom-game-to-library.ts | 1 + src/main/services/library-sync/merge-with-remote-games.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/main/events/library/add-custom-game-to-library.ts b/src/main/events/library/add-custom-game-to-library.ts index f2f2dd40..6a90087e 100644 --- a/src/main/events/library/add-custom-game-to-library.ts +++ b/src/main/events/library/add-custom-game-to-library.ts @@ -37,6 +37,7 @@ const addCustomGameToLibrary = async ( logoImageUrl: logoImageUrl || "", logoPosition: null, coverImageUrl: iconUrl || "", + downloadSources: [], }; await gamesShopAssetsSublevel.put(gameKey, assets); diff --git a/src/main/services/library-sync/merge-with-remote-games.ts b/src/main/services/library-sync/merge-with-remote-games.ts index f7ea2744..c00e4961 100644 --- a/src/main/services/library-sync/merge-with-remote-games.ts +++ b/src/main/services/library-sync/merge-with-remote-games.ts @@ -72,6 +72,7 @@ export const mergeWithRemoteGames = async () => { logoImageUrl: game.logoImageUrl, iconUrl: game.iconUrl, logoPosition: game.logoPosition, + downloadSources: game.downloadSources, }); } }) From 8a40c678f7c2db1d4558cf1cca018359a592e3d7 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Tue, 21 Oct 2025 04:21:56 +0100 Subject: [PATCH 105/197] feat: using api download sources --- src/renderer/src/pages/game-details/game-details.tsx | 1 - src/types/index.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/renderer/src/pages/game-details/game-details.tsx b/src/renderer/src/pages/game-details/game-details.tsx index f0778494..04b78aa4 100644 --- a/src/renderer/src/pages/game-details/game-details.tsx +++ b/src/renderer/src/pages/game-details/game-details.tsx @@ -102,7 +102,6 @@ export default function GameDetails() { automaticallyExtract: boolean ) => { const response = await startDownload({ - repackId: repack.id, objectId: objectId!, title: gameTitle, downloader, diff --git a/src/types/index.ts b/src/types/index.ts index 092adaf8..7d11171b 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -107,7 +107,6 @@ export type AppUpdaterEvent = /* Events */ export interface StartGameDownloadPayload { - repackId: number; objectId: string; title: string; shop: GameShop; From 945173f48e7f4017a409705325ca7fdffd3133c1 Mon Sep 17 00:00:00 2001 From: spectre365 Date: Wed, 22 Oct 2025 16:32:39 -0300 Subject: [PATCH 106/197] Interface modification for the default game page --- .env.example | 5 --- .../game-details/game-details-content.tsx | 12 +++--- .../src/pages/game-details/game-details.scss | 34 ++++++++++++++--- .../pages/game-details/hero/hero-panel.scss | 8 +++- .../pages/game-details/hero/hero-panel.tsx | 38 ++++++++++--------- 5 files changed, 63 insertions(+), 34 deletions(-) delete mode 100644 .env.example diff --git a/.env.example b/.env.example deleted file mode 100644 index 3f914eb3..00000000 --- a/.env.example +++ /dev/null @@ -1,5 +0,0 @@ -MAIN_VITE_API_URL= -MAIN_VITE_AUTH_URL= -MAIN_VITE_WS_URL= -RENDERER_VITE_REAL_DEBRID_REFERRAL_ID= -RENDERER_VITE_TORBOX_REFERRAL_CODE= diff --git a/src/renderer/src/pages/game-details/game-details-content.tsx b/src/renderer/src/pages/game-details/game-details-content.tsx index edf314c7..e019d984 100644 --- a/src/renderer/src/pages/game-details/game-details-content.tsx +++ b/src/renderer/src/pages/game-details/game-details-content.tsx @@ -153,11 +153,13 @@ export function GameDetailsContent() { >
    - {game?.title} +
    + {game?.title} +
    -
    {getInfo()}
    -
    - -
    +
    +
    +
    {getInfo()}
    +
    + +
    - {showProgressBar && ( - - )} + {showProgressBar && ( + + )} +
    ); } From 864fd282f002e0fda04f5e995f24799730a7273f Mon Sep 17 00:00:00 2001 From: spectre365 Date: Wed, 22 Oct 2025 16:42:21 -0300 Subject: [PATCH 107/197] Interface modification for the default game page --- .../src/pages/game-details/game-details.scss | 8 +- .../pages/game-details/hero/hero-panel.scss | 124 +++++++++--------- 2 files changed, 68 insertions(+), 64 deletions(-) diff --git a/src/renderer/src/pages/game-details/game-details.scss b/src/renderer/src/pages/game-details/game-details.scss index 38319b0a..14b583ee 100644 --- a/src/renderer/src/pages/game-details/game-details.scss +++ b/src/renderer/src/pages/game-details/game-details.scss @@ -584,13 +584,17 @@ $hero-height: 300px; z-index: 0; &::after { - content: ''; + content: ""; position: absolute; top: 0; left: 0; right: 0; bottom: 0; - background: linear-gradient(0deg, rgba(0, 0, 0, 0.3) 60%, transparent 100%); + background: linear-gradient( + 0deg, + rgba(0, 0, 0, 0.3) 60%, + transparent 100% + ); z-index: 1; pointer-events: none; border-radius: inherit; diff --git a/src/renderer/src/pages/game-details/hero/hero-panel.scss b/src/renderer/src/pages/game-details/hero/hero-panel.scss index 8a3611ec..9fbdf453 100644 --- a/src/renderer/src/pages/game-details/hero/hero-panel.scss +++ b/src/renderer/src/pages/game-details/hero/hero-panel.scss @@ -4,75 +4,75 @@ padding: 0px 12px 12px; margin: 0; -.hero-panel { - width: 100%; - height: 72px; - min-height: 72px; - padding: calc(globals.$spacing-unit * 2) calc(globals.$spacing-unit * 3); - background: rgba(0, 0, 0, 0.6); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); - border: solid 1px rgba(255, 255, 255, 0.15); - display: flex; - align-items: center; - justify-content: space-between; - transition: all ease 0.2s; - border-bottom: solid 1px globals.$border-color; - overflow: hidden; - top: 0; - z-index: 2; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); - border-radius: 8px; - - &--stuck { - background: rgba(0, 0, 0, 0.7); - backdrop-filter: blur(12px); - -webkit-backdrop-filter: blur(12px); - box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, 0.8); - } - - &__content { - display: flex; - flex-direction: column; - gap: globals.$spacing-unit; - } - - &__actions { - display: flex; - gap: globals.$spacing-unit; - } - - &__download-details { - gap: globals.$spacing-unit; - display: flex; - color: globals.$body-color; - align-items: center; - } - - &__downloads-link { - color: globals.$body-color; - text-decoration: underline; - } - - &__progress-bar { - position: absolute; - bottom: 0; - left: 0; + .hero-panel { width: 100%; - height: 3px; + height: 72px; + min-height: 72px; + padding: calc(globals.$spacing-unit * 2) calc(globals.$spacing-unit * 3); + background: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + border: solid 1px rgba(255, 255, 255, 0.15); + display: flex; + align-items: center; + justify-content: space-between; transition: all ease 0.2s; + border-bottom: solid 1px globals.$border-color; + overflow: hidden; + top: 0; + z-index: 2; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + border-radius: 8px; - &::-webkit-progress-bar { - background-color: transparent; + &--stuck { + background: rgba(0, 0, 0, 0.7); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, 0.8); } - &::-webkit-progress-value { - background-color: globals.$muted-color; + &__content { + display: flex; + flex-direction: column; + gap: globals.$spacing-unit; } - &--disabled { - opacity: globals.$disabled-opacity; + &__actions { + display: flex; + gap: globals.$spacing-unit; + } + + &__download-details { + gap: globals.$spacing-unit; + display: flex; + color: globals.$body-color; + align-items: center; + } + + &__downloads-link { + color: globals.$body-color; + text-decoration: underline; + } + + &__progress-bar { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 3px; + transition: all ease 0.2s; + + &::-webkit-progress-bar { + background-color: transparent; + } + + &::-webkit-progress-value { + background-color: globals.$muted-color; + } + + &--disabled { + opacity: globals.$disabled-opacity; + } } } } -} \ No newline at end of file From 7435bff64f1da9f94430509bd6da736c3999b1ad Mon Sep 17 00:00:00 2001 From: spectre365 Date: Wed, 22 Oct 2025 17:17:37 -0300 Subject: [PATCH 108/197] Interface modification for the default game page --- .env.example | 8 ++ .../pages/game-details/hero/hero-panel.scss | 124 +++++++++--------- .../pages/game-details/hero/hero-panel.tsx | 2 +- 3 files changed, 71 insertions(+), 63 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..c71b5e68 --- /dev/null +++ b/.env.example @@ -0,0 +1,8 @@ +MAIN_VITE_API_URL=https://api-staging.hydralauncher.gg +MAIN_VITE_AUTH_URL=https://auth-staging.hydralauncher.gg +MAIN_VITE_WS_URL=wss://ws-staging.hydralauncher.gg +MAIN_VITE_EXTERNAL_RESOURCES_URL=https://assets.hydralauncher.gg +RENDERER_VITE_EXTERNAL_RESOURCES_URL=https://assets.hydralauncher.gg +MAIN_VITE_CHECKOUT_URL=https://checkout-staging.hydralauncher.gg +RENDERER_VITE_REAL_DEBRID_REFERRAL_ID= +RENDERER_VITE_TORBOX_REFERRAL_CODE= diff --git a/src/renderer/src/pages/game-details/hero/hero-panel.scss b/src/renderer/src/pages/game-details/hero/hero-panel.scss index 9fbdf453..fa797988 100644 --- a/src/renderer/src/pages/game-details/hero/hero-panel.scss +++ b/src/renderer/src/pages/game-details/hero/hero-panel.scss @@ -1,78 +1,78 @@ @use "../../../scss/globals.scss"; -.hero-panel-wrapper { +.hero-panel { + width: 100%; + height: 72px; + min-height: 72px; + padding: calc(globals.$spacing-unit * 2) calc(globals.$spacing-unit * 3); + background: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + border: solid 1px rgba(255, 255, 255, 0.15); + display: flex; + align-items: center; + justify-content: space-between; + transition: all ease 0.2s; + border-bottom: solid 1px globals.$border-color; + overflow: hidden; + top: 0; + z-index: 2; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + border-radius: 8px; + + &__container { padding: 0px 12px 12px; margin: 0; + } - .hero-panel { - width: 100%; - height: 72px; - min-height: 72px; - padding: calc(globals.$spacing-unit * 2) calc(globals.$spacing-unit * 3); - background: rgba(0, 0, 0, 0.6); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); - border: solid 1px rgba(255, 255, 255, 0.15); + &--stuck { + background: rgba(0, 0, 0, 0.7); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, 0.8); + } + + &__content { display: flex; + flex-direction: column; + gap: globals.$spacing-unit; + } + + &__actions { + display: flex; + gap: globals.$spacing-unit; + } + + &__download-details { + gap: globals.$spacing-unit; + display: flex; + color: globals.$body-color; align-items: center; - justify-content: space-between; + } + + &__downloads-link { + color: globals.$body-color; + text-decoration: underline; + } + + &__progress-bar { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 3px; transition: all ease 0.2s; - border-bottom: solid 1px globals.$border-color; - overflow: hidden; - top: 0; - z-index: 2; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); - border-radius: 8px; - &--stuck { - background: rgba(0, 0, 0, 0.7); - backdrop-filter: blur(12px); - -webkit-backdrop-filter: blur(12px); - box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, 0.8); + &::-webkit-progress-bar { + background-color: transparent; } - &__content { - display: flex; - flex-direction: column; - gap: globals.$spacing-unit; + &::-webkit-progress-value { + background-color: globals.$muted-color; } - &__actions { - display: flex; - gap: globals.$spacing-unit; - } - - &__download-details { - gap: globals.$spacing-unit; - display: flex; - color: globals.$body-color; - align-items: center; - } - - &__downloads-link { - color: globals.$body-color; - text-decoration: underline; - } - - &__progress-bar { - position: absolute; - bottom: 0; - left: 0; - width: 100%; - height: 3px; - transition: all ease 0.2s; - - &::-webkit-progress-bar { - background-color: transparent; - } - - &::-webkit-progress-value { - background-color: globals.$muted-color; - } - - &--disabled { - opacity: globals.$disabled-opacity; - } + &--disabled { + opacity: globals.$disabled-opacity; } } } diff --git a/src/renderer/src/pages/game-details/hero/hero-panel.tsx b/src/renderer/src/pages/game-details/hero/hero-panel.tsx index db0164de..a1e6fed5 100644 --- a/src/renderer/src/pages/game-details/hero/hero-panel.tsx +++ b/src/renderer/src/pages/game-details/hero/hero-panel.tsx @@ -50,7 +50,7 @@ export function HeroPanel() { game?.download?.status === "paused"; return ( -
    +
    {getInfo()}
    From ca35da37ededae768923576e4b4fef78aadd818b Mon Sep 17 00:00:00 2001 From: spectre365 Date: Wed, 22 Oct 2025 17:20:48 -0300 Subject: [PATCH 109/197] Interface modification for the default game page --- src/renderer/src/pages/game-details/hero/hero-panel.scss | 8 ++++---- src/renderer/src/pages/game-details/hero/hero-panel.tsx | 4 +++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/renderer/src/pages/game-details/hero/hero-panel.scss b/src/renderer/src/pages/game-details/hero/hero-panel.scss index fa797988..a0d32e9e 100644 --- a/src/renderer/src/pages/game-details/hero/hero-panel.scss +++ b/src/renderer/src/pages/game-details/hero/hero-panel.scss @@ -18,11 +18,11 @@ top: 0; z-index: 2; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); - border-radius: 8px; - + border-radius: 8px; + &__container { - padding: 0px 12px 12px; - margin: 0; + padding: 0px 12px 12px; + margin: 0; } &--stuck { diff --git a/src/renderer/src/pages/game-details/hero/hero-panel.tsx b/src/renderer/src/pages/game-details/hero/hero-panel.tsx index a1e6fed5..799f2c36 100644 --- a/src/renderer/src/pages/game-details/hero/hero-panel.tsx +++ b/src/renderer/src/pages/game-details/hero/hero-panel.tsx @@ -61,7 +61,9 @@ export function HeroPanel() { Date: Wed, 22 Oct 2025 17:51:31 -0300 Subject: [PATCH 110/197] Interface modification for the default game page --- .env.example | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.env.example b/.env.example index c71b5e68..e69de29b 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +0,0 @@ -MAIN_VITE_API_URL=https://api-staging.hydralauncher.gg -MAIN_VITE_AUTH_URL=https://auth-staging.hydralauncher.gg -MAIN_VITE_WS_URL=wss://ws-staging.hydralauncher.gg -MAIN_VITE_EXTERNAL_RESOURCES_URL=https://assets.hydralauncher.gg -RENDERER_VITE_EXTERNAL_RESOURCES_URL=https://assets.hydralauncher.gg -MAIN_VITE_CHECKOUT_URL=https://checkout-staging.hydralauncher.gg -RENDERER_VITE_REAL_DEBRID_REFERRAL_ID= -RENDERER_VITE_TORBOX_REFERRAL_CODE= From 99e34ce0601c67aac63785e2cca7bc24caddc601 Mon Sep 17 00:00:00 2001 From: spectre365 Date: Wed, 22 Oct 2025 17:56:20 -0300 Subject: [PATCH 111/197] Interface modification for the default game page --- .env.example | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.env.example b/.env.example index e69de29b..19c67fe5 100644 --- a/.env.example +++ b/.env.example @@ -0,0 +1,5 @@ +MAIN_VITE_API_URL= +MAIN_VITE_AUTH_URL= +MAIN_VITE_WS_URL= +RENDERER_VITE_REAL_DEBRID_REFERRAL_ID= +RENDERER_VITE_TORBOX_REFERRAL_CODE= \ No newline at end of file From a7b5bdb3b47013d329d596970fc8fcb20dab3af1 Mon Sep 17 00:00:00 2001 From: spectre365 Date: Wed, 22 Oct 2025 18:03:24 -0300 Subject: [PATCH 112/197] Interface modification for the default game page --- .env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 19c67fe5..3f914eb3 100644 --- a/.env.example +++ b/.env.example @@ -2,4 +2,4 @@ MAIN_VITE_API_URL= MAIN_VITE_AUTH_URL= MAIN_VITE_WS_URL= RENDERER_VITE_REAL_DEBRID_REFERRAL_ID= -RENDERER_VITE_TORBOX_REFERRAL_CODE= \ No newline at end of file +RENDERER_VITE_TORBOX_REFERRAL_CODE= From 00e597c910db941cc862f3628749bc15eb5ebf26 Mon Sep 17 00:00:00 2001 From: GrimmDevel Date: Thu, 23 Oct 2025 00:51:00 +0300 Subject: [PATCH 113/197] Added Latvian translation --- .env.example | 5 - src/locales/index.ts | 2 + src/locales/lv/translation.json | 708 ++++++++++++++++++++++++++++++++ 3 files changed, 710 insertions(+), 5 deletions(-) delete mode 100644 .env.example create mode 100644 src/locales/lv/translation.json diff --git a/.env.example b/.env.example deleted file mode 100644 index 3f914eb3..00000000 --- a/.env.example +++ /dev/null @@ -1,5 +0,0 @@ -MAIN_VITE_API_URL= -MAIN_VITE_AUTH_URL= -MAIN_VITE_WS_URL= -RENDERER_VITE_REAL_DEBRID_REFERRAL_ID= -RENDERER_VITE_TORBOX_REFERRAL_CODE= diff --git a/src/locales/index.ts b/src/locales/index.ts index a44480e6..d58ee59a 100644 --- a/src/locales/index.ts +++ b/src/locales/index.ts @@ -28,6 +28,7 @@ import bg from "./bg/translation.json"; import uz from "./uz/translation.json"; import fi from "./fi/translation.json"; import sv from "./sv/translation.json"; +import lv from "./lv/translation.json"; export default { "pt-BR": ptBR, @@ -60,4 +61,5 @@ export default { et, uz, sv, + lv, }; diff --git a/src/locales/lv/translation.json b/src/locales/lv/translation.json new file mode 100644 index 00000000..a33a1986 --- /dev/null +++ b/src/locales/lv/translation.json @@ -0,0 +1,708 @@ +{ + "language_name": "Latviešu", + "app": { + "successfully_signed_in": "Veiksmīga pieteikšanās" + }, + "home": { + "surprise_me": "Pārsteidz mani", + "no_results": "Nekas nav atrasts", + "start_typing": "Sākt rakstīt...", + "hot": "Šobrīd populārs", + "weekly": "📅 Nedēļas labākās spēles", + "achievements": "🏆 Spēles ar sasniegumiem" + }, + "sidebar": { + "catalogue": "Katalogs", + "downloads": "Lejupielādes", + "settings": "Iestatījumi", + "my_library": "Bibliotēka", + "downloading_metadata": "{{title}} (Lejupielādē metadatus…)", + "paused": "{{title}} (Apturēts)", + "downloading": "{{title}} ({{percentage}} - Lejupielādē…)", + "filter": "Meklēt", + "home": "Sākums", + "queued": "{{title}} (Rindā)", + "game_has_no_executable": "Spēles palaišanas fails nav izvēlēts", + "sign_in": "Pieteikties", + "friends": "Draugi", + "need_help": "Nepieciešama palīdzība?", + "favorites": "Izlase", + "playable_button_title": "Rādīt tikai instalētās spēles.", + "add_custom_game_tooltip": "Pievienot pielāgotu spēli", + "show_playable_only_tooltip": "Rādīt tikai spēlēšanai pieejamās", + "custom_game_modal": "Pievienot pielāgotu spēli", + "custom_game_modal_description": "Pievienojiet pielāgotu spēli bibliotēkai, izvēloties izpildāmo failu", + "custom_game_modal_executable_path": "Ceļš uz izpildāmo failu", + "custom_game_modal_select_executable": "Izvēlieties izpildāmo failu", + "custom_game_modal_title": "Spēles nosaukums", + "custom_game_modal_enter_title": "Ievadiet spēles nosaukumu", + "custom_game_modal_browse": "Pārlūkot", + "custom_game_modal_cancel": "Atcelt", + "custom_game_modal_add": "Pievienot spēli", + "custom_game_modal_adding": "Pievieno spēli...", + "custom_game_modal_success": "Pielāgota spēle veiksmīgi pievienota", + "custom_game_modal_failed": "Neizdevās pievienot pielāgotu spēli", + "custom_game_modal_executable": "Izpildāmais fails", + "edit_game_modal": "Konfigurēt resursus", + "edit_game_modal_description": "Konfigurējiet spēles resursus un detaļas", + "edit_game_modal_title": "Nosaukums", + "edit_game_modal_enter_title": "Ievadiet nosaukumu", + "edit_game_modal_image": "Attēls", + "edit_game_modal_select_image": "Izvēlieties attēlu", + "edit_game_modal_browse": "Pārlūkot", + "edit_game_modal_image_preview": "Attēla priekšskatījums", + "edit_game_modal_icon": "Ikona", + "edit_game_modal_select_icon": "Izvēlieties ikonu", + "edit_game_modal_icon_preview": "Ikona priekšskatījums", + "edit_game_modal_logo": "Logotips", + "edit_game_modal_select_logo": "Izvēlieties logotipu", + "edit_game_modal_logo_preview": "Logotipa priekšskatījums", + "edit_game_modal_hero": "Vāka attēls", + "edit_game_modal_select_hero": "Izvēlieties spēles vāka attēlu", + "edit_game_modal_hero_preview": "Spēles vāka attēla priekšskatījums", + "edit_game_modal_cancel": "Atcelt", + "edit_game_modal_update": "Atjaunināt", + "edit_game_modal_updating": "Atjaunina...", + "edit_game_modal_fill_required": "Lūdzu, aizpildiet visus obligātos laukus", + "edit_game_modal_success": "Resursi veiksmīgi atjaunināti", + "edit_game_modal_failed": "Neizdevās atjaunināt resursus", + "edit_game_modal_image_filter": "Attēls", + "edit_game_modal_icon_resolution": "Ieteicamā izšķirtspēja: 256x256px", + "edit_game_modal_logo_resolution": "Ieteicamā izšķirtspēja: 640x360px", + "edit_game_modal_hero_resolution": "Ieteicamā izšķirtspēja: 1920x620px", + "edit_game_modal_assets": "Resursi", + "edit_game_modal_drop_icon_image_here": "Ievelciet ikonas attēlu šeit", + "edit_game_modal_drop_logo_image_here": "Ievelciet logotipa attēlu šeit", + "edit_game_modal_drop_hero_image_here": "Ievelciet vāka attēlu šeit", + "edit_game_modal_drop_to_replace_icon": "Ievelciet, lai aizstātu ikonu", + "edit_game_modal_drop_to_replace_logo": "Ievelciet, lai aizstātu logotipu", + "edit_game_modal_drop_to_replace_hero": "Ievelciet, lai aizstātu vāku", + "install_decky_plugin": "Instalēt Decky spraudni", + "update_decky_plugin": "Atjaunināt Decky spraudni", + "decky_plugin_installed_version": "Decky spraudnis (v{{version}})", + "install_decky_plugin_title": "Instalēt Hydra Decky spraudni", + "install_decky_plugin_message": "Tas lejupielādēs un instalēs Hydra spraudni Decky Loader. Var būt nepieciešamas paaugstinātas atļaujas. Turpināt?", + "update_decky_plugin_title": "Atjaunināt Hydra Decky spraudni", + "update_decky_plugin_message": "Ir pieejama jauna Hydra Decky spraudņa versija. Vai vēlaties to atjaunināt tagad?", + "decky_plugin_installed": "Decky spraudnis v{{version}} veiksmīgi instalēts", + "decky_plugin_installation_failed": "Neizdevās instalēt Decky spraudni: {{error}}", + "decky_plugin_installation_error": "Decky spraudņa instalēšanas kļūda: {{error}}", + "confirm": "Apstiprināt", + "cancel": "Atcelt" + }, + "header": { + "search": "Meklēt", + "home": "Sākums", + "catalogue": "Katalogs", + "downloads": "Lejupielādes", + "search_results": "Meklēšanas rezultāti", + "settings": "Iestatījumi", + "version_available_install": "Pieejama versija {{version}}. Noklikšķiniet šeit, lai instalētu.", + "version_available_download": "Pieejama versija {{version}}. Noklikšķiniet šeit, lai lejupielādētu." + }, + "bottom_panel": { + "no_downloads_in_progress": "Nav aktīvu lejupielāžu", + "downloading_metadata": "Lejupielādē metadatus {{title}}…", + "downloading": "Lejupielādē {{title}}… ({{percentage}} pabeigts) - Beigsies {{eta}} - {{speed}}", + "calculating_eta": "Lejupielādē {{title}}… ({{percentage}} pabeigts) - Aprēķina atlikušo laiku…", + "checking_files": "Pārbauda failus {{title}}… ({{percentage}} pabeigts)", + "installing_common_redist": "{{log}}…", + "installation_complete": "Instalēšana pabeigta", + "installation_complete_message": "Bibliotēkas veiksmīgi instalētas" + }, + "catalogue": { + "search": "Filtrs…", + "developers": "Izstrādātāji", + "genres": "Žanri", + "tags": "Atzīmes", + "publishers": "Izdevēji", + "download_sources": "Lejupielādes avoti", + "result_count": "{{resultCount}} rezultāti", + "filter_count": "{{filterCount}} pieejami", + "clear_filters": "Notīrīt {{filterCount}} atlasītos" + }, + "game_details": { + "open_download_options": "Atvērt avotus", + "download_options_zero": "Nav avotu", + "download_options_one": "{{count}} avots", + "download_options_other": "{{count}} avoti", + "updated_at": "Atjaunināts {{updated_at}}", + "install": "Instalēt", + "resume": "Atsākt", + "pause": "Apturēt", + "cancel": "Atcelt", + "remove": "Dzēst", + "space_left_on_disk": "{{space}} brīvs diskā", + "eta": "Beigsies {{eta}}", + "calculating_eta": "Aprēķina atlikušo laiku…", + "downloading_metadata": "Lejupielādē metadatus…", + "filter": "Meklēt repakus", + "requirements": "Sistēmas prasības", + "minimum": "Minimālās", + "recommended": "Ieteicamās", + "paused": "Apturēts", + "release_date": "Izdots {{date}}", + "publisher": "Izdevējs {{publisher}}", + "hours": "stundas", + "minutes": "minūtes", + "amount_hours": "{{amount}} stundas", + "amount_minutes": "{{amount}} minūtes", + "accuracy": "precizitāte {{accuracy}}%", + "add_to_library": "Pievienot bibliotēkai", + "already_in_library": "Jau bibliotēkā", + "remove_from_library": "Dzēst no bibliotēkas", + "no_downloads": "Nav pieejamu avotu", + "play_time": "Spēlēts {{amount}}", + "last_time_played": "Pēdējo reizi spēlēts {{period}}", + "not_played_yet": "Jūs vēl neesat spēlējis {{title}}", + "next_suggestion": "Nākamais ieteikums", + "play": "Spēlēt", + "deleting": "Dzēš instalētāju…", + "close": "Aizvērt", + "playing_now": "Palaists", + "change": "Mainīt", + "repacks_modal_description": "Izvēlieties repaku lejupielādei", + "select_folder_hint": "Lai mainītu noklusējuma lejupielāžu mapi, atveriet <0>Iestatījumus", + "download_now": "Lejupielādēt tagad", + "no_shop_details": "Neizdevās iegūt aprakstu", + "download_options": "Avoti", + "download_path": "Ceļš lejupielādēm", + "previous_screenshot": "Iepriekšējais ekrānuzņēmums", + "next_screenshot": "Nākamais ekrānuzņēmums", + "screenshot": "Ekrānuzņēmums {{number}}", + "open_screenshot": "Atvērt ekrānuzņēmumu {{number}}", + "download_settings": "Lejupielādes parametri", + "downloader": "Lejupielādētājs", + "select_executable": "Izvēlēties", + "no_executable_selected": "Fails nav izvēlēts", + "open_folder": "Atvērt mapi", + "open_download_location": "Pārlūkot lejupielādes mapi", + "create_shortcut": "Izveidot īsceļu uz darbvirsmas", + "create_shortcut_simple": "Izveidot īsceļu", + "clear": "Notīrīt", + "remove_files": "Dzēst failus", + "remove_from_library_title": "Vai esat pārliecināts?", + "remove_from_library_description": "{{game}} tiks dzēsta no jūsu bibliotēkas.", + "options": "Iestatījumi", + "properties": "Īpašības", + "executable_section_title": "Fails", + "executable_section_description": "Ceļš uz failu, kas tiks palaists, nospiežot \"Spēlēt\"", + "downloads_section_title": "Lejupielādes", + "downloads_section_description": "Pārbaudīt atjauninājumu vai citu spēles versiju pieejamību", + "danger_zone_section_title": "Bīstamā zona", + "danger_zone_section_description": "Jūs varat dzēst šo spēli no savas bibliotēkas vai failus, kas lejupielādēti no Hydra", + "download_in_progress": "Notiek lejupielāde", + "download_paused": "Lejupielāde apturēta", + "last_downloaded_option": "Pēdējais lejupielādes variants", + "create_steam_shortcut": "Izveidot Steam īsceļu", + "create_shortcut_success": "Īsceļš izveidots", + "you_might_need_to_restart_steam": "Iespējams, jums būs jāpārstartē Steam, lai redzētu izmaiņas", + "create_shortcut_error": "Neizdevās izveidot īsceļu", + "add_to_favorites": "Pievienot izlasei", + "remove_from_favorites": "Dzēst no izlases", + "failed_update_favorites": "Neizdevās atjaunināt izlasi", + "game_removed_from_library": "Spēle dzēsta no bibliotēkas", + "failed_remove_from_library": "Neizdevās dzēst no bibliotēkas", + "files_removed_success": "Faili veiksmīgi dzēsti", + "failed_remove_files": "Neizdevās dzēst failus", + "nsfw_content_title": "Šajā spēlē ir nepiemērots saturs", + "nsfw_content_description": "{{title}} satur saturu, kas var nebūt piemērots visiem vecumiem. \nVai esat pārliecināts, ka vēlaties turpināt?", + "allow_nsfw_content": "Turpināt", + "refuse_nsfw_content": "Atpakaļ", + "stats": "Statistika", + "download_count": "Lejupielādes", + "player_count": "Aktīvie spēlētāji", + "download_error": "Šis lejupielādes variants nav pieejams", + "download": "Lejupielādēt", + "executable_path_in_use": "Izpildāmais fails jau tiek izmantots \"{{game}}\"", + "warning": "Uzmanību:", + "hydra_needs_to_remain_open": "Lai veiktu šo lejupielādi, Hydra jāpaliek atvērtai līdz beigām. Ja Hydra aizvērsies pirms pabeigšanas, jūs zaudēsiet progresu.", + "achievements": "Sasniegumi", + "achievements_count": "Sasniegumi {{unlockedCount}}/{{achievementsCount}}", + "show_more": "Rādīt vairāk", + "show_less": "Rādīt mazāk", + "reviews": "Atsauksmes", + "leave_a_review": "Atstāt atsauksmi", + "write_review_placeholder": "Dalieties savās domās par šo spēli...", + "sort_newest": "Vispirms jaunākās", + "no_reviews_yet": "Pagaidām nav atsauksmju", + "be_first_to_review": "Esiet pirmais, kurš dalīsies savās domās par šo spēli!", + "sort_oldest": "Vispirms vecākās", + "sort_highest_score": "Augstākais vērtējums", + "sort_lowest_score": "Zemākais vērtējums", + "sort_most_voted": "Vispopulārākās", + "rating": "Vērtējums", + "rating_stats": "Vērtējums", + "rating_very_negative": "Ļoti negatīvs", + "rating_negative": "Negatīvs", + "rating_neutral": "Neitrāls", + "rating_positive": "Pozitīvs", + "rating_very_positive": "Ļoti pozitīvs", + "submit_review": "Iesniegt", + "submitting": "Iesniegšana...", + "review_submitted_successfully": "Atsauksme veiksmīgi iesniegta!", + "review_submission_failed": "Neizdevās iesniegt atsauksmi. Lūdzu, mēģiniet vēlreiz.", + "review_cannot_be_empty": "Atsauksmes teksta lauks nevar būt tukšs.", + "review_deleted_successfully": "Atsauksme veiksmīgi dzēsta.", + "review_deletion_failed": "Neizdevās dzēst atsauksmi. Lūdzu, mēģiniet vēlreiz.", + "loading_reviews": "Ielādē atsauksmes...", + "loading_more_reviews": "Ielādē papildu atsauksmes...", + "load_more_reviews": "Ielādēt vairāk atsauksmju", + "you_seemed_to_enjoy_this_game": "Šķiet, jums patika šī spēle", + "would_you_recommend_this_game": "Vai vēlaties atstāt atsauksmi par šo spēli?", + "yes": "Jā", + "maybe_later": "Varbūt vēlāk", + "rating_count": "Vērtējums", + "delete_review": "Dzēst atsauksmi", + "remove_review": "Dzēst atsauksmi", + "delete_review_modal_title": "Vai esat pārliecināts, ka vēlaties dzēst savu atsauksmi?", + "delete_review_modal_description": "Šo darbību nevar atsaukt.", + "delete_review_modal_delete_button": "Dzēst", + "delete_review_modal_cancel_button": "Atcelt", + "show_original": "Rādīt oriģinālu", + "show_translation": "Rādīt tulkojumu", + "show_original_translated_from": "Rādīt oriģinālu (tulkot no {{language}})", + "hide_original": "Slēpt oriģinālu", + "cloud_save": "Mākoņglabāšana", + "cloud_save_description": "Glabājiet savu progresu mākonī un turpiniet spēlēt jebkurā ierīcē", + "backups": "Rezerves kopijas", + "install_backup": "Instalēt", + "delete_backup": "Dzēst", + "create_backup": "Izveidot jaunu rezerves kopiju", + "last_backup_date": "Pēdējā rezerves kopija no {{date}}", + "no_backup_preview": "Šim nosaukumam saglabājumi nav atrasti", + "restoring_backup": "Atjauno rezerves kopiju ({{progress}} pabeigts)…", + "uploading_backup": "Augšupielādē rezerves kopiju…", + "no_backups": "Jūs vēl neesat izveidojis rezerves kopijas šai spēlei", + "backup_uploaded": "Rezerves kopija augšupielādēta", + "backup_failed": "Rezerves kopēšanas kļūda", + "backup_deleted": "Rezerves kopija dzēsta", + "backup_restored": "Rezerves kopija atjaunota", + "see_all_achievements": "Skatīt visus sasniegumus", + "sign_in_to_see_achievements": "Piesakieties, lai redzētu sasniegumus", + "mapping_method_automatic": "Automātiska", + "mapping_method_manual": "Manuāla", + "mapping_method_label": "Kartēšanas metode", + "files_automatically_mapped": "Faili automātiski kartēti", + "no_backups_created": "Šai spēlei nav izveidotas rezerves kopijas", + "manage_files": "Failu pārvaldība", + "loading_save_preview": "Meklē saglabājumus…", + "wine_prefix": "Wine prefikss", + "wine_prefix_description": "Wine prefikss, ko izmanto šīs spēles palaišanai", + "launch_options": "Palaišanas parametri", + "launch_options_description": "Pieredzējuši lietotāji var veikt izmaiņas palaišanas parametros", + "launch_options_placeholder": "Parametrs nav norādīts", + "no_download_option_info": "Informācija nav pieejama", + "backup_deletion_failed": "Neizdevās dzēst rezerves kopiju", + "max_number_of_artifacts_reached": "Sasniegts maksimālais rezerves kopiju skaits šai spēlei", + "achievements_not_sync": "Jūsu sasniegumi nav sinhronizēti", + "manage_files_description": "Pārvaldiet failus, kas tiks saglabāti un atjaunoti", + "select_folder": "Izvēlēties mapi", + "backup_from": "Rezerves kopija no {{date}}", + "automatic_backup_from": "Automātiska rezerves kopija no {{date}}", + "enable_automatic_cloud_sync": "Iespējot automātisku sinhronizāciju mākonī", + "custom_backup_location_set": "Iestatīta pielāgota rezerves kopēšanas vieta", + "no_directory_selected": "Nav izvēlēts katalogs", + "no_write_permission": "Nevar augšupielādēt šajā direktorijā. Noklikšķiniet šeit, lai uzzinātu vairāk.", + "reset_achievements": "Atiestatīt sasniegumus", + "reset_achievements_description": "Tas atiestatīs visus sasniegumus {{game}} spēlei", + "reset_achievements_title": "Vai esat pārliecināts?", + "reset_achievements_success": "Sasniegumi veiksmīgi atiestatīti", + "reset_achievements_error": "Neizdevās atiestatīt sasniegumus", + "download_error_gofile_quota_exceeded": "Jūs pārsniedzāt Gofile mēneša kvotu. Lūdzu, uzgaidiet, kamēr kvota tiks atjaunota.", + "download_error_real_debrid_account_not_authorized": "Jūsu Real-Debrid konts nav autorizēts jaunām lejupielādēm. Lūdzu, pārbaudiet konta iestatījumus un mēģiniet vēlreiz.", + "download_error_not_cached_on_real_debrid": "Šī lejupielāde nav pieejama Real-Debrid, un Real-Debrid lejupielādes statusu pagaidām nav iespējams iegūt.", + "update_playtime_title": "Atjaunināt spēles laiku", + "update_playtime_description": "Manuāli atjauniniet spēles laiku {{game}} spēlei", + "update_playtime": "Atjaunināt spēles laiku", + "update_playtime_success": "Spēles laiks veiksmīgi atjaunināts", + "update_playtime_error": "Neizdevās atjaunināt spēles laiku", + "update_game_playtime": "Atjaunināt spēles laiku", + "manual_playtime_warning": "Jūsu stundas tiks atzīmētas kā manuāli atjauninātas. Šo darbību nevar atcelt.", + "manual_playtime_tooltip": "Šis spēles laiks tika atjaunināts manuāli", + "download_error_not_cached_on_torbox": "Šī lejupielāde nav pieejama TorBox, un TorBox lejupielādes statusu pagaidām nav iespējams iegūt.", + "download_error_not_cached_on_hydra": "Šī lejupielāde nav pieejama Nimbus.", + "game_removed_from_favorites": "Spēle dzēsta no izlases", + "game_added_to_favorites": "Spēle pievienota izlasei", + "game_removed_from_pinned": "Spēle dzēsta no piespraustajiem", + "game_added_to_pinned": "Spēle pievienota piespraustajiem", + "automatically_extract_downloaded_files": "Automātiska lejupielādēto failu izpakošana", + "create_start_menu_shortcut": "Izveidot saīsni sākuma izvēlnē", + "invalid_wine_prefix_path": "Nederīgs Wine prefiksa ceļš", + "invalid_wine_prefix_path_description": "Wine prefiksa ceļš nav derīgs. Lūdzu, pārbaudiet ceļu un mēģiniet vēlreiz.", + "missing_wine_prefix": "Wine prefikss ir nepieciešams, lai izveidotu rezerves kopiju Linux vidē", + "artifact_renamed": "Rezerves kopija veiksmīgi pārsaukta", + "rename_artifact": "Pārsaukt rezerves kopiju", + "rename_artifact_description": "Pārsauciet rezerves kopiju, piešķirot tai aprakstošāku nosaukumu.", + "artifact_name_label": "Rezerves kopijas nosaukums", + "artifact_name_placeholder": "Ievadiet nosaukumu rezerves kopijai", + "save_changes": "Saglabāt izmaiņas", + "required_field": "Šis lauks ir obligāts", + "max_length_field": "Šim laukam jābūt mazāk par {{length}} simboliem", + "freeze_backup": "Piespraust, lai to nepārrakstītu automātiskās rezerves kopijas", + "unfreeze_backup": "Atspraust", + "backup_frozen": "Rezerves kopija piesprausta", + "backup_unfrozen": "Rezerves kopija atsprausta", + "backup_freeze_failed": "Neizdevās piespraust rezerves kopiju", + "backup_freeze_failed_description": "Jums jāatstāj vismaz viens brīvs slots automātiskajām rezerves kopijām", + "edit_game_modal_button": "Rediģēt spēles detaļas", + "game_details": "Spēles detaļas", + "currency_symbol": "₽", + "currency_country": "ru", + "prices": "Cenas", + "no_prices_found": "Cenas nav atrastas", + "view_all_prices": "Noklikšķiniet, lai skatītu visas cenas", + "retail_price": "Mazumtirdzniecības cena", + "keyshop_price": "Atslēgu veikala cena", + "historical_retail": "Vēsturiskās mazumtirdzniecības cenas", + "historical_keyshop": "Vēsturiskās atslēgu veikalu cenas", + "language": "Valoda", + "caption": "Subtitri", + "audio": "Audio", + "filter_by_source": "Filtrēt pēc avota", + "no_repacks_found": "Avoti šai spēlei nav atrasti" + }, + "activation": { + "title": "Aktivizēt Hydra", + "installation_id": "Instalācijas ID:", + "enter_activation_code": "Ievadiet savu aktivizācijas kodu", + "message": "Ja nezināt, kur to pieprasīt, jums to nevajadzētu būt.", + "activate": "Aktivizēt", + "loading": "Ielādēšana…" + }, + "downloads": { + "resume": "Atsākt", + "pause": "Apturēt", + "eta": "Beigsies {{eta}}", + "paused": "Apturēts", + "verifying": "Pārbauda…", + "completed": "Pabeigts", + "removed": "Nav lejupielādēts", + "cancel": "Atcelt", + "filter": "Meklēt lejupielādētās spēles", + "remove": "Dzēst", + "downloading_metadata": "Lejupielādē metadatus…", + "deleting": "Dzēš instalētāju…", + "delete": "Dzēst instalētāju", + "delete_modal_title": "Vai esat pārliecināts?", + "delete_modal_description": "Tas dzēsīs visus instalētājus no jūsu datora", + "install": "Instalēt", + "download_in_progress": "Procesā", + "queued_downloads": "Lejupielādes rindā", + "downloads_completed": "Pabeigts", + "queued": "Rindā", + "no_downloads_title": "Šeit ir tik tukšs...", + "no_downloads_description": "Jūs vēl neko neesat lejupielādējis, izmantojot Hydra, bet nekad nav par vēlu sākt.", + "checking_files": "Pārbauda failus…", + "seeding": "Sēdēšana", + "stop_seeding": "Apturēt sēdēšanu", + "resume_seeding": "Turpināt sēdēšanu", + "options": "Pārvaldīt", + "extract": "Izpakot failus", + "extracting": "Izpako failus…" + }, + "settings": { + "downloads_path": "Lejupielāžu ceļš", + "change": "Mainīt", + "notifications": "Paziņojumi", + "enable_download_notifications": "Pēc lejupielādes pabeigšanas", + "enable_repack_list_notifications": "Pievienojot jaunu repaku", + "real_debrid_api_token_label": "Real-Debrid API-atslēga", + "quit_app_instead_hiding": "Aizvērt lietotni, nevis minimizēt uz paplātes", + "launch_with_system": "Palaist Hydra kopā ar sistēmu", + "general": "Vispārīgi", + "behavior": "Uzvedība", + "download_sources": "Lejupielādes avoti", + "language": "Valoda", + "api_token": "API atslēga", + "enable_real_debrid": "Iespējot Real-Debrid", + "real_debrid_description": "Real-Debrid ir neierobežots lejupielādētājs, kas ļauj ātri lejupielādēt failus, kas izvietoti internetā, vai uzreiz pārsūtīt tos uz atskaņotāju, izmantojot privātu tīklu, kas ļauj apiet jebkādus bloķējumus.", + "debrid_invalid_token": "Nederīga API atslēga", + "debrid_api_token_hint": "API atslēgu var iegūt <0>šeit", + "real_debrid_free_account_error": "Kontam \"{{username}}\" nav abonementa. Lūdzu, iegādājieties Real-Debrid abonementu", + "debrid_linked_message": "Piesaistīts konts \"{{username}}\"", + "save_changes": "Saglabāt izmaiņas", + "changes_saved": "Izmaiņas veiksmīgi saglabātas", + "download_sources_description": "Hydra saņems lejupielādes saites no šiem avotiem. URL jāietver tieša saite uz .json failu ar lejupielādes saitēm.", + "validate_download_source": "Pārbaudīt", + "remove_download_source": "Dzēst", + "add_download_source": "Pievienot avotu", + "download_count_zero": "Sarakstā nav lejupielāžu", + "download_count_one": "{{countFormatted}} lejupielāde sarakstā", + "download_count_other": "{{countFormatted}} lejupielādes sarakstā", + "download_source_url": "Saite uz avotu", + "add_download_source_description": "Ievietojiet saiti uz .json failu", + "download_source_up_to_date": "Atjaunināts", + "download_source_errored": "Kļūda", + "sync_download_sources": "Atjaunināt avotus", + "removed_download_source": "Avots dzēsts", + "removed_download_sources": "Avoti dzēsti", + "cancel_button_confirmation_delete_all_sources": "Nē", + "confirm_button_confirmation_delete_all_sources": "Jā, dzēst visus", + "title_confirmation_delete_all_sources": "Dzēst visus avotus", + "description_confirmation_delete_all_sources": "Jūs dzēsīsiet visus avotus", + "button_delete_all_sources": "Dzēst visus avotus", + "added_download_source": "Avots pievienots", + "download_sources_synced": "Visi avoti atjaunināti", + "insert_valid_json_url": "Ievietojiet derīgu JSON faila URL", + "found_download_option_zero": "Nav atrasts lejupielādes variantu", + "found_download_option_one": "Atrasts {{countFormatted}} lejupielādes variants", + "found_download_option_other": "Atrasti {{countFormatted}} lejupielādes varianti", + "import": "Importēt", + "importing": "Importē...", + "public": "Publisks", + "private": "Privāts", + "friends_only": "Tikai draugiem", + "privacy": "Konfidencialitāte", + "profile_visibility": "Profila redzamība", + "profile_visibility_description": "Izvēlieties, kurš var redzēt jūsu profilu un bibliotēku", + "required_field": "Šis lauks ir obligāts", + "source_already_exists": "Šis avots jau ir pievienots", + "must_be_valid_url": "Avotam jābūt pareizam URL", + "blocked_users": "Bloķētie lietotāji", + "user_unblocked": "Lietotājs atbloķēts", + "enable_achievement_notifications": "Kad sasniegums ir atbloķēts", + "launch_minimized": "Palaist Hydra minimizētā veidā", + "disable_nsfw_alert": "Atspējot brīdinājumu par neķītru saturu", + "seed_after_download_complete": "Sēdēt pēc lejupielādes pabeigšanas", + "show_hidden_achievement_description": "Rādīt slēpto sasniegumu aprakstu pirms to iegūšanas", + "account": "Konts", + "no_users_blocked": "Jums nav bloķētu lietotāju", + "subscription_active_until": "Jūsu Hydra Cloud abonements ir aktīvs līdz {{date}}", + "manage_subscription": "Pārvaldīt abonementu", + "update_email": "Atjaunināt e-pastu", + "update_password": "Atjaunināt paroli", + "current_email": "Pašreizējais e-pasts:", + "no_email_account": "Jūs vēl neesat iestatījis e-pastu", + "account_data_updated_successfully": "Konta dati veiksmīgi atjaunināti", + "renew_subscription": "Atjaunot Hydra Cloud abonementu", + "subscription_expired_at": "Jūsu abonementa termiņš beidzās {{date}}", + "no_subscription": "Izbaudiet Hydra pilnībā", + "become_subscriber": "Kļūstiet par Hydra Cloud īpašnieku", + "subscription_renew_cancelled": "Automātiskā atjaunošana atspējota", + "subscription_renews_on": "Jūsu abonements tiek atjaunots {{date}}", + "bill_sent_until": "Jūsu nākamais rēķins tiks nosūtīts līdz šai dienai", + "no_themes": "Šķiet, ka jums vēl nav tēmu, bet neuztraucieties, noklikšķiniet šeit, lai izveidotu savu pirmo šedevru", + "editor_tab_code": "Kods", + "editor_tab_info": "Informācija", + "editor_tab_save": "Saglabāt", + "web_store": "Tīmekļa veikals", + "clear_themes": "Notīrīt", + "create_theme": "Izveidot", + "create_theme_modal_title": "Izveidot pielāgotu tēmu", + "create_theme_modal_description": "Izveidot jaunu tēmu, lai pielāgotu Hydra izskatu", + "theme_name": "Nosaukums", + "insert_theme_name": "Ievietot tēmas nosaukumu", + "set_theme": "Iestatīt tēmu", + "unset_theme": "Noņemt tēmu", + "delete_theme": "Dzēst tēmu", + "edit_theme": "Rediģēt tēmu", + "delete_all_themes": "Dzēst visas tēmas", + "delete_all_themes_description": "Tas dzēsīs visas jūsu pielāgotās tēmas", + "delete_theme_description": "Tas dzēsīs tēmu {{theme}}", + "cancel": "Atcelt", + "appearance": "Izskats", + "debrid": "Debrid", + "debrid_description": "Debrid servisi ir premium lejupielādētāji bez ierobežojumiem, kas ļauj ātri lejupielādēt failus no dažādiem failu apmaiņas servisiem, ierobežojoties tikai ar jūsu interneta ātrumu.", + "enable_torbox": "Iespējot TorBox", + "torbox_description": "TorBox ir jūsu premium serviss, kas konkurē pat ar labākajiem serveriem tirgū.", + "torbox_account_linked": "TorBox konts piesaistīts", + "create_real_debrid_account": "Noklikšķiniet šeit, ja jums vēl nav Real-Debrid konta", + "create_torbox_account": "Noklikšķiniet šeit, ja jums vēl nav TorBox konta", + "real_debrid_account_linked": "Real-Debrid konts piesaistīts", + "name_min_length": "Tēmas nosaukumam jābūt vismaz 3 simbolus garam", + "import_theme": "Importēt tēmu", + "import_theme_description": "Jūs importēsiet {{theme}} no tēmu veikala", + "error_importing_theme": "Kļūda importējot tēmu", + "theme_imported": "Tēma veiksmīgi importēta", + "enable_friend_request_notifications": "Saņemot draudzības pieprasījumu", + "enable_auto_install": "Automātiski lejupielādēt atjauninājumus", + "common_redist": "Bibliotēkas", + "common_redist_description": "Dažu spēļu palaišanai ir nepieciešamas bibliotēkas. Lai izvairītos no problēmām, ieteicams tās instalēt.", + "install_common_redist": "Instalēt", + "installing_common_redist": "Instalēšana…", + "show_download_speed_in_megabytes": "Rādīt lejupielādes ātrumu megabaitos sekundē", + "extract_files_by_default": "Izpakot failus pēc noklusējuma pēc lejupielādes", + "enable_steam_achievements": "Iespējot Steam sasniegumu meklēšanu", + "achievement_custom_notification_position": "Sasniegumu paziņojumu pozīcija", + "top-left": "Augšējais kreisais stūris", + "top-center": "Augšējais centrs", + "top-right": "Augšējais labais stūris", + "bottom-left": "Apakšējais kreisais stūris", + "bottom-center": "Apakšējais centrs", + "bottom-right": "Apakšējais labais stūris", + "enable_achievement_custom_notifications": "Iespējot sasniegumu paziņojumus", + "alignment": "Izlīdzināšana", + "variation": "Variācija", + "default": "Pēc noklusējuma", + "rare": "Retais", + "platinum": "Platīna", + "hidden": "Slēpts", + "test_notification": "Testa paziņojums", + "notification_preview": "Sasnieguma paziņojuma priekšskatījums", + "enable_friend_start_game_notifications": "Kad draugs sāk spēlēt spēli" + }, + "notifications": { + "download_complete": "Lejupielāde pabeigta", + "game_ready_to_install": "{{title}} ir gatava instalēšanai", + "repack_list_updated": "Repaku saraksts atjaunināts", + "repack_count_one": "{{count}} repaks pievienots", + "repack_count_other": "{{count}} repaki pievienoti", + "new_update_available": "Pieejama jauna versija {{version}}", + "restart_to_install_update": "Pārstartējiet Hydra, lai instalētu atjauninājumu", + "notification_achievement_unlocked_title": "Sasniegums atbloķēts spēlei {{game}}", + "notification_achievement_unlocked_body": "tika atbloķēti {{achievement}} un citi {{count}}", + "new_friend_request_description": "{{displayName}} nosūtīja jums draudzības pieprasījumu", + "new_friend_request_title": "Jauns draudzības pieprasījums", + "extraction_complete": "Izpakošana pabeigta", + "game_extracted": "{{title}} veiksmīgi izpakots", + "friend_started_playing_game": "{{displayName}} sāka spēlēt spēli", + "test_achievement_notification_title": "Šis ir testa paziņojums", + "test_achievement_notification_description": "Diezgan forši, vai ne?" + }, + "system_tray": { + "open": "Atvērt Hydra", + "quit": "Iziet" + }, + "game_card": { + "available_one": "Pieejams", + "available_other": "Pieejams", + "no_downloads": "Nav pieejamu avotu", + "calculating": "Aprēķina" + }, + "binary_not_found_modal": { + "title": "Programmas nav instalētas", + "description": "Wine vai Lutris nav atrasti", + "instructions": "Uzziniet pareizo veidu, kā instalēt kādu no tiem jūsu Linux distribūcijā, lai spēle varētu normāli darboties" + }, + "modal": { + "close": "Aizvērt" + }, + "forms": { + "toggle_password_visibility": "Rādīt paroli" + }, + "user_profile": { + "amount_hours": "{{amount}} stundas", + "amount_minutes": "{{amount}} minūtes", + "amount_hours_short": "{{amount}}h", + "amount_minutes_short": "{{amount}}m", + "last_time_played": "Pēdējā spēle {{period}}", + "activity": "Nesenā aktivitāte", + "library": "Bibliotēka", + "pinned": "Piespraustās", + "achievements_earned": "Nopelnītie sasniegumi", + "played_recently": "Nesen spēlētās", + "playtime": "Spēles laiks", + "total_play_time": "Kopējais spēles laiks", + "manual_playtime_tooltip": "Spēles laiks tika atjaunināts manuāli", + "no_recent_activity_title": "Hmmmm... Šeit nav nekā", + "no_recent_activity_description": "Jūs sen neesat neko spēlējis. Ir laiks to mainīt!", + "display_name": "Parādāmais vārds", + "saving": "Saglabāšana", + "save": "Saglabāt", + "edit_profile": "Rediģēt profilu", + "saved_successfully": "Veiksmīgi saglabāts", + "try_again": "Lūdzu, mēģiniet vēlreiz", + "sign_out_modal_title": "Vai esat pārliecināts?", + "cancel": "Atcelt", + "successfully_signed_out": "Veiksmīga izrakstīšanās no konta", + "sign_out": "Iziet", + "playing_for": "Spēlēts {{amount}}", + "sign_out_modal_text": "Jūsu bibliotēka ir saistīta ar pašreizējo kontu. Izejot no sistēmas, jūsu bibliotēka kļūs nepieejama, un progress netiks saglabāts. Iziet?", + "add_friends": "Pievienot draugus", + "add": "Pievienot", + "friend_code": "Drauga kods", + "see_profile": "Skatīt profilu", + "sending": "Sūtīšana", + "friend_request_sent": "Draudzības pieprasījums nosūtīts", + "friends": "Draugi", + "friends_list": "Draugu saraksts", + "user_not_found": "Lietotājs nav atrasts", + "block_user": "Bloķēt lietotāju", + "add_friend": "Pievienot draugu", + "request_sent": "Pieprasījums nosūtīts", + "request_received": "Pieprasījums saņemts", + "accept_request": "Pieņemt pieprasījumu", + "ignore_request": "Ignorēt pieprasījumu", + "cancel_request": "Atcelt pieprasījumu", + "undo_friendship": "Dzēst draugu", + "request_accepted": "Pieprasījums pieņemts", + "user_blocked_successfully": "Lietotājs veiksmīgi bloķēts", + "user_block_modal_text": "{{displayName}} tiks bloķēts", + "blocked_users": "Bloķētie lietotāji", + "unblock": "Atbloķēt", + "no_friends_added": "Jūs vēl neesat pievienojis nevienu draugu", + "pending": "Gaida", + "no_pending_invites": "Jums nav pieprasījumu, kas gaida atbildi", + "no_blocked_users": "Jūs neesat bloķējis nevienu lietotāju", + "friend_code_copied": "Drauga kods kopēts", + "undo_friendship_modal_text": "Tas atcels jūsu draudzību ar {{displayName}}.", + "privacy_hint": "Lai norādītu, kurš to var redzēt, dodieties uz <0>Iestatījumiem.", + "locked_profile": "Šis profils ir privāts", + "image_process_failure": "Attēlu apstrādes kļūme", + "required_field": "Šis lauks ir obligāts", + "displayname_min_length": "Parādāmam vārdam jābūt vismaz 3 simbolus garam.", + "displayname_max_length": "Parādāmam vārdam jābūt ne vairāk kā 50 simboliem.", + "report_profile": "Ziņot par šo profilu", + "report_reason": "Kāpēc jūs ziņojat par šo profilu?", + "report_description": "Papildu informācija", + "report_description_placeholder": "Papildu informācija", + "report": "Ziņot", + "report_reason_hate": "Naida runa", + "report_reason_sexual_content": "Seksuāls saturs", + "report_reason_violence": "Vardarbība", + "report_reason_spam": "Surogātpasts", + "report_reason_other": "Cits", + "profile_reported": "Ziņojums par profilu nosūtīts", + "your_friend_code": "Jūsu drauga kods:", + "upload_banner": "Augšupielādēt reklāmkarogu", + "uploading_banner": "Augšupielādē reklāmkarogu...", + "background_image_updated": "Fona attēls atjaunināts", + "stats": "Statistika", + "achievements": "Sasniegumi", + "games": "Spēles", + "top_percentile": "Top {{percentile}}%", + "ranking_updated_weekly": "Reitings tiek atjaunināts katru nedēļu", + "playing": "Spēlē {{game}}", + "achievements_unlocked": "Sasniegumi atbloķēti", + "earned_points": "Nopelnītie punkti:", + "show_achievements_on_profile": "Rādīt savus sasniegumus profilā", + "show_points_on_profile": "Rādīt nopelnītos punktus savā profilā", + "error_adding_friend": "Neizdevās nosūtīt draudzības pieprasījumu. Lūdzu, pārbaudiet drauga kodu", + "friend_code_length_error": "Drauga kodam jāsatur 8 simboli", + "game_removed_from_pinned": "Spēle dzēsta no piespraustajiem", + "game_added_to_pinned": "Spēle pievienota piespraustajiem", + "karma": "Karma", + "karma_count": "karma", + "karma_description": "Nopelnīta ar pozitīviem atsauksmju vērtējumiem" + }, + "achievement": { + "achievement_unlocked": "Sasniegums atbloķēts", + "user_achievements": "{{displayName}} sasniegumi", + "your_achievements": "Jūsu sasniegumi", + "unlocked_at": "Atbloķēts: {{date}}", + "subscription_needed": "Šī satura apskatīšanai nepieciešams Hydra Cloud abonements", + "new_achievements_unlocked": "Atbloķēti {{achievementCount}} jauni sasniegumi no {{gameCount}} spēlēm", + "achievement_progress": "{{unlockedCount}}/{{totalCount}} sasniegumi", + "achievements_unlocked_for_game": "Atbloķēti {{achievementCount}} jauni sasniegumi spēlei {{gameTitle}}", + "hidden_achievement_tooltip": "Šis ir slēpts sasniegums", + "achievement_earn_points": "Nopelniet {{points}} punktus ar šo sasniegumu", + "earned_points": "Nopelnītie punkti:", + "available_points": "Pieejamie punkti:", + "how_to_earn_achievements_points": "Kā nopelnīt sasniegumu punktus?" + }, + "hydra_cloud": { + "subscription_tour_title": "Hydra Cloud abonements", + "subscribe_now": "Abonējiet tūlīt", + "cloud_saving": "Saglabāšana mākonī", + "cloud_achievements": "Saglabājiet savus sasniegumus mākonī", + "animated_profile_picture": "Animētas profila bildes", + "premium_support": "Premium atbalsts", + "show_and_compare_achievements": "Rādiet un salīdziniet savus sasniegumus ar citu lietotāju sasniegumiem", + "animated_profile_banner": "Animēts profila reklāmkarogs", + "hydra_cloud": "Hydra Cloud", + "hydra_cloud_feature_found": "Jūs tikko atklājāt Hydra Cloud funkciju!", + "learn_more": "Uzzināt vairāk", + "debrid_description": "Lejupielādējiet 4 reizes ātrāk ar Nimbus" + } +} \ No newline at end of file From 3df07fefe5f480fac232cc98a95c28cfc0fd1104 Mon Sep 17 00:00:00 2001 From: GrimmDevel Date: Thu, 23 Oct 2025 00:59:38 +0300 Subject: [PATCH 114/197] fixed lint error --- src/locales/index.ts | 2 +- src/locales/lv/translation.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/locales/index.ts b/src/locales/index.ts index d58ee59a..ca9ec757 100644 --- a/src/locales/index.ts +++ b/src/locales/index.ts @@ -28,7 +28,7 @@ import bg from "./bg/translation.json"; import uz from "./uz/translation.json"; import fi from "./fi/translation.json"; import sv from "./sv/translation.json"; -import lv from "./lv/translation.json"; +import lv from "./lv/translation.json"; export default { "pt-BR": ptBR, diff --git a/src/locales/lv/translation.json b/src/locales/lv/translation.json index a33a1986..26aacb74 100644 --- a/src/locales/lv/translation.json +++ b/src/locales/lv/translation.json @@ -705,4 +705,4 @@ "learn_more": "Uzzināt vairāk", "debrid_description": "Lejupielādējiet 4 reizes ātrāk ar Nimbus" } -} \ No newline at end of file +} From 9a278dc6144b5c93bbd52b57529e99ee17961053 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 09:49:07 -0300 Subject: [PATCH 115/197] chore: action to update aur --- .github/workflows/update-aur.yml | 120 +++++++++++++++++++++++++++++++ scripts/update-pkgver.js | 32 +++++++++ 2 files changed, 152 insertions(+) create mode 100644 .github/workflows/update-aur.yml create mode 100755 scripts/update-pkgver.js diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml new file mode 100644 index 00000000..7ccfb746 --- /dev/null +++ b/.github/workflows/update-aur.yml @@ -0,0 +1,120 @@ +name: Update AUR Package + +on: + workflow_dispatch: + release: + types: [published] + +jobs: + update-aur: + runs-on: ubuntu-latest + container: + image: archlinux:latest + + steps: + - name: Checkout main repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install dependencies + run: | + pacman -Syu --noconfirm + pacman -S --noconfirm nodejs npm git base-devel + + - name: Get version to update + id: get-version + run: | + if [ "${{ github.event_name }}" = "release" ]; then + # Remove 'v' prefix if present + VERSION="${{ github.event.release.tag_name }}" + VERSION="${VERSION#v}" + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "source=release" >> $GITHUB_OUTPUT + else + # Get latest release version + VERSION=$(curl -s https://api.github.com/repos/${{ github.repository }}/releases/latest | jq -r '.tag_name' | sed 's/^v//') + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "source=latest" >> $GITHUB_OUTPUT + fi + echo "Version to update: $VERSION" + + - name: Setup SSH for AUR + run: | + mkdir -p ~/.ssh + echo "${{ secrets.AUR_SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ssh-keyscan aur.archlinux.org >> ~/.ssh/known_hosts + eval "$(ssh-agent -s)" + ssh-add ~/.ssh/id_rsa + + - name: Clone AUR repository + run: | + git clone ssh://aur@aur.archlinux.org/hydra-launcher-bin.git + + - name: Check if update is needed + id: check-update + run: | + cd hydra-launcher-bin + CURRENT_VERSION=$(grep '^pkgver=' hydra-launcher-bin/PKGBUILD | cut -d'=' -f2) + NEW_VERSION="${{ steps.get-version.outputs.version }}" + + echo "Current AUR version: $CURRENT_VERSION" + echo "New version: $NEW_VERSION" + + if [ "$CURRENT_VERSION" = "$NEW_VERSION" ]; then + echo "update_needed=false" >> $GITHUB_OUTPUT + echo "No update needed - versions are the same" + else + echo "update_needed=true" >> $GITHUB_OUTPUT + echo "Update needed" + fi + + - name: Update PKGBUILD and .SRCINFO + if: steps.check-update.outputs.update_needed == 'true' + run: | + cd hydra-launcher-bin + node ../scripts/update-pkgver.js "${{ steps.get-version.outputs.version }}" ./PKGBUILD + updpkgsums + makepkg --printsrcinfo > .SRCINFO + + - name: Configure Git + if: steps.check-update.outputs.update_needed == 'true' + run: | + cd hydra-launcher-bin + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Commit and push changes + if: steps.check-update.outputs.update_needed == 'true' + run: | + cd hydra-launcher-bin + git add PKGBUILD .SRCINFO + + if git diff --staged --quiet; then + echo "No changes to commit" + else + COMMIT_MSG="Update to ${{ steps.get-version.outputs.version }}" + if [ "${{ steps.get-version.outputs.source }}" = "release" ]; then + COMMIT_MSG="$COMMIT_MSG (automated release update)" + else + COMMIT_MSG="$COMMIT_MSG (latest release)" + fi + + git commit -m "$COMMIT_MSG" + git push origin master + echo "Successfully updated AUR package to version ${{ steps.get-version.outputs.version }}" + fi + + - name: Create summary + if: always() + run: | + echo "## AUR Update Summary" >> $GITHUB_STEP_SUMMARY + echo "- **Version**: ${{ steps.get-version.outputs.version }}" >> $GITHUB_STEP_SUMMARY + echo "- **Source**: ${{ steps.get-version.outputs.source }}" >> $GITHUB_STEP_SUMMARY + echo "- **Update needed**: ${{ steps.check-update.outputs.update_needed }}" >> $GITHUB_STEP_SUMMARY + if [ "${{ steps.check-update.outputs.update_needed }}" = "true" ]; then + echo "- **Status**: ✅ AUR package updated successfully" >> $GITHUB_STEP_SUMMARY + else + echo "- **Status**: ⏭️ No update needed" >> $GITHUB_STEP_SUMMARY + fi diff --git a/scripts/update-pkgver.js b/scripts/update-pkgver.js new file mode 100755 index 00000000..41d87f0b --- /dev/null +++ b/scripts/update-pkgver.js @@ -0,0 +1,32 @@ +const fs = require("node:fs"); + +function updatePkgver(newVersion, pkgbuildPath) { + try { + const content = fs.readFileSync(pkgbuildPath, "utf8"); + const lines = content.split("\n"); + + const updatedLines = lines.map((line) => { + if (line.trim().startsWith("pkgver=")) { + return `pkgver=${newVersion}`; + } + return line; + }); + + fs.writeFileSync(pkgbuildPath, updatedLines.join("\n"), "utf8"); + + console.log( + `✅ Successfully updated pkgver to ${newVersion} in ${pkgbuildPath}` + ); + } catch (error) { + console.error(`❌ Error updating pkgver: ${error.message}`); + process.exit(1); + } +} + +// Get version from command line arguments +const args = process.argv.slice(2); + +const newVersion = args[0]; +const pkgbuildPath = args[1] || "./PKGBUILD"; + +updatePkgver(newVersion, pkgbuildPath); From face25916725096a4eb6f9ca4bcdf724aba295d0 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 10:02:31 -0300 Subject: [PATCH 116/197] chore: dont apply aur changes on workflow_dispatch --- .github/workflows/update-aur.yml | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 7ccfb746..fce745d9 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -85,8 +85,20 @@ jobs: git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - - name: Commit and push changes - if: steps.check-update.outputs.update_needed == 'true' + - name: Show changes (workflow_dispatch only) + if: steps.check-update.outputs.update_needed == 'true' && github.event_name == 'workflow_dispatch' + run: | + cd hydra-launcher-bin + echo "## Git Diff Preview" + echo "Changes that would be made:" + git diff PKGBUILD .SRCINFO || echo "No changes to show" + echo "" + echo "Staged changes:" + git add PKGBUILD .SRCINFO + git diff --staged || echo "No staged changes" + + - name: Commit and push changes (release only) + if: steps.check-update.outputs.update_needed == 'true' && github.event_name == 'release' run: | cd hydra-launcher-bin git add PKGBUILD .SRCINFO @@ -94,12 +106,7 @@ jobs: if git diff --staged --quiet; then echo "No changes to commit" else - COMMIT_MSG="Update to ${{ steps.get-version.outputs.version }}" - if [ "${{ steps.get-version.outputs.source }}" = "release" ]; then - COMMIT_MSG="$COMMIT_MSG (automated release update)" - else - COMMIT_MSG="$COMMIT_MSG (latest release)" - fi + COMMIT_MSG="Update to ${{ steps.get-version.outputs.version }} (automated release update)" git commit -m "$COMMIT_MSG" git push origin master From 1e8983d0c0595d5a0955676b2bb7f7cd3d774ca0 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 10:23:40 -0300 Subject: [PATCH 117/197] chore: add openssh in arch packages --- .github/workflows/update-aur.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index fce745d9..f49cf821 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -20,7 +20,7 @@ jobs: - name: Install dependencies run: | pacman -Syu --noconfirm - pacman -S --noconfirm nodejs npm git base-devel + pacman -S --noconfirm nodejs npm git base-devel openssh - name: Get version to update id: get-version From 313f2cd5853be692f09d35d4885d1b82175f9b82 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 10:52:53 -0300 Subject: [PATCH 118/197] chore: try fixing action --- .github/workflows/update-aur.yml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index f49cf821..d7297859 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -26,20 +26,19 @@ jobs: id: get-version run: | if [ "${{ github.event_name }}" = "release" ]; then - # Remove 'v' prefix if present VERSION="${{ github.event.release.tag_name }}" - VERSION="${VERSION#v}" echo "version=$VERSION" >> $GITHUB_OUTPUT echo "source=release" >> $GITHUB_OUTPUT else - # Get latest release version - VERSION=$(curl -s https://api.github.com/repos/${{ github.repository }}/releases/latest | jq -r '.tag_name' | sed 's/^v//') + echo "Getting latest release version" + VERSION=$(curl -s https://api.github.com/repos/${{ github.repository }}/releases/latest | jq -r '.tag_name') echo "version=$VERSION" >> $GITHUB_OUTPUT echo "source=latest" >> $GITHUB_OUTPUT fi + echo "Version to update: $VERSION" - - name: Setup SSH for AUR + - name: Clone AUR repository run: | mkdir -p ~/.ssh echo "${{ secrets.AUR_SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa @@ -47,9 +46,6 @@ jobs: ssh-keyscan aur.archlinux.org >> ~/.ssh/known_hosts eval "$(ssh-agent -s)" ssh-add ~/.ssh/id_rsa - - - name: Clone AUR repository - run: | git clone ssh://aur@aur.archlinux.org/hydra-launcher-bin.git - name: Check if update is needed From 805d67d2d176145713a5053c4d0e76c842760f4f Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 10:56:39 -0300 Subject: [PATCH 119/197] fix: add missing package --- .github/workflows/update-aur.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index d7297859..8a17f1bb 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -20,7 +20,7 @@ jobs: - name: Install dependencies run: | pacman -Syu --noconfirm - pacman -S --noconfirm nodejs npm git base-devel openssh + pacman -S --noconfirm nodejs npm git base-devel openssh jq - name: Get version to update id: get-version From 65ae5991e79f0e23722ef9f4a0fe6b1dd2d3ee3b Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 11:14:41 -0300 Subject: [PATCH 120/197] chore: update-aur --- .github/workflows/update-aur.yml | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 8a17f1bb..39883ed1 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -12,16 +12,27 @@ jobs: image: archlinux:latest steps: - - name: Checkout main repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Install dependencies run: | pacman -Syu --noconfirm pacman -S --noconfirm nodejs npm git base-devel openssh jq + - name: Clone AUR repository + run: | + mkdir -p ~/.ssh + echo "${{ secrets.AUR_SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ssh-keyscan aur.archlinux.org > ~/.ssh/known_hosts + cat ~/.ssh/known_hosts + eval "$(ssh-agent -s)" + ssh-add ~/.ssh/id_rsa + git clone ssh://aur@aur.archlinux.org/hydra-launcher-bin.git + + - name: Checkout main repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Get version to update id: get-version run: | @@ -38,16 +49,6 @@ jobs: echo "Version to update: $VERSION" - - name: Clone AUR repository - run: | - mkdir -p ~/.ssh - echo "${{ secrets.AUR_SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa - ssh-keyscan aur.archlinux.org >> ~/.ssh/known_hosts - eval "$(ssh-agent -s)" - ssh-add ~/.ssh/id_rsa - git clone ssh://aur@aur.archlinux.org/hydra-launcher-bin.git - - name: Check if update is needed id: check-update run: | From a1552020c0595483ed2d0cc8666544afbe2e1944 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 11:22:54 -0300 Subject: [PATCH 121/197] chore: update aur --- .github/workflows/update-aur.yml | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 39883ed1..7d24bb34 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -17,15 +17,34 @@ jobs: pacman -Syu --noconfirm pacman -S --noconfirm nodejs npm git base-devel openssh jq - - name: Clone AUR repository + - name: Setup SSH for AUR run: | mkdir -p ~/.ssh echo "${{ secrets.AUR_SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa chmod 600 ~/.ssh/id_rsa - ssh-keyscan aur.archlinux.org > ~/.ssh/known_hosts - cat ~/.ssh/known_hosts + chmod 700 ~/.ssh + + # Add AUR host key to known_hosts + ssh-keyscan aur.archlinux.org >> ~/.ssh/known_hosts + + # Configure SSH to use the key + cat > ~/.ssh/config << EOF + Host aur.archlinux.org + HostName aur.archlinux.org + User aur + IdentityFile ~/.ssh/id_rsa + StrictHostKeyChecking no + EOF + + # Start SSH agent and add key eval "$(ssh-agent -s)" ssh-add ~/.ssh/id_rsa + + # Test SSH connection + ssh aur@aur.archlinux.org "echo 'SSH connection successful'" + + - name: Clone AUR repository + run: | git clone ssh://aur@aur.archlinux.org/hydra-launcher-bin.git - name: Checkout main repository From 4d3ba51b61d425582d8e85afaf382f9a937730a3 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 11:47:38 -0300 Subject: [PATCH 122/197] update-aur.yml --- .github/workflows/update-aur.yml | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 7d24bb34..0aa1153a 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -30,22 +30,17 @@ jobs: # Configure SSH to use the key cat > ~/.ssh/config << EOF Host aur.archlinux.org - HostName aur.archlinux.org - User aur IdentityFile ~/.ssh/id_rsa - StrictHostKeyChecking no + IdentitiesOnly yes + User aur + UserKnownHostsFile ~/.ssh/known_hosts EOF # Start SSH agent and add key eval "$(ssh-agent -s)" ssh-add ~/.ssh/id_rsa - # Test SSH connection - ssh aur@aur.archlinux.org "echo 'SSH connection successful'" - - - name: Clone AUR repository - run: | - git clone ssh://aur@aur.archlinux.org/hydra-launcher-bin.git + ssh-agent bash -c 'ssh-add ~/.ssh/id_rsa; git clone ssh://aur@aur.archlinux.org/hydra-launcher-bin.git' - name: Checkout main repository uses: actions/checkout@v4 From 95a5c3716c7bba133e5434ad24e4dc7e6788223a Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 12:06:37 -0300 Subject: [PATCH 123/197] update-aur --- .github/workflows/update-aur.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 0aa1153a..6bfd7f2b 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -34,6 +34,7 @@ jobs: IdentitiesOnly yes User aur UserKnownHostsFile ~/.ssh/known_hosts + StrictHostKeyChecking no EOF # Start SSH agent and add key From 00e716375eabb71eb0a619fd8e8c99afe41066dd Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 12:13:13 -0300 Subject: [PATCH 124/197] clone with https for testing --- .github/workflows/update-aur.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 6bfd7f2b..dcb257a9 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -41,7 +41,8 @@ jobs: eval "$(ssh-agent -s)" ssh-add ~/.ssh/id_rsa - ssh-agent bash -c 'ssh-add ~/.ssh/id_rsa; git clone ssh://aur@aur.archlinux.org/hydra-launcher-bin.git' + # ssh-agent bash -c 'ssh-add ~/.ssh/id_rsa; git clone ssh://aur@aur.archlinux.org/hydra-launcher-bin.git' + git clone https://aur.archlinux.org/hydra-launcher-bin.git - name: Checkout main repository uses: actions/checkout@v4 From 19d8a09f9d07f3350d933cd743a5b201c3951a60 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 12:17:02 -0300 Subject: [PATCH 125/197] update aur --- .github/workflows/update-aur.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index dcb257a9..af5b6ba6 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -86,7 +86,7 @@ jobs: - name: Update PKGBUILD and .SRCINFO if: steps.check-update.outputs.update_needed == 'true' run: | - cd hydra-launcher-bin + cd ~/hydra-launcher-bin node ../scripts/update-pkgver.js "${{ steps.get-version.outputs.version }}" ./PKGBUILD updpkgsums makepkg --printsrcinfo > .SRCINFO @@ -94,14 +94,14 @@ jobs: - name: Configure Git if: steps.check-update.outputs.update_needed == 'true' run: | - cd hydra-launcher-bin + cd ~/hydra-launcher-bin git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - name: Show changes (workflow_dispatch only) if: steps.check-update.outputs.update_needed == 'true' && github.event_name == 'workflow_dispatch' run: | - cd hydra-launcher-bin + cd ~/hydra-launcher-bin echo "## Git Diff Preview" echo "Changes that would be made:" git diff PKGBUILD .SRCINFO || echo "No changes to show" @@ -113,7 +113,7 @@ jobs: - name: Commit and push changes (release only) if: steps.check-update.outputs.update_needed == 'true' && github.event_name == 'release' run: | - cd hydra-launcher-bin + cd ~/hydra-launcher-bin git add PKGBUILD .SRCINFO if git diff --staged --quiet; then From 7f28929c68e9c82d72bfd6c70f6f2ffbe654cdef Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 12:19:12 -0300 Subject: [PATCH 126/197] update yml --- .github/workflows/update-aur.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index af5b6ba6..8aa9400f 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -68,7 +68,7 @@ jobs: - name: Check if update is needed id: check-update run: | - cd hydra-launcher-bin + cd ~/hydra-launcher-bin CURRENT_VERSION=$(grep '^pkgver=' hydra-launcher-bin/PKGBUILD | cut -d'=' -f2) NEW_VERSION="${{ steps.get-version.outputs.version }}" From 6545c7d7cd7588343b3e8e7e3c23022d8af83d5a Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 12:22:08 -0300 Subject: [PATCH 127/197] update aur --- .github/workflows/update-aur.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 8aa9400f..f1ef2543 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -43,6 +43,8 @@ jobs: # ssh-agent bash -c 'ssh-add ~/.ssh/id_rsa; git clone ssh://aur@aur.archlinux.org/hydra-launcher-bin.git' git clone https://aur.archlinux.org/hydra-launcher-bin.git + pwd + ls - name: Checkout main repository uses: actions/checkout@v4 @@ -68,6 +70,8 @@ jobs: - name: Check if update is needed id: check-update run: | + pwd + ls cd ~/hydra-launcher-bin CURRENT_VERSION=$(grep '^pkgver=' hydra-launcher-bin/PKGBUILD | cut -d'=' -f2) NEW_VERSION="${{ steps.get-version.outputs.version }}" From 2529bdf5ca7fb81ab0b99e7c3df4db3679cb53df Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 12:33:19 -0300 Subject: [PATCH 128/197] fix --- .github/workflows/update-aur.yml | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index f1ef2543..34b114f8 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -43,14 +43,10 @@ jobs: # ssh-agent bash -c 'ssh-add ~/.ssh/id_rsa; git clone ssh://aur@aur.archlinux.org/hydra-launcher-bin.git' git clone https://aur.archlinux.org/hydra-launcher-bin.git + cd hydra-launcher-bin pwd ls - - name: Checkout main repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Get version to update id: get-version run: | @@ -72,7 +68,6 @@ jobs: run: | pwd ls - cd ~/hydra-launcher-bin CURRENT_VERSION=$(grep '^pkgver=' hydra-launcher-bin/PKGBUILD | cut -d'=' -f2) NEW_VERSION="${{ steps.get-version.outputs.version }}" @@ -90,7 +85,6 @@ jobs: - name: Update PKGBUILD and .SRCINFO if: steps.check-update.outputs.update_needed == 'true' run: | - cd ~/hydra-launcher-bin node ../scripts/update-pkgver.js "${{ steps.get-version.outputs.version }}" ./PKGBUILD updpkgsums makepkg --printsrcinfo > .SRCINFO @@ -98,14 +92,12 @@ jobs: - name: Configure Git if: steps.check-update.outputs.update_needed == 'true' run: | - cd ~/hydra-launcher-bin git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - name: Show changes (workflow_dispatch only) if: steps.check-update.outputs.update_needed == 'true' && github.event_name == 'workflow_dispatch' run: | - cd ~/hydra-launcher-bin echo "## Git Diff Preview" echo "Changes that would be made:" git diff PKGBUILD .SRCINFO || echo "No changes to show" @@ -117,7 +109,6 @@ jobs: - name: Commit and push changes (release only) if: steps.check-update.outputs.update_needed == 'true' && github.event_name == 'release' run: | - cd ~/hydra-launcher-bin git add PKGBUILD .SRCINFO if git diff --staged --quiet; then From 52714e3323eb04bcd260b40afd9f37dad63479be Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 12:38:31 -0300 Subject: [PATCH 129/197] remove v from tag --- .github/workflows/update-aur.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 34b114f8..575b6e13 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -56,7 +56,7 @@ jobs: echo "source=release" >> $GITHUB_OUTPUT else echo "Getting latest release version" - VERSION=$(curl -s https://api.github.com/repos/${{ github.repository }}/releases/latest | jq -r '.tag_name') + VERSION=$(curl -s https://api.github.com/repos/${{ github.repository }}/releases/latest | jq -r '.tag_name' | sed 's/^v//') echo "version=$VERSION" >> $GITHUB_OUTPUT echo "source=latest" >> $GITHUB_OUTPUT fi From e12fdf8f8f8a920aa24adb33f8753278f8292d90 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 12:45:09 -0300 Subject: [PATCH 130/197] remove js script --- .github/workflows/update-aur.yml | 15 ++++++++++++++- scripts/update-pkgver.js | 32 -------------------------------- 2 files changed, 14 insertions(+), 33 deletions(-) delete mode 100755 scripts/update-pkgver.js diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 575b6e13..4382d7da 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -85,7 +85,20 @@ jobs: - name: Update PKGBUILD and .SRCINFO if: steps.check-update.outputs.update_needed == 'true' run: | - node ../scripts/update-pkgver.js "${{ steps.get-version.outputs.version }}" ./PKGBUILD + # Update pkgver in PKGBUILD + NEW_VERSION="${{ steps.get-version.outputs.version }}" + + echo "Updating PKGBUILD pkgver to $NEW_VERSION" + + # Read PKGBUILD and update pkgver line + sed -i "s/^pkgver=.*/pkgver=$NEW_VERSION/" ./PKGBUILD + + # Reset pkgrel to 1 when version changes + sed -i "s/^pkgrel=.*/pkgrel=1/" "$PKGBUILD_PATH" + + echo "✅ Successfully updated pkgver to $NEW_VERSION in $PKGBUILD_PATH" + + # Update package checksums and generate .SRCINFO updpkgsums makepkg --printsrcinfo > .SRCINFO diff --git a/scripts/update-pkgver.js b/scripts/update-pkgver.js deleted file mode 100755 index 41d87f0b..00000000 --- a/scripts/update-pkgver.js +++ /dev/null @@ -1,32 +0,0 @@ -const fs = require("node:fs"); - -function updatePkgver(newVersion, pkgbuildPath) { - try { - const content = fs.readFileSync(pkgbuildPath, "utf8"); - const lines = content.split("\n"); - - const updatedLines = lines.map((line) => { - if (line.trim().startsWith("pkgver=")) { - return `pkgver=${newVersion}`; - } - return line; - }); - - fs.writeFileSync(pkgbuildPath, updatedLines.join("\n"), "utf8"); - - console.log( - `✅ Successfully updated pkgver to ${newVersion} in ${pkgbuildPath}` - ); - } catch (error) { - console.error(`❌ Error updating pkgver: ${error.message}`); - process.exit(1); - } -} - -// Get version from command line arguments -const args = process.argv.slice(2); - -const newVersion = args[0]; -const pkgbuildPath = args[1] || "./PKGBUILD"; - -updatePkgver(newVersion, pkgbuildPath); From b96e6095dca4b1e8bfc5363c4054afc0aa71324f Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 16:03:51 -0300 Subject: [PATCH 131/197] update aur --- .github/workflows/update-aur.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 4382d7da..aa11aedb 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -41,8 +41,9 @@ jobs: eval "$(ssh-agent -s)" ssh-add ~/.ssh/id_rsa - # ssh-agent bash -c 'ssh-add ~/.ssh/id_rsa; git clone ssh://aur@aur.archlinux.org/hydra-launcher-bin.git' - git clone https://aur.archlinux.org/hydra-launcher-bin.git + export GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa -F ~/.ssh/config -o UserKnownHostsFile=$SSH_PATH/known_hosts" + + git clone ssh://aur@aur.archlinux.org/hydra-launcher-bin.git cd hydra-launcher-bin pwd ls From 321d1706348894f8207db142132612ce815bdde3 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 16:13:24 -0300 Subject: [PATCH 132/197] force different version to test script --- .github/workflows/update-aur.yml | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index aa11aedb..9ee9ef50 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -45,8 +45,6 @@ jobs: git clone ssh://aur@aur.archlinux.org/hydra-launcher-bin.git cd hydra-launcher-bin - pwd - ls - name: Get version to update id: get-version @@ -67,10 +65,9 @@ jobs: - name: Check if update is needed id: check-update run: | - pwd - ls CURRENT_VERSION=$(grep '^pkgver=' hydra-launcher-bin/PKGBUILD | cut -d'=' -f2) NEW_VERSION="${{ steps.get-version.outputs.version }}" + NEW_VERSION="3.7.0" echo "Current AUR version: $CURRENT_VERSION" echo "New version: $NEW_VERSION" @@ -88,6 +85,7 @@ jobs: run: | # Update pkgver in PKGBUILD NEW_VERSION="${{ steps.get-version.outputs.version }}" + NEW_VERSION=3.7.0 echo "Updating PKGBUILD pkgver to $NEW_VERSION" @@ -109,9 +107,11 @@ jobs: git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - - name: Show changes (workflow_dispatch only) - if: steps.check-update.outputs.update_needed == 'true' && github.event_name == 'workflow_dispatch' + - name: Commit and push changes + if: steps.check-update.outputs.update_needed == 'true' run: | + git add PKGBUILD .SRCINFO + echo "## Git Diff Preview" echo "Changes that would be made:" git diff PKGBUILD .SRCINFO || echo "No changes to show" @@ -120,18 +120,13 @@ jobs: git add PKGBUILD .SRCINFO git diff --staged || echo "No staged changes" - - name: Commit and push changes (release only) - if: steps.check-update.outputs.update_needed == 'true' && github.event_name == 'release' - run: | - git add PKGBUILD .SRCINFO - if git diff --staged --quiet; then echo "No changes to commit" else COMMIT_MSG="Update to ${{ steps.get-version.outputs.version }} (automated release update)" git commit -m "$COMMIT_MSG" - git push origin master + # git push origin master echo "Successfully updated AUR package to version ${{ steps.get-version.outputs.version }}" fi From 9e84cd970e6541437915415c00e8801e381acd33 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 16:24:49 -0300 Subject: [PATCH 133/197] update aur --- .github/workflows/update-aur.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 9ee9ef50..9ec77b92 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -44,7 +44,6 @@ jobs: export GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa -F ~/.ssh/config -o UserKnownHostsFile=$SSH_PATH/known_hosts" git clone ssh://aur@aur.archlinux.org/hydra-launcher-bin.git - cd hydra-launcher-bin - name: Get version to update id: get-version @@ -84,6 +83,7 @@ jobs: if: steps.check-update.outputs.update_needed == 'true' run: | # Update pkgver in PKGBUILD + cd hydra-launcher-bin NEW_VERSION="${{ steps.get-version.outputs.version }}" NEW_VERSION=3.7.0 @@ -104,12 +104,14 @@ jobs: - name: Configure Git if: steps.check-update.outputs.update_needed == 'true' run: | + cd hydra-launcher-bin git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - name: Commit and push changes if: steps.check-update.outputs.update_needed == 'true' run: | + cd hydra-launcher-bin git add PKGBUILD .SRCINFO echo "## Git Diff Preview" From 0814c084594b1f8d9c0e442edef0ad79a42bd1d3 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 16:30:01 -0300 Subject: [PATCH 134/197] update aur --- .github/workflows/update-aur.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 9ec77b92..d34b27ae 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -93,9 +93,9 @@ jobs: sed -i "s/^pkgver=.*/pkgver=$NEW_VERSION/" ./PKGBUILD # Reset pkgrel to 1 when version changes - sed -i "s/^pkgrel=.*/pkgrel=1/" "$PKGBUILD_PATH" + sed -i "s/^pkgrel=.*/pkgrel=1/" ./PKGBUILD - echo "✅ Successfully updated pkgver to $NEW_VERSION in $PKGBUILD_PATH" + echo "✅ Successfully updated pkgver to $NEW_VERSION in ./PKGBUILD" # Update package checksums and generate .SRCINFO updpkgsums From 2179086285ffcb7aa2ced4811fd64d5eff941be8 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 16:34:47 -0300 Subject: [PATCH 135/197] install missing arch package --- .github/workflows/build.yml | 3 --- .github/workflows/release.yml | 4 ---- .github/workflows/update-aur.yml | 2 +- electron-builder.yml | 1 - scripts/upload-build.cjs | 2 +- 5 files changed, 2 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6bc508ec..5062c7ad 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -41,8 +41,6 @@ jobs: - name: Build Linux if: matrix.os == 'ubuntu-latest' run: | - sudo apt-get update - sudo apt-get install -y libarchive-tools yarn build:linux env: MAIN_VITE_API_URL: ${{ vars.MAIN_VITE_STAGING_API_URL }} @@ -98,5 +96,4 @@ jobs: dist/*.tar.gz dist/*.yml dist/*.blockmap - dist/*.pacman dist/*.AppImage diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 72e6e0f3..3ceb42c7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -42,8 +42,6 @@ jobs: - name: Build Linux if: matrix.os == 'ubuntu-latest' run: | - sudo apt-get update - sudo apt-get install -y libarchive-tools yarn build:linux env: MAIN_VITE_API_URL: ${{ vars.MAIN_VITE_API_URL }} @@ -90,7 +88,6 @@ jobs: dist/*.tar.gz dist/*.yml dist/*.blockmap - dist/*.pacman - name: Upload build env: @@ -119,6 +116,5 @@ jobs: dist/*.tar.gz dist/*.yml dist/*.blockmap - dist/*.pacman env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index d34b27ae..97e84209 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -15,7 +15,7 @@ jobs: - name: Install dependencies run: | pacman -Syu --noconfirm - pacman -S --noconfirm nodejs npm git base-devel openssh jq + pacman -S --noconfirm nodejs npm git base-devel openssh jq pacman-contrib - name: Setup SSH for AUR run: | diff --git a/electron-builder.yml b/electron-builder.yml index 50fe8139..ec162530 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -56,7 +56,6 @@ linux: - AppImage - snap - deb - - pacman - rpm maintainer: electronjs.org category: Game diff --git a/scripts/upload-build.cjs b/scripts/upload-build.cjs index fe475163..15e3a5b4 100644 --- a/scripts/upload-build.cjs +++ b/scripts/upload-build.cjs @@ -20,7 +20,7 @@ const s3 = new S3Client({ const dist = path.resolve(__dirname, "..", "dist"); -const extensionsToUpload = [".deb", ".exe", ".pacman", ".AppImage"]; +const extensionsToUpload = [".deb", ".exe", ".AppImage"]; fs.readdir(dist, async (err, files) => { if (err) throw err; From 7fc9962e040c338f79409439b61e19754663367c Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 16:55:51 -0300 Subject: [PATCH 136/197] chore: create builder user to run makepkg --- .github/workflows/update-aur.yml | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 97e84209..1e170ace 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -17,6 +17,17 @@ jobs: pacman -Syu --noconfirm pacman -S --noconfirm nodejs npm git base-devel openssh jq pacman-contrib + - name: Create builder user + run: | + # Create builder user with home directory + useradd -m -s /bin/bash builder + + # Add builder to wheel group for sudo access + usermod -aG wheel builder + + # Configure sudo for builder user (no password required) + echo 'builder ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers + - name: Setup SSH for AUR run: | mkdir -p ~/.ssh @@ -45,6 +56,9 @@ jobs: git clone ssh://aur@aur.archlinux.org/hydra-launcher-bin.git + # Give builder user ownership of the repository + chown -R builder:builder hydra-launcher-bin + - name: Get version to update id: get-version run: | @@ -97,9 +111,9 @@ jobs: echo "✅ Successfully updated pkgver to $NEW_VERSION in ./PKGBUILD" - # Update package checksums and generate .SRCINFO - updpkgsums - makepkg --printsrcinfo > .SRCINFO + # Update package checksums and generate .SRCINFO as builder user + sudo -u builder updpkgsums + sudo -u builder makepkg --printsrcinfo > .SRCINFO - name: Configure Git if: steps.check-update.outputs.update_needed == 'true' From 8a64b5e245a9f6ca93c8cffe495be3006e041e51 Mon Sep 17 00:00:00 2001 From: GrimmDevel Date: Thu, 23 Oct 2025 23:02:44 +0300 Subject: [PATCH 137/197] Fix --- .env.example | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..19c67fe5 --- /dev/null +++ b/.env.example @@ -0,0 +1,5 @@ +MAIN_VITE_API_URL= +MAIN_VITE_AUTH_URL= +MAIN_VITE_WS_URL= +RENDERER_VITE_REAL_DEBRID_REFERRAL_ID= +RENDERER_VITE_TORBOX_REFERRAL_CODE= \ No newline at end of file From 089d4179508fa05b0ec74e3a8508131851fe9eee Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 17:04:03 -0300 Subject: [PATCH 138/197] debug --- .github/workflows/update-aur.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 1e170ace..2fa762a9 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -4,6 +4,8 @@ on: workflow_dispatch: release: types: [published] + push: + branches: [main] jobs: update-aur: @@ -119,13 +121,17 @@ jobs: if: steps.check-update.outputs.update_needed == 'true' run: | cd hydra-launcher-bin - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" + pwd + ls -la + git config --global user.name "github-actions[bot]" + git config --galobal user.email "github-actions[bot]@users.noreply.github.com" - name: Commit and push changes if: steps.check-update.outputs.update_needed == 'true' run: | cd hydra-launcher-bin + pwd + ls -la git add PKGBUILD .SRCINFO echo "## Git Diff Preview" From e23ee8940cc96d95dc552b56a5f443bce9e16e4a Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 17:06:55 -0300 Subject: [PATCH 139/197] debug --- .github/workflows/update-aur.yml | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 2fa762a9..9bd8d447 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -117,21 +117,16 @@ jobs: sudo -u builder updpkgsums sudo -u builder makepkg --printsrcinfo > .SRCINFO - - name: Configure Git - if: steps.check-update.outputs.update_needed == 'true' - run: | - cd hydra-launcher-bin - pwd - ls -la - git config --global user.name "github-actions[bot]" - git config --galobal user.email "github-actions[bot]@users.noreply.github.com" - - name: Commit and push changes if: steps.check-update.outputs.update_needed == 'true' run: | cd hydra-launcher-bin pwd ls -la + + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git add PKGBUILD .SRCINFO echo "## Git Diff Preview" From 03770c03f1dcacaccce84bc34df687a88bd09d60 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 17:14:08 -0300 Subject: [PATCH 140/197] debug --- .github/workflows/update-aur.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 9bd8d447..1941ac42 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -123,7 +123,7 @@ jobs: cd hydra-launcher-bin pwd ls -la - + git config --global --add safe.directory . git config --global user.name "github-actions[bot]" git config --global user.email "github-actions[bot]@users.noreply.github.com" From ef8c6c90fbffd23b9234c9ed800bfa296dc67191 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 17:16:54 -0300 Subject: [PATCH 141/197] remove debug --- .github/workflows/update-aur.yml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 1941ac42..2a3583bc 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -4,8 +4,6 @@ on: workflow_dispatch: release: types: [published] - push: - branches: [main] jobs: update-aur: @@ -82,7 +80,6 @@ jobs: run: | CURRENT_VERSION=$(grep '^pkgver=' hydra-launcher-bin/PKGBUILD | cut -d'=' -f2) NEW_VERSION="${{ steps.get-version.outputs.version }}" - NEW_VERSION="3.7.0" echo "Current AUR version: $CURRENT_VERSION" echo "New version: $NEW_VERSION" @@ -101,7 +98,6 @@ jobs: # Update pkgver in PKGBUILD cd hydra-launcher-bin NEW_VERSION="${{ steps.get-version.outputs.version }}" - NEW_VERSION=3.7.0 echo "Updating PKGBUILD pkgver to $NEW_VERSION" @@ -121,8 +117,6 @@ jobs: if: steps.check-update.outputs.update_needed == 'true' run: | cd hydra-launcher-bin - pwd - ls -la git config --global --add safe.directory . git config --global user.name "github-actions[bot]" git config --global user.email "github-actions[bot]@users.noreply.github.com" @@ -140,10 +134,10 @@ jobs: if git diff --staged --quiet; then echo "No changes to commit" else - COMMIT_MSG="Update to ${{ steps.get-version.outputs.version }} (automated release update)" + COMMIT_MSG="v${{ steps.get-version.outputs.version }}" git commit -m "$COMMIT_MSG" - # git push origin master + git push origin master echo "Successfully updated AUR package to version ${{ steps.get-version.outputs.version }}" fi From 0a8db2a9760c8dddeb73978d1ce733f61f86f3ab Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 17:18:46 -0300 Subject: [PATCH 142/197] Fix newline at end of .env.example file --- .env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 19c67fe5..3f914eb3 100644 --- a/.env.example +++ b/.env.example @@ -2,4 +2,4 @@ MAIN_VITE_API_URL= MAIN_VITE_AUTH_URL= MAIN_VITE_WS_URL= RENDERER_VITE_REAL_DEBRID_REFERRAL_ID= -RENDERER_VITE_TORBOX_REFERRAL_CODE= \ No newline at end of file +RENDERER_VITE_TORBOX_REFERRAL_CODE= From 40f7e6e2ad210d56bddd15b2b59828358f3919fb Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 17:47:54 -0300 Subject: [PATCH 143/197] chore: bump electron version to 35 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 342b078a..59497aad 100644 --- a/package.json +++ b/package.json @@ -116,7 +116,7 @@ "@types/winreg": "^1.2.36", "@types/ws": "^8.18.1", "@vitejs/plugin-react": "^4.2.1", - "electron": "^33.4.11", + "electron": "^35.7.5", "electron-builder": "^26.0.12", "electron-vite": "^3.0.0", "eslint": "^8.56.0", From a388acf9481ac1dcdff964b7c24749d084ba6247 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 17:51:15 -0300 Subject: [PATCH 144/197] chore: update node version on gh actions --- .github/workflows/build-renderer.yml | 4 ++-- .github/workflows/build.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/release.yml | 2 +- yarn.lock | 19 +++++++++++++------ 5 files changed, 18 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build-renderer.yml b/.github/workflows/build-renderer.yml index 6aefac43..ed7a99ab 100644 --- a/.github/workflows/build-renderer.yml +++ b/.github/workflows/build-renderer.yml @@ -6,7 +6,7 @@ concurrency: on: push: - branches: main + branches: [main] jobs: build: @@ -19,7 +19,7 @@ jobs: - name: Install Node.js uses: actions/setup-node@v4 with: - node-version: 20.18.0 + node-version: 22.19.5 - name: Install dependencies run: yarn --frozen-lockfile --ignore-scripts diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5062c7ad..32688379 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,7 +22,7 @@ jobs: - name: Install Node.js uses: actions/setup-node@v4 with: - node-version: 20.18.3 + node-version: 22.19.5 - name: Install dependencies run: yarn --frozen-lockfile diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index ac359364..6d08525c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -17,7 +17,7 @@ jobs: - name: Install Node.js uses: actions/setup-node@v4 with: - node-version: 20.18.3 + node-version: 22.19.5 - name: Install dependencies run: yarn --frozen-lockfile diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3ceb42c7..a06eeb21 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,7 +23,7 @@ jobs: - name: Install Node.js uses: actions/setup-node@v4 with: - node-version: 20.18.3 + node-version: 22.19.5 - name: Install dependencies run: yarn --frozen-lockfile diff --git a/yarn.lock b/yarn.lock index 0337a77b..5ffc3f03 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3206,13 +3206,20 @@ dependencies: undici-types "~7.14.0" -"@types/node@^20.12.7", "@types/node@^20.9.0": +"@types/node@^20.12.7": version "20.19.21" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.21.tgz#6e5378e04993c40395473b13baf94a09875157b8" integrity sha512-CsGG2P3I5y48RPMfprQGfy4JPRZ6csfC3ltBZSRItG3ngggmNY/qs2uZKp4p9VbrpqNNSMzUZNFZKzgOGnd/VA== dependencies: undici-types "~6.21.0" +"@types/node@^22.7.7": + version "22.18.12" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.18.12.tgz#e165d87bc25d7bf6d3657035c914db7485de84fb" + integrity sha512-BICHQ67iqxQGFSzfCFTT7MRQ5XcBjG5aeKh5Ok38UBbPe5fxTyE+aHFxwVrGyr8GNlqFMLKD1D3P2K/1ks8tog== + dependencies: + undici-types "~6.21.0" + "@types/parse-torrent-file@*": version "4.0.6" resolved "https://registry.yarnpkg.com/@types/parse-torrent-file/-/parse-torrent-file-4.0.6.tgz#11801dfd5b0a017302a164b72c8869f2bcba15b1" @@ -4651,13 +4658,13 @@ electron-vite@^3.0.0: magic-string "^0.30.17" picocolors "^1.1.1" -electron@^33.4.11: - version "33.4.11" - resolved "https://registry.yarnpkg.com/electron/-/electron-33.4.11.tgz#225d7f106ed3edf788ced318c63858d8b8a446dc" - integrity sha512-xmdAs5QWRkInC7TpXGNvzo/7exojubk+72jn1oJL7keNeIlw7xNglf8TGtJtkR4rWC5FJq0oXiIXPS9BcK2Irg== +electron@^35.7.5: + version "35.7.5" + resolved "https://registry.yarnpkg.com/electron/-/electron-35.7.5.tgz#294a4aebb2ad2a884de730c410f2358d061e8d53" + integrity sha512-dnL+JvLraKZl7iusXTVTGYs10TKfzUi30uEDTqsmTm0guN9V2tbOjTzyIZbh9n3ygUjgEYyo+igAwMRXIi3IPw== dependencies: "@electron/get" "^2.0.0" - "@types/node" "^20.9.0" + "@types/node" "^22.7.7" extract-zip "^2.0.1" embla-carousel-autoplay@^8.6.0: From 29e822f2f110a3ae83d0f18862a37d5614112fbe Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 23 Oct 2025 17:56:45 -0300 Subject: [PATCH 145/197] fix: node version on gh actions files --- .github/workflows/build-renderer.yml | 2 +- .github/workflows/build.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/release.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-renderer.yml b/.github/workflows/build-renderer.yml index ed7a99ab..f7361883 100644 --- a/.github/workflows/build-renderer.yml +++ b/.github/workflows/build-renderer.yml @@ -19,7 +19,7 @@ jobs: - name: Install Node.js uses: actions/setup-node@v4 with: - node-version: 22.19.5 + node-version: 22.21.0 - name: Install dependencies run: yarn --frozen-lockfile --ignore-scripts diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 32688379..86fce350 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,7 +22,7 @@ jobs: - name: Install Node.js uses: actions/setup-node@v4 with: - node-version: 22.19.5 + node-version: 22.21.0 - name: Install dependencies run: yarn --frozen-lockfile diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 6d08525c..89e8b59f 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -17,7 +17,7 @@ jobs: - name: Install Node.js uses: actions/setup-node@v4 with: - node-version: 22.19.5 + node-version: 22.21.0 - name: Install dependencies run: yarn --frozen-lockfile diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a06eeb21..11df9b9f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,7 +23,7 @@ jobs: - name: Install Node.js uses: actions/setup-node@v4 with: - node-version: 22.19.5 + node-version: 22.21.0 - name: Install dependencies run: yarn --frozen-lockfile From dec0af8a80dbc19cd5cd0eaf709e7da12347f55c Mon Sep 17 00:00:00 2001 From: Kyatto <140931995+Lianela@users.noreply.github.com> Date: Thu, 23 Oct 2025 23:49:18 -0700 Subject: [PATCH 146/197] feat: translated new strings --- src/locales/es/translation.json | 92 +++++++++++++++++++++++++++++++-- 1 file changed, 88 insertions(+), 4 deletions(-) diff --git a/src/locales/es/translation.json b/src/locales/es/translation.json index de9ccfdb..c14a3df8 100644 --- a/src/locales/es/translation.json +++ b/src/locales/es/translation.json @@ -70,6 +70,24 @@ "edit_game_modal_icon_resolution": "Resolución recomendada: 256x256px", "edit_game_modal_logo_resolution": "Resolución recomendada: 640x360px", "edit_game_modal_hero_resolution": "Resolución recomendada: 1920x620px", + "cancel": "Cancelar", + "confirm": "Confirmar", + "decky_plugin_installation_error": "Error instalando plugin Decky: {{error}}", + "decky_plugin_installation_failed": "Falló instalar plugin Decky: {{error}}", + "decky_plugin_installed": "Plugin Decky v{{version}} instalanda exitosamente", + "decky_plugin_installed_version": "Plugin Decky (v{{version}})", + "edit_game_modal_drop_hero_image_here": "Soltá la imagen hero acá", + "edit_game_modal_drop_icon_image_here": "Soltá la imagen de ícono hero acá", + "edit_game_modal_drop_logo_image_here": "Soltá la imagen de logo hero acá", + "edit_game_modal_drop_to_replace_hero": "Soltá para reemplazar hero", + "edit_game_modal_drop_to_replace_icon": "Soltá para reemplazar el ícono", + "edit_game_modal_drop_to_replace_logo": "Soltá para reemplazar el logo", + "install_decky_plugin": "Instalar plugin Decky", + "install_decky_plugin_message": "Esto va a descargar e instalar el plugin de Decky Loader para Hydra. Esto quizás requierea permisos elevados, ¿querés continuar?", + "install_decky_plugin_title": "Instarlar el plugin Decky Hydra", + "update_decky_plugin": "Actualizar plugin Decky", + "update_decky_plugin_message": "Una nueva versión del plugin Decky para Hydra está disponible. ¿Querés actualizarlo ahora?", + "update_decky_plugin_title": "Actualizar plugin Decky para Hydra" "edit_game_modal_assets": "Recursos" }, "header": { @@ -285,6 +303,63 @@ "keyshop_price": "Precio de tiendas de terceros", "historical_retail": "Precio de tiendas", "historical_keyshop": "Precio de tiendas de terceros", + "add_to_favorites": "Añadir a favoritos", + "be_first_to_review": "¡Sé la primera persona en compartir lo que pensas de este juego!", + "create_shortcut_simple": "Crear atajo", + "delete_review": "Eliminar reseña", + "delete_review_modal_cancel_button": "Cancelar", + "delete_review_modal_delete_button": "Eliminar", + "delete_review_modal_description": "Esta acción no se puede deshacer.", + "delete_review_modal_title": "¿De verdad querés eliminar esta reseña?", + "failed_remove_files": "Error al eliminar los archivos", + "failed_remove_from_library": "Error al eliminar de la librería", + "failed_update_favorites": "Error al actualizar favoritos", + "files_removed_success": "Archivos eliminados correctamente", + "filter_by_source": "Filtrar por fuente", + "game_removed_from_library": "Juego eliminado de la librería", + "hide_original": "Ocultar original", + "leave_a_review": "Crear una reseña", + "load_more_reviews": "Cargar más reseñas", + "loading_more_reviews": "Cargando más reseñas...", + "loading_reviews": "Cargando reseñas...", + "maybe_later": "Tal vez después", + "no_repacks_found": "Sin fuentes encontradas para este juego", + "no_reviews_yet": "Sin reseñas aún", + "properties": "Propiedades", + "rating": "Calificación", + "rating_count": "Calificación", + "rating_negative": "Negativa", + "rating_neutral": "Neutral", + "rating_positive": "Positiva", + "rating_stats": "Calificación", + "rating_very_negative": "Muy Negativa", + "rating_very_positive": "Muy Positiva", + "remove_from_favorites": "Eliminar de favoritos", + "remove_review": "Eliminar reseña", + "review_cannot_be_empty": "El campo de la reseña no puede estar vacío.", + "review_deleted_successfully": "Reseña eliminada exitosamente.", + "review_deletion_failed": "Error al eliminar reseña. Por favor intentá de nuevo.", + "review_submission_failed": "Error al subir reseña. Por favor intentá de nuevo.", + "review_submitted_successfully": "¡Reseña eliminada exitosamente!", + "reviews": "Reseñas", + "show_less": "Ver menos", + "show_more": "Ver más", + "show_original": "Ver original", + "show_original_translated_from": "Ver original (traducido del {{language}})", + "show_translation": "Ver traducción", + "sort_highest_score": "Puntuación más alta", + "sort_lowest_score": "Puntuación más baja", + "sort_most_voted": "Más votads", + "sort_newest": "Más nuevos", + "sort_oldest": "Más viejos", + "submit_review": "Enviar", + "submitting": "Subiendo...", + "vote_failed": "Error al registrar tu voto. Por favor intentá de nuevo.", + "would_you_recommend_this_game": "¿Querés escribir una reseña para este juego?", + "write_review_placeholder": "Compartí tus pensamientos sobre este juego...", + "yes": "Si", + "you_seemed_to_enjoy_this_game": "Parece que has disfrutado de este juego", + "language": "Idioma", "caption": "Subtítulo", "audio": "Audio" @@ -345,7 +420,7 @@ "enable_real_debrid": "Habilitar Real-Debrid", "real_debrid_description": "Real-Debrid es un descargador que te permite descargar archivos más rápidos, solo límitado por la velocidad de tu internet.", "debrid_invalid_token": "Token API inválido", - "debrid_api_token_hint": "Podés obtener la el token de tu API <0>acá", + "debrid_api_token_hint": "Podés obtener el token de tu API <0>acá", "real_debrid_free_account_error": "La cuenta \"{{username}}\" es una cuenta gratis. Por favor suscribíte a Real-Debrid", "debrid_linked_message": "Cuenta \"{{username}}\" vinculada", "save_changes": "Guardar cambios", @@ -357,7 +432,7 @@ "download_count_zero": "Sin opciones de descarga", "download_count_one": "{{countFormatted}} opción de descarga", "download_count_other": "{{countFormatted}} opciones de descarga", - "download_source_url": "Descargar fuente URL", + "download_source_url": "Añadir URL de una fuente", "add_download_source_description": "Introducí la URL del archivo .json", "download_source_up_to_date": "Actualizado", "download_source_errored": "Error", @@ -409,7 +484,7 @@ "subscription_renew_cancelled": "Renovación automática desactivada", "subscription_renews_on": "Tu suscripción se renueva el {{date}}", "bill_sent_until": "Tu próxima factura se enviará este día", - "no_themes": "Parece que no tenés ningún tema aún, pero no te preocupés, presiona acá para hacer tu primera obra maestra.", + "no_themes": "Parece que no tenés ningún tema aún, pero no te preocupes, presiona acá para hacer tu primera obra maestra.", "editor_tab_code": "Código", "editor_tab_info": "Info", "editor_tab_save": "Guardar", @@ -443,7 +518,7 @@ "enable_friend_request_notifications": "Cuando recibís una solicitud de amistad", "enable_auto_install": "Descargar actualizaciones automáticamente", "common_redist": "Common redistributables", - "common_redist_description": "Common redistributables son requeridos para algunos juegos. Es recomendable instalarlos para evitar algunos problemas.", + "common_redist_description": "Los common redistributables son requeridos para algunos juegos. Es recomendable instalarlos para evitar algunos problemas.", "install_common_redist": "Instalar", "installing_common_redist": "Instalando…", "show_download_speed_in_megabytes": "Mostrar velocidad de descarga en megabytes por segundo", @@ -465,6 +540,8 @@ "hidden": "Oculto", "test_notification": "Probar notificación", "notification_preview": "Probar notificación de logro", + "debrid": "Debrid", + "debrid_description": "Los servicios Debrid son descargadores premium sin restricciones que te dejan descargar más rápido archivos alojados en servicios de alojamiento siendo que la única limitación es tu velocidad de internet.", "enable_friend_start_game_notifications": "Cuando un amigo está jugando un juego" }, "notifications": { @@ -492,6 +569,7 @@ "game_card": { "available_one": "Disponible", "available_other": "Disponibles", + "calculating": "Calculando", "no_downloads": "Sin descargas disponibles" }, "binary_not_found_modal": { @@ -593,6 +671,12 @@ "error_adding_friend": "No se pudo enviar la solicitud de amistad. Por favor revisá el código", "friend_code_length_error": "El código de amistad debe tener mínimo 8 caracteres", "game_removed_from_pinned": "Juego removido de fijados", + "amount_hours_short": "{{amount}}h", + "amount_minutes_short": "{{amount}}m", + "karma": "Karma", + "karma_count": "karma", + "karma_description": "Conseguido por me gustas positivos en reseñas", + "sort_by": "Filtrar por:", "game_added_to_pinned": "Juego añadido a fijados" }, "achievement": { From 362774a3ccb46772c900a454b56e67b2d4551865 Mon Sep 17 00:00:00 2001 From: Kyatto <140931995+Lianela@users.noreply.github.com> Date: Thu, 23 Oct 2025 23:53:16 -0700 Subject: [PATCH 147/197] fix: comma --- src/locales/es/translation.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/locales/es/translation.json b/src/locales/es/translation.json index c14a3df8..a69ac127 100644 --- a/src/locales/es/translation.json +++ b/src/locales/es/translation.json @@ -87,7 +87,7 @@ "install_decky_plugin_title": "Instarlar el plugin Decky Hydra", "update_decky_plugin": "Actualizar plugin Decky", "update_decky_plugin_message": "Una nueva versión del plugin Decky para Hydra está disponible. ¿Querés actualizarlo ahora?", - "update_decky_plugin_title": "Actualizar plugin Decky para Hydra" + "update_decky_plugin_title": "Actualizar plugin Decky para Hydra", "edit_game_modal_assets": "Recursos" }, "header": { From 2c1a8bf639a36b74080f59c150f5fdb69bc46172 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Fri, 24 Oct 2025 06:42:39 -0300 Subject: [PATCH 148/197] Remove extra newline in Spanish translation file --- src/locales/es/translation.json | 1 - 1 file changed, 1 deletion(-) diff --git a/src/locales/es/translation.json b/src/locales/es/translation.json index a69ac127..dfa7f7a1 100644 --- a/src/locales/es/translation.json +++ b/src/locales/es/translation.json @@ -359,7 +359,6 @@ "write_review_placeholder": "Compartí tus pensamientos sobre este juego...", "yes": "Si", "you_seemed_to_enjoy_this_game": "Parece que has disfrutado de este juego", - "language": "Idioma", "caption": "Subtítulo", "audio": "Audio" From 881564daa73b0f17b8b36a339e21ba5d859c47b8 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Fri, 24 Oct 2025 17:13:36 +0100 Subject: [PATCH 149/197] fix: fixing game hero --- src/main/services/hydra-api.ts | 2 +- src/main/services/python-rpc.ts | 2 +- .../description-header.scss | 1 - .../gallery-slider/gallery-slider.scss | 2 +- .../game-details/game-details-content.tsx | 16 +- .../src/pages/game-details/game-details.scss | 112 ++----------- .../src/pages/game-details/game-reviews.tsx | 150 +++++++++--------- .../pages/game-details/hero/hero-panel.scss | 1 + 8 files changed, 92 insertions(+), 194 deletions(-) diff --git a/src/main/services/hydra-api.ts b/src/main/services/hydra-api.ts index dd26e6f0..07f81d68 100644 --- a/src/main/services/hydra-api.ts +++ b/src/main/services/hydra-api.ts @@ -29,7 +29,7 @@ export class HydraApi { private static instance: AxiosInstance; private static readonly EXPIRATION_OFFSET_IN_MS = 1000 * 60 * 5; // 5 minutes - private static readonly ADD_LOG_INTERCEPTOR = true; + private static readonly ADD_LOG_INTERCEPTOR = false; private static secondsToMilliseconds(seconds: number) { return seconds * 1000; diff --git a/src/main/services/python-rpc.ts b/src/main/services/python-rpc.ts index f3ce9f6c..2a1dce79 100644 --- a/src/main/services/python-rpc.ts +++ b/src/main/services/python-rpc.ts @@ -106,7 +106,7 @@ export class PythonRPC { "main.py" ); - const childProcess = cp.spawn("python3", [scriptPath, ...commonArgs], { + const childProcess = cp.spawn("python", [scriptPath, ...commonArgs], { stdio: ["inherit", "inherit"], }); diff --git a/src/renderer/src/pages/game-details/description-header/description-header.scss b/src/renderer/src/pages/game-details/description-header/description-header.scss index a29caa34..74126fd5 100644 --- a/src/renderer/src/pages/game-details/description-header/description-header.scss +++ b/src/renderer/src/pages/game-details/description-header/description-header.scss @@ -11,7 +11,6 @@ border-radius: 8px; border: 1px solid rgba(255, 255, 255, 0.05); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); - margin-bottom: calc(globals.$spacing-unit * 1.5); &__info { display: flex; diff --git a/src/renderer/src/pages/game-details/gallery-slider/gallery-slider.scss b/src/renderer/src/pages/game-details/gallery-slider/gallery-slider.scss index 6f9e753c..f9da431d 100644 --- a/src/renderer/src/pages/game-details/gallery-slider/gallery-slider.scss +++ b/src/renderer/src/pages/game-details/gallery-slider/gallery-slider.scss @@ -2,7 +2,7 @@ .gallery-slider { &__container { - padding: calc(globals.$spacing-unit * 1.5) calc(globals.$spacing-unit * 1); + padding: calc(globals.$spacing-unit * 1.5) 0; width: 100%; display: flex; flex-direction: column; diff --git a/src/renderer/src/pages/game-details/game-details-content.tsx b/src/renderer/src/pages/game-details/game-details-content.tsx index edf314c7..8f2d840c 100644 --- a/src/renderer/src/pages/game-details/game-details-content.tsx +++ b/src/renderer/src/pages/game-details/game-details-content.tsx @@ -53,8 +53,6 @@ const getImageWithCustomPriority = ( }; export function GameDetailsContent() { - const heroRef = useRef(null); - const { t } = useTranslation("game_details"); const { @@ -152,18 +150,12 @@ export function GameDetailsContent() { className={`game-details__wrapper ${hasNSFWContentBlocked ? "game-details__wrapper--blurred" : ""}`} >
    -
    +
    {game?.title} -
    + +
    + +
    - -
    diff --git a/src/renderer/src/pages/game-details/game-details.scss b/src/renderer/src/pages/game-details/game-details.scss index 56022b07..313896fc 100644 --- a/src/renderer/src/pages/game-details/game-details.scss +++ b/src/renderer/src/pages/game-details/game-details.scss @@ -1,6 +1,6 @@ @use "../../scss/globals.scss"; -$hero-height: 300px; +$hero-height: 350px; @keyframes slide-in { 0% { @@ -27,6 +27,10 @@ $hero-height: 300px; } } + &__hero-panel { + padding: globals.$spacing-unit; + } + &__review-form { display: flex; flex-direction: column; @@ -34,19 +38,6 @@ $hero-height: 300px; margin-bottom: 24px; } - &__review-form-controls { - display: flex; - gap: calc(globals.$spacing-unit * 2); - align-items: flex-end; - flex-wrap: wrap; - - @media (max-width: 768px) { - flex-direction: column; - align-items: stretch; - gap: calc(globals.$spacing-unit * 1.5); - } - } - &__review-form-bottom { display: flex; justify-content: space-between; @@ -55,39 +46,12 @@ $hero-height: 300px; flex-wrap: wrap; } - &__review-message { - padding: calc(globals.$spacing-unit * 1); - border-radius: 4px; - font-size: globals.$small-font-size; - font-weight: 500; - margin-top: calc(globals.$spacing-unit * 1); - border: 1px solid; - - &--success { - background: rgba(34, 197, 94, 0.1); - color: #86efac; - border-color: rgba(34, 197, 94, 0.3); - } - - &--error { - background: rgba(239, 68, 68, 0.1); - color: #fca5a5; - border-color: rgba(239, 68, 68, 0.3); - } - } - &__review-score-container { display: flex; align-items: center; gap: 4px; } - &__review-score-label { - font-size: 14px; - color: #ffffff; - font-weight: 500; - } - &__review-score-select { background-color: #2a2a2a; border: 1px solid #3a3a3a; @@ -220,10 +184,6 @@ $hero-height: 300px; } } - &__reviews-list { - margin-top: calc(globals.$spacing-unit * 3); - } - &__reviews-container { display: flex; flex-direction: column; @@ -578,8 +538,8 @@ $hero-height: 300px; &__hero-image { width: 100%; - height: calc($hero-height + 72px); - min-height: calc($hero-height + 72px); + height: $hero-height; + min-height: $hero-height; object-fit: cover; object-position: top; transition: all ease 0.2s; @@ -588,8 +548,8 @@ $hero-height: 300px; @media (min-width: 1250px) { object-position: center; - height: calc(350px + 72px); - min-height: calc(350px + 72px); + height: $hero-height; + min-height: $hero-height; } } @@ -630,14 +590,6 @@ $hero-height: 300px; } } - &__hero-image-skeleton { - height: 300px; - - @media (min-width: 1250px) { - height: 350px; - } - } - &__container { width: 100%; height: 100%; @@ -782,34 +734,6 @@ $hero-height: 300px; } } - &__randomizer-button { - animation: slide-in 0.2s; - position: fixed; - bottom: calc(globals.$spacing-unit * 3); - right: calc(9px + globals.$spacing-unit * 2); - box-shadow: rgba(255, 255, 255, 0.1) 0px 0px 10px 1px; - border: solid 2px globals.$border-color; - z-index: 1; - background-color: globals.$background-color; - - &:hover { - background-color: globals.$background-color; - box-shadow: rgba(255, 255, 255, 0.1) 0px 0px 15px 5px; - opacity: 1; - } - - &:active { - transform: scale(0.98); - } - - &:disabled { - box-shadow: none; - transform: none; - opacity: 0.8; - background-color: globals.$background-color; - } - } - &__hero-panel-skeleton { width: 100%; padding: calc(globals.$spacing-unit * 2); @@ -852,19 +776,6 @@ $hero-height: 300px; } } - &__stars-icon-container { - width: 16px; - height: 16px; - position: relative; - } - - &__stars-icon { - width: 70px; - position: absolute; - top: -28px; - left: -27px; - } - &__cloud-icon-container { width: 20px; height: 16px; @@ -880,11 +791,6 @@ $hero-height: 300px; top: -3px; } - &__hero-backdrop { - flex: 1; - transition: opacity 0.2s ease; - } - &__reviews-section { margin-top: calc(globals.$spacing-unit * 3); padding-top: calc(globals.$spacing-unit * 3); diff --git a/src/renderer/src/pages/game-details/game-reviews.tsx b/src/renderer/src/pages/game-details/game-reviews.tsx index f8117f43..02901982 100644 --- a/src/renderer/src/pages/game-details/game-reviews.tsx +++ b/src/renderer/src/pages/game-details/game-reviews.tsx @@ -469,84 +469,82 @@ export function GameReviews({ )} -
    -
    -
    -

    {t("reviews")}

    - - {totalReviewCount} - -
    +
    +
    +

    {t("reviews")}

    + + {totalReviewCount} +
    - - - {reviewsLoading && reviews.length === 0 && ( -
    - {t("loading_reviews")} -
    - )} - - {!reviewsLoading && reviews.length === 0 && ( -
    -
    - -
    -

    - {t("no_reviews_yet")} -

    -

    - {t("be_first_to_review")} -

    -
    - )} - -
    0 ? 0.5 : 1, - transition: "opacity 0.2s ease", - }} - > - {reviews.map((review) => ( - - ))} -
    - - {hasMoreReviews && !reviewsLoading && ( - - )} - - {reviewsLoading && reviews.length > 0 && ( -
    - {t("loading_more_reviews")} -
    - )}
    + + + {reviewsLoading && reviews.length === 0 && ( +
    + {t("loading_reviews")} +
    + )} + + {!reviewsLoading && reviews.length === 0 && ( +
    +
    + +
    +

    + {t("no_reviews_yet")} +

    +

    + {t("be_first_to_review")} +

    +
    + )} + +
    0 ? 0.5 : 1, + transition: "opacity 0.2s ease", + }} + > + {reviews.map((review) => ( + + ))} +
    + + {hasMoreReviews && !reviewsLoading && ( + + )} + + {reviewsLoading && reviews.length > 0 && ( +
    + {t("loading_more_reviews")} +
    + )}
    ); } diff --git a/src/renderer/src/pages/game-details/hero/hero-panel.scss b/src/renderer/src/pages/game-details/hero/hero-panel.scss index 4dd1cc22..4ff74fbf 100644 --- a/src/renderer/src/pages/game-details/hero/hero-panel.scss +++ b/src/renderer/src/pages/game-details/hero/hero-panel.scss @@ -18,6 +18,7 @@ top: 0; z-index: 2; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + border-radius: 8px; &--stuck { background: rgba(0, 0, 0, 0.7); From 0c7767de362c5f55df139b08a9650e9d4984b565 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Fri, 24 Oct 2025 18:22:46 +0100 Subject: [PATCH 150/197] fix: remove unused useRef import --- src/renderer/src/pages/game-details/game-details-content.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/src/pages/game-details/game-details-content.tsx b/src/renderer/src/pages/game-details/game-details-content.tsx index 1ec17d8a..ab51a212 100644 --- a/src/renderer/src/pages/game-details/game-details-content.tsx +++ b/src/renderer/src/pages/game-details/game-details-content.tsx @@ -1,4 +1,4 @@ -import { useContext, useEffect, useMemo, useRef, useState } from "react"; +import { useContext, useEffect, useMemo, useState } from "react"; import { PencilIcon } from "@primer/octicons-react"; import { useTranslation } from "react-i18next"; From 11c19f5fe5bd49e7fb5560f65e383e98fb3000fa Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Fri, 24 Oct 2025 20:20:51 -0300 Subject: [PATCH 151/197] chore: downgrade to latest of 34 --- package.json | 2 +- src/renderer/src/pages/game-details/game-reviews.tsx | 4 ---- yarn.lock | 2 +- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 59497aad..f74825a1 100644 --- a/package.json +++ b/package.json @@ -116,7 +116,7 @@ "@types/winreg": "^1.2.36", "@types/ws": "^8.18.1", "@vitejs/plugin-react": "^4.2.1", - "electron": "^35.7.5", + "electron": "^34.5.8", "electron-builder": "^26.0.12", "electron-vite": "^3.0.0", "eslint": "^8.56.0", diff --git a/src/renderer/src/pages/game-details/game-reviews.tsx b/src/renderer/src/pages/game-details/game-reviews.tsx index f8117f43..1ce44550 100644 --- a/src/renderer/src/pages/game-details/game-reviews.tsx +++ b/src/renderer/src/pages/game-details/game-reviews.tsx @@ -144,8 +144,6 @@ export function GameReviews({ } }, [objectId, userDetailsId, shop, game, onUserReviewedChange]); - console.log("reviews", reviews); - const loadReviews = useCallback( async (reset = false) => { if (!objectId) return; @@ -440,8 +438,6 @@ export function GameReviews({ }); }, [reviews]); - console.log("reviews", reviews); - return (
    {showReviewPrompt && diff --git a/yarn.lock b/yarn.lock index 5ffc3f03..d936ff61 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4658,7 +4658,7 @@ electron-vite@^3.0.0: magic-string "^0.30.17" picocolors "^1.1.1" -electron@^35.7.5: +electron@^35.2.1: version "35.7.5" resolved "https://registry.yarnpkg.com/electron/-/electron-35.7.5.tgz#294a4aebb2ad2a884de730c410f2358d061e8d53" integrity sha512-dnL+JvLraKZl7iusXTVTGYs10TKfzUi30uEDTqsmTm0guN9V2tbOjTzyIZbh9n3ygUjgEYyo+igAwMRXIi3IPw== From 4471bf0f8bc9ee7e27a48dbfc2c9a3be3299f6a7 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Fri, 24 Oct 2025 21:05:40 -0300 Subject: [PATCH 152/197] chore: bump to electron 37 --- package.json | 4 ++-- yarn.lock | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index f74825a1..b34425a0 100644 --- a/package.json +++ b/package.json @@ -116,9 +116,9 @@ "@types/winreg": "^1.2.36", "@types/ws": "^8.18.1", "@vitejs/plugin-react": "^4.2.1", - "electron": "^34.5.8", + "electron": "^37.7.1", "electron-builder": "^26.0.12", - "electron-vite": "^3.0.0", + "electron-vite": "^3.1.0", "eslint": "^8.56.0", "eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-react": "^7.37.4", diff --git a/yarn.lock b/yarn.lock index d936ff61..c362ada8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4646,7 +4646,7 @@ electron-updater@^6.6.2: semver "^7.6.3" tiny-typed-emitter "^2.1.0" -electron-vite@^3.0.0: +electron-vite@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/electron-vite/-/electron-vite-3.1.0.tgz#1784907a83d23c6c8093ec68b8e414a74d814385" integrity sha512-M7aAzaRvSl5VO+6KN4neJCYLHLpF/iWo5ztchI/+wMxIieDZQqpbCYfaEHHHPH6eupEzfvZdLYdPdmvGqoVe0Q== @@ -4658,10 +4658,10 @@ electron-vite@^3.0.0: magic-string "^0.30.17" picocolors "^1.1.1" -electron@^35.2.1: - version "35.7.5" - resolved "https://registry.yarnpkg.com/electron/-/electron-35.7.5.tgz#294a4aebb2ad2a884de730c410f2358d061e8d53" - integrity sha512-dnL+JvLraKZl7iusXTVTGYs10TKfzUi30uEDTqsmTm0guN9V2tbOjTzyIZbh9n3ygUjgEYyo+igAwMRXIi3IPw== +electron@^37.7.1: + version "37.7.1" + resolved "https://registry.yarnpkg.com/electron/-/electron-37.7.1.tgz#7d771b3d3365b5458f8bc758385defee14387034" + integrity sha512-2EmIqWv4T8BtgFQosB3/0Fezs09X3l0wXhIzes/cNt/GI+UDljbQr3NiF2J9WnqP0aFSbUEfztGUQMiX+qDvsw== dependencies: "@electron/get" "^2.0.0" "@types/node" "^22.7.7" From ee35bc24b27c05458f888cbf2e477bdb80f7c1df Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Fri, 24 Oct 2025 21:11:56 -0300 Subject: [PATCH 153/197] chore: undo remove hydra api logs --- src/main/services/hydra-api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/services/hydra-api.ts b/src/main/services/hydra-api.ts index 07f81d68..dd26e6f0 100644 --- a/src/main/services/hydra-api.ts +++ b/src/main/services/hydra-api.ts @@ -29,7 +29,7 @@ export class HydraApi { private static instance: AxiosInstance; private static readonly EXPIRATION_OFFSET_IN_MS = 1000 * 60 * 5; // 5 minutes - private static readonly ADD_LOG_INTERCEPTOR = false; + private static readonly ADD_LOG_INTERCEPTOR = true; private static secondsToMilliseconds(seconds: number) { return seconds * 1000; From 7f2343413efc80cb9ba894b77162b96ee1aa34dc Mon Sep 17 00:00:00 2001 From: Moyasee Date: Sun, 26 Oct 2025 17:26:25 +0200 Subject: [PATCH 154/197] feat: added manual page selection and changed functionality of pagination --- .../src/pages/catalogue/pagination.scss | 29 +++++ .../src/pages/catalogue/pagination.tsx | 112 ++++++++++++++---- 2 files changed, 121 insertions(+), 20 deletions(-) diff --git a/src/renderer/src/pages/catalogue/pagination.scss b/src/renderer/src/pages/catalogue/pagination.scss index 141dfe54..cac10211 100644 --- a/src/renderer/src/pages/catalogue/pagination.scss +++ b/src/renderer/src/pages/catalogue/pagination.scss @@ -1,3 +1,5 @@ +@use "../../scss/globals.scss"; + .pagination { display: flex; gap: 4px; @@ -18,4 +20,31 @@ font-size: 16px; } } + + &__page-input { + box-sizing: border-box; + width: 40px; + min-width: 40px; + max-width: 40px; + min-height: 40px; + border-radius: 8px; + border: solid 1px globals.$border-color; + background-color: transparent; + color: globals.$muted-color; + text-align: center; + font-size: 12px; + padding: 0 6px; + outline: none; + } + + &__double-chevron { + display: flex; + align-items: center; + justify-content: center; + font-size: 0; // remove whitespace node width between SVGs + } + + &__double-chevron > svg + svg { + margin-left: -8px; // pull the second chevron closer + } } diff --git a/src/renderer/src/pages/catalogue/pagination.tsx b/src/renderer/src/pages/catalogue/pagination.tsx index dfae6164..4040c4b5 100644 --- a/src/renderer/src/pages/catalogue/pagination.tsx +++ b/src/renderer/src/pages/catalogue/pagination.tsx @@ -1,6 +1,7 @@ import { Button } from "@renderer/components/button/button"; import { ChevronLeftIcon, ChevronRightIcon } from "@primer/octicons-react"; import { useFormat } from "@renderer/hooks/use-format"; +import { useEffect, useRef, useState } from "react"; import "./pagination.scss"; interface PaginationProps { @@ -16,6 +17,17 @@ export function Pagination({ }: PaginationProps) { const { formatNumber } = useFormat(); + const [isJumpOpen, setIsJumpOpen] = useState(false); + const [jumpValue, setJumpValue] = useState(""); + const jumpInputRef = useRef(null); + + useEffect(() => { + if (isJumpOpen) { + setJumpValue(""); + setTimeout(() => jumpInputRef.current?.focus(), 0); + } + }, [isJumpOpen, page]); + if (totalPages <= 1) return null; const visiblePages = 3; @@ -30,6 +42,19 @@ export function Pagination({ return (
    + {startPage > 1 && ( + + )} + - {page > 2 && ( - <> - - -
    - ... -
    - - )} - {Array.from( { length: endPage - startPage + 1 }, (_, i) => startPage + i @@ -72,9 +80,60 @@ export function Pagination({ {page < totalPages - 1 && ( <> -
    - ... -
    + {isJumpOpen ? ( + { + const val = e.target.value; + if (val === "") { + setJumpValue(""); + return; + } + const num = Number(val); + if (Number.isNaN(num)) { + return; + } + if (num < 1) { + setJumpValue("1"); + return; + } + if (num > totalPages) { + setJumpValue(String(totalPages)); + return; + } + setJumpValue(val); + }} + onKeyDown={(e) => { + if (e.key === "Enter") { + if (jumpValue.trim() === "") return; + const parsed = Number(jumpValue); + if (Number.isNaN(parsed)) return; + const target = Math.max(1, Math.min(totalPages, parsed)); + onPageChange(target); + setIsJumpOpen(false); + } else if (e.key === "Escape") { + setIsJumpOpen(false); + } + }} + onBlur={() => { + setIsJumpOpen(false); + }} + aria-label="Go to page" + /> + ) : ( + + )} + + {endPage < totalPages && ( + + )}
    ); } From cb3e52de34be44bf731aa2f5a04b477d7454d5c8 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Sun, 26 Oct 2025 19:37:57 +0200 Subject: [PATCH 155/197] fix: go to page button did not appear correctly for the last pages --- .../src/pages/catalogue/pagination.tsx | 74 ++++++++++++++++++- 1 file changed, 72 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/pages/catalogue/pagination.tsx b/src/renderer/src/pages/catalogue/pagination.tsx index 4040c4b5..eaaa97a8 100644 --- a/src/renderer/src/pages/catalogue/pagination.tsx +++ b/src/renderer/src/pages/catalogue/pagination.tsx @@ -31,11 +31,15 @@ export function Pagination({ if (totalPages <= 1) return null; const visiblePages = 3; + const isLastThree = totalPages > 3 && page >= totalPages - 2; let startPage = Math.max(1, page - 1); let endPage = startPage + visiblePages - 1; - if (endPage > totalPages) { + if (isLastThree) { + startPage = Math.max(1, totalPages - 2); + endPage = totalPages; + } else if (endPage > totalPages) { endPage = totalPages; startPage = Math.max(1, endPage - visiblePages + 1); } @@ -64,6 +68,72 @@ export function Pagination({ + {isLastThree && startPage > 1 && ( + <> + + {isJumpOpen ? ( + { + const val = e.target.value; + if (val === "") { + setJumpValue(""); + return; + } + const num = Number(val); + if (Number.isNaN(num)) { + return; + } + if (num < 1) { + setJumpValue("1"); + return; + } + if (num > totalPages) { + setJumpValue(String(totalPages)); + return; + } + setJumpValue(val); + }} + onKeyDown={(e) => { + if (e.key === "Enter") { + if (jumpValue.trim() === "") return; + const parsed = Number(jumpValue); + if (Number.isNaN(parsed)) return; + const target = Math.max(1, Math.min(totalPages, parsed)); + onPageChange(target); + setIsJumpOpen(false); + } else if (e.key === "Escape") { + setIsJumpOpen(false); + } + }} + onBlur={() => { + setIsJumpOpen(false); + }} + aria-label="Go to page" + /> + ) : ( + + )} + + )} + {Array.from( { length: endPage - startPage + 1 }, (_, i) => startPage + i @@ -78,7 +148,7 @@ export function Pagination({ ))} - {page < totalPages - 1 && ( + {!isLastThree && page < totalPages - 1 && ( <> {isJumpOpen ? ( Date: Sun, 26 Oct 2025 19:49:15 +0200 Subject: [PATCH 156/197] fix: duplications --- .../src/pages/catalogue/pagination.tsx | 171 +++++++----------- 1 file changed, 63 insertions(+), 108 deletions(-) diff --git a/src/renderer/src/pages/catalogue/pagination.tsx b/src/renderer/src/pages/catalogue/pagination.tsx index eaaa97a8..1ba02d06 100644 --- a/src/renderer/src/pages/catalogue/pagination.tsx +++ b/src/renderer/src/pages/catalogue/pagination.tsx @@ -2,6 +2,7 @@ import { Button } from "@renderer/components/button/button"; import { ChevronLeftIcon, ChevronRightIcon } from "@primer/octicons-react"; import { useFormat } from "@renderer/hooks/use-format"; import { useEffect, useRef, useState } from "react"; +import type { ChangeEvent, KeyboardEvent } from "react"; import "./pagination.scss"; interface PaginationProps { @@ -44,6 +45,66 @@ export function Pagination({ startPage = Math.max(1, endPage - visiblePages + 1); } + const onJumpChange = (e: ChangeEvent) => { + const val = e.target.value; + if (val === "") { + setJumpValue(""); + return; + } + const num = Number(val); + if (Number.isNaN(num)) { + return; + } + if (num < 1) { + setJumpValue("1"); + return; + } + if (num > totalPages) { + setJumpValue(String(totalPages)); + return; + } + setJumpValue(val); + }; + + const onJumpKeyDown = (e: KeyboardEvent) => { + if (e.key === "Enter") { + if (jumpValue.trim() === "") return; + const parsed = Number(jumpValue); + if (Number.isNaN(parsed)) return; + const target = Math.max(1, Math.min(totalPages, parsed)); + onPageChange(target); + setIsJumpOpen(false); + } else if (e.key === "Escape") { + setIsJumpOpen(false); + } + }; + + const JumpControl = () => + isJumpOpen ? ( + { + setIsJumpOpen(false); + }} + aria-label="Go to page" + /> + ) : ( + + ); + return (
    {startPage > 1 && ( @@ -77,60 +138,7 @@ export function Pagination({ > {formatNumber(1)} - {isJumpOpen ? ( - { - const val = e.target.value; - if (val === "") { - setJumpValue(""); - return; - } - const num = Number(val); - if (Number.isNaN(num)) { - return; - } - if (num < 1) { - setJumpValue("1"); - return; - } - if (num > totalPages) { - setJumpValue(String(totalPages)); - return; - } - setJumpValue(val); - }} - onKeyDown={(e) => { - if (e.key === "Enter") { - if (jumpValue.trim() === "") return; - const parsed = Number(jumpValue); - if (Number.isNaN(parsed)) return; - const target = Math.max(1, Math.min(totalPages, parsed)); - onPageChange(target); - setIsJumpOpen(false); - } else if (e.key === "Escape") { - setIsJumpOpen(false); - } - }} - onBlur={() => { - setIsJumpOpen(false); - }} - aria-label="Go to page" - /> - ) : ( - - )} + )} @@ -150,60 +158,7 @@ export function Pagination({ {!isLastThree && page < totalPages - 1 && ( <> - {isJumpOpen ? ( - { - const val = e.target.value; - if (val === "") { - setJumpValue(""); - return; - } - const num = Number(val); - if (Number.isNaN(num)) { - return; - } - if (num < 1) { - setJumpValue("1"); - return; - } - if (num > totalPages) { - setJumpValue(String(totalPages)); - return; - } - setJumpValue(val); - }} - onKeyDown={(e) => { - if (e.key === "Enter") { - if (jumpValue.trim() === "") return; - const parsed = Number(jumpValue); - if (Number.isNaN(parsed)) return; - const target = Math.max(1, Math.min(totalPages, parsed)); - onPageChange(target); - setIsJumpOpen(false); - } else if (e.key === "Escape") { - setIsJumpOpen(false); - } - }} - onBlur={() => { - setIsJumpOpen(false); - }} - aria-label="Go to page" - /> - ) : ( - - )} + + ); +} + interface PaginationProps { page: number; totalPages: number; @@ -79,32 +120,6 @@ export function Pagination({ } }; - const JumpControl = () => - isJumpOpen ? ( - { - setIsJumpOpen(false); - }} - aria-label="Go to page" - /> - ) : ( - - ); - return (
    {startPage > 1 && ( @@ -138,7 +153,16 @@ export function Pagination({ > {formatNumber(1)} - + setIsJumpOpen(true)} + onClose={() => setIsJumpOpen(false)} + onChange={onJumpChange} + onKeyDown={onJumpKeyDown} + /> )} @@ -158,7 +182,16 @@ export function Pagination({ {!isLastThree && page < totalPages - 1 && ( <> - + setIsJumpOpen(true)} + onClose={() => setIsJumpOpen(false)} + onChange={onJumpChange} + onKeyDown={onJumpKeyDown} + />
    -

    Hydra Cloud

    +

    {t("hydra_cloud")}

    {getHydraCloudSectionContent().description}
    diff --git a/src/renderer/src/pages/settings/settings-download-sources.scss b/src/renderer/src/pages/settings/settings-download-sources.scss index a12bdff3..df0f5c8b 100644 --- a/src/renderer/src/pages/settings/settings-download-sources.scss +++ b/src/renderer/src/pages/settings/settings-download-sources.scss @@ -1,5 +1,14 @@ @use "../../scss/globals.scss"; +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + .settings-download-sources { &__list { padding: 0; @@ -22,6 +31,17 @@ &--syncing { opacity: globals.$disabled-opacity; } + + &--pending { + opacity: 0.6; + } + } + + &__spinner { + animation: spin 1s linear infinite; + margin-right: calc(globals.$spacing-unit / 2); + width: 12px; + height: 12px; } &__item-header { diff --git a/src/renderer/src/pages/settings/settings-download-sources.tsx b/src/renderer/src/pages/settings/settings-download-sources.tsx index 08670145..75f0cc73 100644 --- a/src/renderer/src/pages/settings/settings-download-sources.tsx +++ b/src/renderer/src/pages/settings/settings-download-sources.tsx @@ -22,6 +22,7 @@ import { settingsContext } from "@renderer/context"; import { useNavigate } from "react-router-dom"; import { setFilters, clearFilters } from "@renderer/features"; import "./settings-download-sources.scss"; +import { logger } from "@renderer/logger"; export function SettingsDownloadSources() { const [ @@ -58,6 +59,30 @@ export function SettingsDownloadSources() { fetchDownloadSources(); }, []); + useEffect(() => { + const hasPendingOrMatchingSource = downloadSources.some( + (source) => + source.status === DownloadSourceStatus.PendingMatching || + source.status === DownloadSourceStatus.Matching + ); + + if (!hasPendingOrMatchingSource || !downloadSources.length) { + return; + } + + const intervalId = setInterval(async () => { + try { + await window.electron.syncDownloadSources(); + const sources = await window.electron.getDownloadSources(); + setDownloadSources(sources); + } catch (error) { + logger.error("Failed to fetch download sources:", error); + } + }, 5000); + + return () => clearInterval(intervalId); + }, [downloadSources]); + const handleRemoveSource = async (downloadSource: DownloadSource) => { setIsRemovingDownloadSource(true); @@ -67,7 +92,7 @@ export function SettingsDownloadSources() { setDownloadSources(sources as DownloadSource[]); showSuccessToast(t("removed_download_source")); } catch (error) { - console.error("Failed to remove download source:", error); + logger.error("Failed to remove download source:", error); } finally { setIsRemovingDownloadSource(false); } @@ -82,7 +107,7 @@ export function SettingsDownloadSources() { setDownloadSources(sources as DownloadSource[]); showSuccessToast(t("removed_all_download_sources")); } catch (error) { - console.error("Failed to remove all download sources:", error); + logger.error("Failed to remove all download sources:", error); } finally { setIsRemovingDownloadSource(false); setShowConfirmationDeleteAllSourcesModal(false); @@ -94,7 +119,7 @@ export function SettingsDownloadSources() { const sources = await window.electron.getDownloadSources(); setDownloadSources(sources as DownloadSource[]); } catch (error) { - console.error("Failed to refresh download sources:", error); + logger.error("Failed to refresh download sources:", error); } }; @@ -127,7 +152,7 @@ export function SettingsDownloadSources() { const navigateToCatalogue = (fingerprint?: string) => { if (!fingerprint) { - console.error("Cannot navigate: fingerprint is undefined"); + logger.error("Cannot navigate: fingerprint is undefined"); return; } @@ -202,16 +227,25 @@ export function SettingsDownloadSources() {
      {downloadSources.map((downloadSource) => { + const isPendingOrMatching = + downloadSource.status === DownloadSourceStatus.PendingMatching || + downloadSource.status === DownloadSourceStatus.Matching; + return (
    • {downloadSource.name}

      - {statusTitle[downloadSource.status]} + + {isPendingOrMatching && ( + + )} + {statusTitle[downloadSource.status]} +
      diff --git a/src/renderer/src/pages/settings/settings-real-debrid.tsx b/src/renderer/src/pages/settings/settings-real-debrid.tsx index 42ba6ad9..db3a29a3 100644 --- a/src/renderer/src/pages/settings/settings-real-debrid.tsx +++ b/src/renderer/src/pages/settings/settings-real-debrid.tsx @@ -133,7 +133,7 @@ export function SettingsRealDebrid() { {t("save_changes")} } - placeholder="API Token" + placeholder={t("api_token")} hint={ diff --git a/src/renderer/src/pages/settings/settings-torbox.tsx b/src/renderer/src/pages/settings/settings-torbox.tsx index 610dc942..46c8e2f9 100644 --- a/src/renderer/src/pages/settings/settings-torbox.tsx +++ b/src/renderer/src/pages/settings/settings-torbox.tsx @@ -116,7 +116,7 @@ export function SettingsTorBox() { onChange={(event) => setForm({ ...form, torBoxApiToken: event.target.value }) } - placeholder="API Token" + placeholder={t("api_token")} rightContent={ )} - {game?.shop !== "custom" && shop && objectId && ( + {shop !== "custom" && shop && objectId && ( - {game?.shop !== "custom" && } + {shop !== "custom" && }
    diff --git a/src/types/game.types.ts b/src/types/game.types.ts index ed8fb852..35d537a8 100644 --- a/src/types/game.types.ts +++ b/src/types/game.types.ts @@ -1,4 +1,4 @@ -export type GameShop = "steam" | "epic" | "custom"; +export type GameShop = "steam" | "custom"; export type ShortcutLocation = "desktop" | "start_menu"; From ad588b5600a1172c4f7e67d1c2a8f78f6b0404ce Mon Sep 17 00:00:00 2001 From: Moyasee Date: Wed, 29 Oct 2025 19:51:09 +0200 Subject: [PATCH 190/197] fix: images with big height breaking layout --- src/renderer/src/pages/game-details/hero.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/renderer/src/pages/game-details/hero.scss b/src/renderer/src/pages/game-details/hero.scss index 6bd63320..41264fe4 100644 --- a/src/renderer/src/pages/game-details/hero.scss +++ b/src/renderer/src/pages/game-details/hero.scss @@ -146,6 +146,8 @@ $hero-height: 350px; &__game-logo { width: 200px; align-self: flex-end; + object-fit: contain; + object-position: left bottom; @media (min-width: 768px) { width: 250px; @@ -153,6 +155,7 @@ $hero-height: 350px; @media (min-width: 1024px) { width: 300px; + max-height: 150px; } } From 499a830e3ec1bd50124f97c515be01363ecf770e Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Wed, 29 Oct 2025 18:23:06 +0000 Subject: [PATCH 191/197] chore: sync with main --- .github/workflows/release.yml | 1 - .../src/components/text-field/text-field.tsx | 16 ++++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7408665f..df01b358 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,7 +7,6 @@ concurrency: on: push: branches: - - main - release/** jobs: diff --git a/src/renderer/src/components/text-field/text-field.tsx b/src/renderer/src/components/text-field/text-field.tsx index 7c0cbb58..76759126 100644 --- a/src/renderer/src/components/text-field/text-field.tsx +++ b/src/renderer/src/components/text-field/text-field.tsx @@ -4,10 +4,11 @@ import { useTranslation } from "react-i18next"; import cn from "classnames"; import "./text-field.scss"; -export interface TextFieldProps extends React.DetailedHTMLProps< - React.InputHTMLAttributes, - HTMLInputElement -> { +export interface TextFieldProps + extends React.DetailedHTMLProps< + React.InputHTMLAttributes, + HTMLInputElement + > { theme?: "primary" | "dark"; label?: string | React.ReactNode; hint?: string | React.ReactNode; @@ -42,7 +43,10 @@ export const TextField = React.forwardRef( const [isPasswordVisible, setIsPasswordVisible] = useState(false); const { t } = useTranslation("forms"); const showPasswordToggleButton = props.type === "password"; - const inputType = props.type === "password" && isPasswordVisible ? "text" : props.type ?? "text"; + const inputType = + props.type === "password" && isPasswordVisible + ? "text" + : (props.type ?? "text"); const hintContent = error ? ( {error} ) : hint ? ( @@ -106,4 +110,4 @@ export const TextField = React.forwardRef( ); } ); -TextField.displayName = "TextField"; \ No newline at end of file +TextField.displayName = "TextField"; From 49df40650c8e8779b7cfff917441ead54df971f2 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Wed, 29 Oct 2025 15:27:36 -0300 Subject: [PATCH 192/197] chore: prettier --- .../src/components/text-field/text-field.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/renderer/src/components/text-field/text-field.tsx b/src/renderer/src/components/text-field/text-field.tsx index 7c0cbb58..76759126 100644 --- a/src/renderer/src/components/text-field/text-field.tsx +++ b/src/renderer/src/components/text-field/text-field.tsx @@ -4,10 +4,11 @@ import { useTranslation } from "react-i18next"; import cn from "classnames"; import "./text-field.scss"; -export interface TextFieldProps extends React.DetailedHTMLProps< - React.InputHTMLAttributes, - HTMLInputElement -> { +export interface TextFieldProps + extends React.DetailedHTMLProps< + React.InputHTMLAttributes, + HTMLInputElement + > { theme?: "primary" | "dark"; label?: string | React.ReactNode; hint?: string | React.ReactNode; @@ -42,7 +43,10 @@ export const TextField = React.forwardRef( const [isPasswordVisible, setIsPasswordVisible] = useState(false); const { t } = useTranslation("forms"); const showPasswordToggleButton = props.type === "password"; - const inputType = props.type === "password" && isPasswordVisible ? "text" : props.type ?? "text"; + const inputType = + props.type === "password" && isPasswordVisible + ? "text" + : (props.type ?? "text"); const hintContent = error ? ( {error} ) : hint ? ( @@ -106,4 +110,4 @@ export const TextField = React.forwardRef( ); } ); -TextField.displayName = "TextField"; \ No newline at end of file +TextField.displayName = "TextField"; From 2fb44a6c0e4fd899f85500ccad1c114d5481e5f2 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Wed, 29 Oct 2025 15:49:43 -0300 Subject: [PATCH 193/197] chore: remove build renderer trigger on main --- .github/workflows/build-renderer.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build-renderer.yml b/.github/workflows/build-renderer.yml index 2904bcb8..34f7d303 100644 --- a/.github/workflows/build-renderer.yml +++ b/.github/workflows/build-renderer.yml @@ -7,7 +7,6 @@ concurrency: on: push: branches: - - main - release/** jobs: From 53c162f0e49f91250e61ef910fb76f9ae3f0524b Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Wed, 29 Oct 2025 17:55:55 -0300 Subject: [PATCH 194/197] feat: add i18n --- src/locales/es/translation.json | 4 +++- src/locales/pt-BR/translation.json | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/locales/es/translation.json b/src/locales/es/translation.json index dfa7f7a1..863b8332 100644 --- a/src/locales/es/translation.json +++ b/src/locales/es/translation.json @@ -541,7 +541,9 @@ "notification_preview": "Probar notificación de logro", "debrid": "Debrid", "debrid_description": "Los servicios Debrid son descargadores premium sin restricciones que te dejan descargar más rápido archivos alojados en servicios de alojamiento siendo que la única limitación es tu velocidad de internet.", - "enable_friend_start_game_notifications": "Cuando un amigo está jugando un juego" + "enable_friend_start_game_notifications": "Cuando un amigo está jugando un juego", + "autoplay_trailers_on_game_page": "Reproducir trailers automáticamente en la página del juego", + "hide_to_tray_on_game_start": "Ocultar Hydra en la bandeja al iniciar un juego" }, "notifications": { "download_complete": "Descarga completada", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index c9e908ac..5bfc2af3 100755 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -538,7 +538,9 @@ "hidden": "Oculta", "test_notification": "Testar notificação", "notification_preview": "Prévia da Notificação de Conquistas", - "enable_friend_start_game_notifications": "Quando um amigo iniciar um jogo" + "enable_friend_start_game_notifications": "Quando um amigo iniciar um jogo", + "autoplay_trailers_on_game_page": "Reproduzir trailers automaticamente na página do jogo", + "hide_to_tray_on_game_start": "Ocultar o Hydra na bandeja ao iniciar um jogo" }, "notifications": { "download_complete": "Download concluído", From 0990951183325aac65245911898f750932ed1ca6 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Wed, 29 Oct 2025 18:06:46 -0300 Subject: [PATCH 195/197] chore: fix aur package --- .github/workflows/update-aur.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 2a3583bc..4ce84f3c 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -98,6 +98,7 @@ jobs: # Update pkgver in PKGBUILD cd hydra-launcher-bin NEW_VERSION="${{ steps.get-version.outputs.version }}" + NEW_VERSION="${NEW_VERSION#v}" echo "Updating PKGBUILD pkgver to $NEW_VERSION" From 65e49550ad8f0f85c32f70f8c4ac2af79d62f22f Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Wed, 29 Oct 2025 18:10:27 -0300 Subject: [PATCH 196/197] chore: fix aur package --- .github/workflows/update-aur.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/update-aur.yml b/.github/workflows/update-aur.yml index 4ce84f3c..52fe907e 100644 --- a/.github/workflows/update-aur.yml +++ b/.github/workflows/update-aur.yml @@ -138,6 +138,9 @@ jobs: COMMIT_MSG="v${{ steps.get-version.outputs.version }}" git commit -m "$COMMIT_MSG" + + export GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa -F ~/.ssh/config -o UserKnownHostsFile=$SSH_PATH/known_hosts" + git push origin master echo "Successfully updated AUR package to version ${{ steps.get-version.outputs.version }}" fi From 0e7e53478a5e02ca9b09769e15b2a8f408d459d5 Mon Sep 17 00:00:00 2001 From: Wkeynhk <86107421+Wkeynhk@users.noreply.github.com> Date: Thu, 30 Oct 2025 00:47:30 +0300 Subject: [PATCH 197/197] Update translation.json --- src/locales/ru/translation.json | 41 ++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/src/locales/ru/translation.json b/src/locales/ru/translation.json index 886c7d07..15a9c9cb 100644 --- a/src/locales/ru/translation.json +++ b/src/locales/ru/translation.json @@ -212,6 +212,7 @@ "stats": "Статистика", "download_count": "Загрузки", "player_count": "Активные игроки", + "rating_count": "Оценка", "download_error": "Этот вариант загрузки недоступен", "download": "Скачать", "executable_path_in_use": "Исполняемый файл уже используется \"{{game}}\"", @@ -252,17 +253,6 @@ "would_you_recommend_this_game": "Хотите оставить отзыв об этой игре?", "yes": "Да", "maybe_later": "Возможно позже", - "rating_count": "Оценка", - "delete_review": "Удалить отзыв", - "remove_review": "Удалить отзыв", - "delete_review_modal_title": "Вы уверены, что хотите удалить свой отзыв?", - "delete_review_modal_description": "Это действие нельзя отменить.", - "delete_review_modal_delete_button": "Удалить", - "delete_review_modal_cancel_button": "Отмена", - "show_original": "Показать оригинал", - "show_translation": "Показать перевод", - "show_original_translated_from": "Показать оригинал (переведено с {{language}})", - "hide_original": "Скрыть оригинал", "cloud_save": "Облачное сохранение", "cloud_save_description": "Сохраняйте ваш прогресс в облаке и продолжайте играть на любом устройстве", "backups": "Резервные копии", @@ -360,7 +350,18 @@ "caption": "Субтитры", "audio": "Аудио", "filter_by_source": "Фильтр по источнику", - "no_repacks_found": "Источники для этой игры не найдены" + "no_repacks_found": "Источники для этой игры не найдены", + "delete_review": "Удалить отзыв", + "remove_review": "Удалить отзыв", + "delete_review_modal_title": "Вы уверены, что хотите удалить свой отзыв?", + "delete_review_modal_description": "Это действие нельзя отменить.", + "delete_review_modal_delete_button": "Удалить", + "delete_review_modal_cancel_button": "Отмена", + "vote_failed": "Не удалось зарегистрировать ваш голос. Пожалуйста, попробуйте снова.", + "show_original": "Показать оригинал", + "show_translation": "Показать перевод", + "show_original_translated_from": "Показать оригинал (переведено с {{language}})", + "hide_original": "Скрыть оригинал" }, "activation": { "title": "Активировать Hydra", @@ -427,6 +428,9 @@ "validate_download_source": "Проверить", "remove_download_source": "Удалить", "add_download_source": "Добавить источник", + "adding": "Добавление…", + "failed_add_download_source": "Не удалось добавить источник. Пожалуйста, попробуйте снова.", + "download_source_already_exists": "Этот URL источника уже существует.", "download_count_zero": "В списке нет загрузок", "download_count_one": "{{countFormatted}} загрузка в списке", "download_count_other": "{{countFormatted}} загрузок в списке", @@ -434,9 +438,16 @@ "add_download_source_description": "Вставьте ссылку на .json-файл", "download_source_up_to_date": "Обновлён", "download_source_errored": "Ошибка", + "download_source_pending_matching": "Скоро обновится", + "download_source_matched": "Обновлен", + "download_source_matching": "Обновление", + "download_source_failed": "Ошибка", + "download_source_no_information": "Информация отсутствует", "sync_download_sources": "Обновить источники", "removed_download_source": "Источник удален", "removed_download_sources": "Источники удалены", + "removed_all_download_sources": "Все источники удалены", + "download_sources_synced_successfully": "Все источники синхронизированы", "cancel_button_confirmation_delete_all_sources": "Нет", "confirm_button_confirmation_delete_all_sources": "Да, удалить все", "title_confirmation_delete_all_sources": "Удалить все источники", @@ -467,6 +478,7 @@ "seed_after_download_complete": "Раздавать после завершения загрузки", "show_hidden_achievement_description": "Показывать описание скрытых достижений перед их получением", "account": "Аккаунт", + "hydra_cloud": "Hydra Cloud", "no_users_blocked": "У вас нет заблокированных пользователей", "subscription_active_until": "Ваша подписка на Hydra Cloud активна до {{date}}", "manage_subscription": "Управлять подпиской", @@ -540,7 +552,9 @@ "hidden": "Скрытый", "test_notification": "Тестовое уведомление", "notification_preview": "Предварительный просмотр уведомления о достижении", - "enable_friend_start_game_notifications": "Когда друг начинает играть в игру" + "enable_friend_start_game_notifications": "Когда друг начинает играть в игру", + "autoplay_trailers_on_game_page": "Автоматически начинать воспроизведение трейлеров на странице игры", + "hide_to_tray_on_game_start": "Скрывать Hydra в трей при запуске игры" }, "notifications": { "download_complete": "Загрузка завершена", @@ -590,6 +604,7 @@ "activity": "Недавняя активность", "library": "Библиотека", "pinned": "Закрепленные", + "sort_by": "Сортировать по:", "achievements_earned": "Заработанные достижения", "played_recently": "Недавно сыгранные", "playtime": "Время игры",