mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-26 12:21:03 +00:00
Fix: marked props read-only and catch error
This commit is contained in:
@@ -223,7 +223,7 @@
|
|||||||
"rating": "Rating",
|
"rating": "Rating",
|
||||||
"rating_stats": "Rating",
|
"rating_stats": "Rating",
|
||||||
"rating_very_negative": "Very Negative",
|
"rating_very_negative": "Very Negative",
|
||||||
"rating_negative": "Negative",
|
"rating_negative": "Negative",
|
||||||
"rating_neutral": "Neutral",
|
"rating_neutral": "Neutral",
|
||||||
"rating_positive": "Positive",
|
"rating_positive": "Positive",
|
||||||
"rating_very_positive": "Very Positive",
|
"rating_very_positive": "Very Positive",
|
||||||
|
|||||||
@@ -73,7 +73,7 @@
|
|||||||
color: globals.$muted-color;
|
color: globals.$muted-color;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
// Ensure star rating is properly aligned
|
// Ensure star rating is properly aligned
|
||||||
.star-rating {
|
.star-rating {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ export function GameCard({ game, ...props }: GameCardProps) {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="game-card__specifics-item">
|
<div className="game-card__specifics-item">
|
||||||
<StarRating
|
<StarRating
|
||||||
rating={stats?.averageScore || null}
|
rating={stats?.averageScore || null}
|
||||||
size={14}
|
size={14}
|
||||||
showCalculating={!!(stats && stats.averageScore === null)}
|
showCalculating={!!(stats && stats.averageScore === null)}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export * from "./star-rating";
|
export * from "./star-rating";
|
||||||
|
|||||||
@@ -51,4 +51,4 @@
|
|||||||
color: globals.$muted-color;
|
color: globals.$muted-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ export interface StarRatingProps {
|
|||||||
calculatingText?: string;
|
calculatingText?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function StarRating({
|
export function StarRating({
|
||||||
rating,
|
rating,
|
||||||
maxStars = 5,
|
maxStars = 5,
|
||||||
size = 12,
|
size = 12,
|
||||||
showCalculating = false,
|
showCalculating = false,
|
||||||
calculatingText = "Calculating"
|
calculatingText = "Calculating",
|
||||||
}: StarRatingProps) {
|
}: Readonly<StarRatingProps>) {
|
||||||
if (rating === null && showCalculating) {
|
if (rating === null && showCalculating) {
|
||||||
return (
|
return (
|
||||||
<div className="star-rating star-rating--calculating">
|
<div className="star-rating star-rating--calculating">
|
||||||
@@ -40,25 +40,36 @@ export function StarRating({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="star-rating">
|
<div className="star-rating">
|
||||||
|
|
||||||
{Array.from({ length: filledStars }, (_, index) => (
|
{Array.from({ length: filledStars }, (_, index) => (
|
||||||
<StarFillIcon key={`filled-${index}`} size={size} className="star-rating__star star-rating__star--filled" />
|
<StarFillIcon
|
||||||
|
key={`filled-${index}`}
|
||||||
|
size={size}
|
||||||
|
className="star-rating__star star-rating__star--filled"
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
|
||||||
{hasHalfStar && (
|
{hasHalfStar && (
|
||||||
<div className="star-rating__half-star" key="half-star">
|
<div className="star-rating__half-star" key="half-star">
|
||||||
<StarIcon size={size} className="star-rating__star star-rating__star--empty" />
|
<StarIcon
|
||||||
<StarFillIcon size={size} className="star-rating__star star-rating__star--half" />
|
size={size}
|
||||||
|
className="star-rating__star star-rating__star--empty"
|
||||||
|
/>
|
||||||
|
<StarFillIcon
|
||||||
|
size={size}
|
||||||
|
className="star-rating__star star-rating__star--half"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
{Array.from({ length: emptyStars }, (_, index) => (
|
{Array.from({ length: emptyStars }, (_, index) => (
|
||||||
<StarIcon key={`empty-${index}`} size={size} className="star-rating__star star-rating__star--empty" />
|
<StarIcon
|
||||||
|
key={`empty-${index}`}
|
||||||
|
size={size}
|
||||||
|
className="star-rating__star star-rating__star--empty"
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<span className="star-rating__value">{rating.toFixed(1)}</span>
|
<span className="star-rating__value">{rating.toFixed(1)}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { useContext, useEffect, useMemo, useRef, useState } from "react";
|
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 { ThumbsUp, ThumbsDown, Star } from "lucide-react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useEditor, EditorContent } from "@tiptap/react";
|
import { useEditor, EditorContent } from "@tiptap/react";
|
||||||
@@ -68,12 +73,18 @@ const getSelectScoreColorClass = (score: number): string => {
|
|||||||
|
|
||||||
const getRatingText = (score: number, t: (key: string) => string): string => {
|
const getRatingText = (score: number, t: (key: string) => string): string => {
|
||||||
switch (score) {
|
switch (score) {
|
||||||
case 1: return t("rating_very_negative");
|
case 1:
|
||||||
case 2: return t("rating_negative");
|
return t("rating_very_negative");
|
||||||
case 3: return t("rating_neutral");
|
case 2:
|
||||||
case 4: return t("rating_positive");
|
return t("rating_negative");
|
||||||
case 5: return t("rating_very_positive");
|
case 3:
|
||||||
default: return "";
|
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) => {
|
handlePaste: (view, event) => {
|
||||||
const htmlContent = event.clipboardData?.getData("text/html") || "";
|
const htmlContent = event.clipboardData?.getData("text/html") || "";
|
||||||
const plainText = event.clipboardData?.getData("text/plain") || "";
|
const plainText = event.clipboardData?.getData("text/plain") || "";
|
||||||
|
|
||||||
const currentText = view.state.doc.textContent;
|
const currentText = view.state.doc.textContent;
|
||||||
const remainingChars = MAX_REVIEW_CHARS - currentText.length;
|
const remainingChars = MAX_REVIEW_CHARS - currentText.length;
|
||||||
|
|
||||||
if ((htmlContent || plainText) && remainingChars > 0) {
|
if ((htmlContent || plainText) && remainingChars > 0) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
if (htmlContent) {
|
if (htmlContent) {
|
||||||
const tempDiv = document.createElement("div");
|
const tempDiv = document.createElement("div");
|
||||||
tempDiv.innerHTML = htmlContent;
|
tempDiv.innerHTML = htmlContent;
|
||||||
const textLength = tempDiv.textContent?.length || 0;
|
const textLength = tempDiv.textContent?.length || 0;
|
||||||
|
|
||||||
if (textLength <= remainingChars) {
|
if (textLength <= remainingChars) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const truncatedText = plainText.slice(0, remainingChars);
|
const truncatedText = plainText.slice(0, remainingChars);
|
||||||
view.dispatch(view.state.tr.insertText(truncatedText));
|
view.dispatch(view.state.tr.insertText(truncatedText));
|
||||||
return true;
|
return true;
|
||||||
@@ -343,7 +354,7 @@ export function GameDetailsContent() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setSubmittingReview(true);
|
setSubmittingReview(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await window.electron.createGameReview(
|
await window.electron.createGameReview(
|
||||||
shop,
|
shop,
|
||||||
@@ -355,12 +366,13 @@ export function GameDetailsContent() {
|
|||||||
editor?.commands.clearContent();
|
editor?.commands.clearContent();
|
||||||
setReviewScore(null);
|
setReviewScore(null);
|
||||||
showSuccessToast(t("review_submitted_successfully"));
|
showSuccessToast(t("review_submitted_successfully"));
|
||||||
|
|
||||||
await loadReviews(true);
|
await loadReviews(true);
|
||||||
setShowReviewForm(false);
|
setShowReviewForm(false);
|
||||||
setShowReviewPrompt(false);
|
setShowReviewPrompt(false);
|
||||||
setHasUserReviewed(true);
|
setHasUserReviewed(true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error("Failed to submit review:", error);
|
||||||
showErrorToast(t("review_submission_failed"));
|
showErrorToast(t("review_submission_failed"));
|
||||||
} finally {
|
} finally {
|
||||||
setSubmittingReview(false);
|
setSubmittingReview(false);
|
||||||
@@ -387,7 +399,7 @@ export function GameDetailsContent() {
|
|||||||
const handleReviewPromptLater = () => {
|
const handleReviewPromptLater = () => {
|
||||||
setShowReviewPrompt(false);
|
setShowReviewPrompt(false);
|
||||||
if (objectId) {
|
if (objectId) {
|
||||||
sessionStorage.setItem(`reviewPromptDismissed_${objectId}`, 'true');
|
sessionStorage.setItem(`reviewPromptDismissed_${objectId}`, "true");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -422,7 +434,7 @@ export function GameDetailsContent() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (objectId && (game || shop)) {
|
if (objectId && (game || shop)) {
|
||||||
loadReviews(true);
|
loadReviews(true);
|
||||||
checkUserReview();
|
checkUserReview();
|
||||||
}
|
}
|
||||||
}, [game, shop, objectId, reviewsSortBy, userDetails]);
|
}, [game, shop, objectId, reviewsSortBy, userDetails]);
|
||||||
|
|
||||||
@@ -661,9 +673,13 @@ export function GameDetailsContent() {
|
|||||||
onClick={() => setReviewScore(starValue)}
|
onClick={() => setReviewScore(starValue)}
|
||||||
title={getRatingText(starValue, t)}
|
title={getRatingText(starValue, t)}
|
||||||
>
|
>
|
||||||
<Star
|
<Star
|
||||||
size={24}
|
size={24}
|
||||||
fill={reviewScore && starValue <= reviewScore ? "currentColor" : "none"}
|
fill={
|
||||||
|
reviewScore && starValue <= reviewScore
|
||||||
|
? "currentColor"
|
||||||
|
: "none"
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
@@ -684,7 +700,6 @@ export function GameDetailsContent() {
|
|||||||
? t("submitting")
|
? t("submitting")
|
||||||
: t("submit_review")}
|
: t("submit_review")}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@@ -774,12 +789,19 @@ export function GameDetailsContent() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="game-details__review-score-stars" title={getRatingText(review.score, t)}>
|
<div
|
||||||
|
className="game-details__review-score-stars"
|
||||||
|
title={getRatingText(review.score, t)}
|
||||||
|
>
|
||||||
{[1, 2, 3, 4, 5].map((starValue) => (
|
{[1, 2, 3, 4, 5].map((starValue) => (
|
||||||
<Star
|
<Star
|
||||||
key={starValue}
|
key={starValue}
|
||||||
size={20}
|
size={20}
|
||||||
fill={starValue <= review.score ? "currentColor" : "none"}
|
fill={
|
||||||
|
starValue <= review.score
|
||||||
|
? "currentColor"
|
||||||
|
: "none"
|
||||||
|
}
|
||||||
className={`game-details__review-star ${
|
className={`game-details__review-star ${
|
||||||
starValue <= review.score
|
starValue <= review.score
|
||||||
? "game-details__review-star--filled"
|
? "game-details__review-star--filled"
|
||||||
|
|||||||
@@ -62,13 +62,13 @@ $hero-height: 300px;
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
margin-top: calc(globals.$spacing-unit * 1);
|
margin-top: calc(globals.$spacing-unit * 1);
|
||||||
border: 1px solid;
|
border: 1px solid;
|
||||||
|
|
||||||
&--success {
|
&--success {
|
||||||
background: rgba(34, 197, 94, 0.1);
|
background: rgba(34, 197, 94, 0.1);
|
||||||
color: #86efac;
|
color: #86efac;
|
||||||
border-color: rgba(34, 197, 94, 0.3);
|
border-color: rgba(34, 197, 94, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
&--error {
|
&--error {
|
||||||
background: rgba(239, 68, 68, 0.1);
|
background: rgba(239, 68, 68, 0.1);
|
||||||
color: #fca5a5;
|
color: #fca5a5;
|
||||||
|
|||||||
Reference in New Issue
Block a user