-
- {width}x{height}
-
-
{isResolutionValid ? (
{t('Valid resolution')}
+ ) : showInvalidTooltip ? (
+
+
+
+
+ {t('Invalid resolution')}
+
+
+
+
+
+
+
+
+
) : (
{t('Invalid resolution')}
)}
+
+
+ {width}x{height}
+
- {isResolutionValid && hasConsistencyWarning ? (
+ {!isResolutionValid ? (
+
{invalidExplanation}
+ ) : null}
+
+ {showUpscaleNudge ? (
+
+
+ {t('1x capture, render at 2x or 3x for a sharper screenshot')}
+
+ ) : null}
+
+ {showConsistencyWarning ? (
{consistencyMessage}
@@ -55,3 +106,30 @@ export const ScreenshotPreviewMeta: FC = ({
);
};
+
+interface AcceptedSizesTooltipProps {
+ screenshotResolutions: Array<{ width: number; height: number }>;
+ supportsUpscaledScreenshots?: boolean;
+}
+
+const AcceptedSizesTooltip: FC
= ({
+ screenshotResolutions,
+ supportsUpscaledScreenshots,
+}) => {
+ const { t } = useTranslation();
+
+ // Sort by width, then by height.
+ const sortedResolutions = [...screenshotResolutions].sort(
+ (a, b) => a.width - b.width || a.height - b.height,
+ );
+ const nativeList = sortedResolutions.map((r) => `${r.width}x${r.height}`).join(', ');
+
+ return (
+
+
{nativeList}
+ {supportsUpscaledScreenshots ? (
+
{t('or 2x or 3x of any of these')}
+ ) : null}
+
+ );
+};
diff --git a/resources/js/features/games/components/UploadForm/UploadForm.test.tsx b/resources/js/features/games/components/UploadForm/UploadForm.test.tsx
index 5a4748268d..27fd4bcaca 100644
--- a/resources/js/features/games/components/UploadForm/UploadForm.test.tsx
+++ b/resources/js/features/games/components/UploadForm/UploadForm.test.tsx
@@ -89,18 +89,36 @@ describe('Component: UploadForm', () => {
expect(URL.revokeObjectURL).toHaveBeenCalledWith('blob:test');
});
- it('given screenshot resolutions are provided, displays them in the drop zone', () => {
+ it('given a non-upscaling system, displays the supported resolutions line in the drop zone with the x sign', () => {
// ARRANGE
render(
,
+ );
+
+ // ASSERT
+ expect(screen.getByText('Supported resolutions: 320x240')).toBeVisible();
+ });
+
+ it('given an upscaling-capable system, displays the upscale nudge in the drop zone', () => {
+ // ARRANGE
+ render(
+ ,
);
// ASSERT
- expect(screen.getByText(/expected resolutions: 320x240/i)).toBeVisible();
+ expect(
+ screen.getByText(/upscaled screenshots look sharper\. render at 2x or 3x/i),
+ ).toBeVisible();
});
it('given the preview is valid and matches the existing canonical resolution, does not show a consistency warning', async () => {
@@ -356,46 +374,7 @@ describe('Component: UploadForm', () => {
});
});
- it('given the file has an invalid resolution, shows a validation error', async () => {
- // ARRANGE
- vi.stubGlobal(
- 'Image',
- class MockImage {
- naturalWidth = 999;
- naturalHeight = 888;
- onload: (() => void) | null = null;
- onerror: ((error: unknown) => void) | null = null;
-
- set src(_value: string) {
- queueMicrotask(() => this.onload?.());
- }
- },
- );
-
- render(
- ,
- );
-
- const fileInput = screen.getByLabelText(/upload screenshot file/i) as HTMLInputElement;
- await userEvent.upload(fileInput, createMockImageFile());
- await waitFor(() => {
- expect(screen.getByRole('button', { name: /submit screenshot/i })).toBeEnabled();
- });
-
- // ACT
- await userEvent.click(screen.getByRole('button', { name: /submit screenshot/i }));
-
- // ASSERT
- await waitFor(() => {
- expect(screen.getByText(/999x888.*don't match/i)).toBeVisible();
- });
- });
-
- it('given upscaled screenshots are supported and the resolution is invalid, includes multiples info in the error', async () => {
+ it('given the file has an invalid resolution, shows a short form-error pointing to the preview', async () => {
// ARRANGE
vi.stubGlobal(
'Image',
@@ -416,7 +395,6 @@ describe('Component: UploadForm', () => {
gameId={1}
screenshotResolutions={[{ width: 320, height: 240 }]}
selectedType="ingame"
- supportsUpscaledScreenshots={true}
/>,
);
@@ -431,7 +409,7 @@ describe('Component: UploadForm', () => {
// ASSERT
await waitFor(() => {
- expect(screen.getByText(/2x\/3x multiples/i)).toBeVisible();
+ expect(screen.getByText(/resolution doesn't match\. see the preview above/i)).toBeVisible();
});
});
diff --git a/resources/js/features/games/components/UploadForm/UploadForm.tsx b/resources/js/features/games/components/UploadForm/UploadForm.tsx
index fecd657e98..0de5151db2 100644
--- a/resources/js/features/games/components/UploadForm/UploadForm.tsx
+++ b/resources/js/features/games/components/UploadForm/UploadForm.tsx
@@ -12,10 +12,9 @@ import {
BaseFormMessage,
} from '@/common/components/+vendor/BaseForm';
import { toastMessage } from '@/common/components/+vendor/BaseToaster';
-import { usePageProps } from '@/common/hooks/usePageProps';
+import { getIsNativeScreenshotResolution } from '@/common/utils/getIsNativeScreenshotResolution';
import { getIsSameScreenshotResolution } from '@/common/utils/getIsSameScreenshotResolution';
import { getIsValidScreenshotResolution } from '@/common/utils/getIsValidScreenshotResolution';
-import { getUserIntlLocale } from '@/common/utils/getUserIntlLocale';
import { ScreenshotDropZone } from './ScreenshotDropZone';
import { useGameScreenshotUploadForm } from './useGameScreenshotUploadForm';
@@ -43,11 +42,8 @@ export const UploadForm: FC = ({
selectedType,
supportsUpscaledScreenshots,
}) => {
- const { auth } = usePageProps();
const { t } = useTranslation();
- const locale = getUserIntlLocale(auth?.user);
-
const { form, mutation, onSubmit } = useGameScreenshotUploadForm({
gameId,
screenshotResolutions,
@@ -156,12 +152,16 @@ export const UploadForm: FC = ({
)
);
- const formattedResolutions =
- screenshotResolutions.length > 0
- ? new Intl.ListFormat(locale, { style: 'narrow', type: 'conjunction' }).format(
- screenshotResolutions.map((r) => `${r.width}x${r.height}`),
- )
- : '';
+ const is1xCapture = !!(
+ previewDimensions &&
+ isResolutionValid &&
+ getIsNativeScreenshotResolution(
+ previewDimensions.width,
+ previewDimensions.height,
+ screenshotResolutions,
+ hasAnalogTvOutput,
+ )
+ );
const handleFormSubmit = async (values: Parameters[0]) => {
await onSubmit(values, (screenshot) => {
@@ -189,14 +189,15 @@ export const UploadForm: FC = ({
diff --git a/resources/js/features/games/components/UploadForm/useGameScreenshotUploadForm.ts b/resources/js/features/games/components/UploadForm/useGameScreenshotUploadForm.ts
index f03e5e61f0..ff416f825a 100644
--- a/resources/js/features/games/components/UploadForm/useGameScreenshotUploadForm.ts
+++ b/resources/js/features/games/components/UploadForm/useGameScreenshotUploadForm.ts
@@ -5,9 +5,7 @@ import { useTranslation } from 'react-i18next';
import { z } from 'zod';
import { toastMessage } from '@/common/components/+vendor/BaseToaster';
-import { usePageProps } from '@/common/hooks/usePageProps';
import { getIsValidScreenshotResolution } from '@/common/utils/getIsValidScreenshotResolution';
-import { getUserIntlLocale } from '@/common/utils/getUserIntlLocale';
import { useSubmitGameScreenshotMutation } from '../../hooks/mutations/useSubmitGameScreenshotMutation';
@@ -31,11 +29,8 @@ export function useGameScreenshotUploadForm({
screenshotResolutions,
supportsUpscaledScreenshots,
}: UseGameScreenshotUploadFormOptions) {
- const { auth } = usePageProps();
const { t } = useTranslation();
- const locale = getUserIntlLocale(auth?.user);
-
const form = useForm({
resolver: zodResolver(formSchema),
defaultValues: {
@@ -62,22 +57,9 @@ export function useGameScreenshotUploadForm({
supportsUpscaledScreenshots,
)
) {
- const formatted = new Intl.ListFormat(locale, {
- style: 'narrow',
- type: 'conjunction',
- }).format(screenshotResolutions.map((r) => `${r.width}x${r.height}`));
-
- const errorMessage = supportsUpscaledScreenshots
- ? t(
- "This screenshot's dimensions ({{width}}x{{height}}) don't match the expected resolutions: {{resolutions}} (or 2x/3x multiples).",
- { width, height, resolutions: formatted },
- )
- : t(
- "This screenshot's dimensions ({{width}}x{{height}}) don't match the expected resolutions: {{resolutions}}.",
- { width, height, resolutions: formatted },
- );
-
- form.setError('imageData', { message: errorMessage });
+ form.setError('imageData', {
+ message: t("Resolution doesn't match. See the preview above."),
+ });
return;
}