mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-16 19:33:11 +02:00
Fix SAML login "something went wrong" when language list = 1 (#5750)
# Description of Changes <!-- Please provide a summary of the changes, including: - What was changed - Why the change was made - Any challenges encountered Closes #(issue_number) --> --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### Translations (if applicable) - [ ] I ran [`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details.
This commit is contained in:
+1
-1
@@ -67,7 +67,7 @@ springBoot {
|
|||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
group = 'stirling.software'
|
group = 'stirling.software'
|
||||||
version = '2.5.0'
|
version = '2.5.1'
|
||||||
|
|
||||||
configurations.configureEach {
|
configurations.configureEach {
|
||||||
exclude group: 'commons-logging', module: 'commons-logging'
|
exclude group: 'commons-logging', module: 'commons-logging'
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||||
"productName": "Stirling-PDF",
|
"productName": "Stirling-PDF",
|
||||||
"version": "2.5.0",
|
"version": "2.5.1",
|
||||||
"identifier": "stirling.pdf.dev",
|
"identifier": "stirling.pdf.dev",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../dist",
|
"frontendDist": "../dist",
|
||||||
|
|||||||
@@ -52,12 +52,15 @@ const LanguageItem: React.FC<LanguageItemProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const labelText = option.label;
|
||||||
|
const comingSoonText = t('comingSoon', 'Coming soon');
|
||||||
|
|
||||||
const label = disabled ? (
|
const label = disabled ? (
|
||||||
<Tooltip content={t('comingSoon', 'Coming soon')} position="left" arrow>
|
<Tooltip content={comingSoonText} position="left" arrow>
|
||||||
<p>{option.label}</p>
|
<p>{labelText}</p>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : (
|
) : (
|
||||||
<p>{option.label}</p>
|
<p>{labelText}</p>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -157,12 +160,27 @@ const LanguageSelector: React.FC<LanguageSelectorProps> = ({
|
|||||||
compact = false,
|
compact = false,
|
||||||
tooltip
|
tooltip
|
||||||
}) => {
|
}) => {
|
||||||
const { i18n } = useTranslation();
|
const { i18n, ready } = useTranslation();
|
||||||
const [opened, setOpened] = useState(false);
|
const [opened, setOpened] = useState(false);
|
||||||
const [animationTriggered, setAnimationTriggered] = useState(false);
|
const [animationTriggered, setAnimationTriggered] = useState(false);
|
||||||
const [pendingLanguage, setPendingLanguage] = useState<string | null>(null);
|
const [pendingLanguage, setPendingLanguage] = useState<string | null>(null);
|
||||||
const [rippleEffect, setRippleEffect] = useState<RippleEffect | null>(null);
|
const [rippleEffect, setRippleEffect] = useState<RippleEffect | null>(null);
|
||||||
|
|
||||||
|
// Trigger animation when dropdown opens
|
||||||
|
useEffect(() => {
|
||||||
|
if (opened) {
|
||||||
|
setAnimationTriggered(false);
|
||||||
|
// Small delay to ensure DOM is ready
|
||||||
|
setTimeout(() => setAnimationTriggered(true), 20);
|
||||||
|
}
|
||||||
|
}, [opened]);
|
||||||
|
|
||||||
|
// Don't render until i18n is ready to prevent race condition
|
||||||
|
// during SAML auth where components render before i18n initializes
|
||||||
|
if (!ready || !i18n.language) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Get the filtered list of supported languages from i18n
|
// Get the filtered list of supported languages from i18n
|
||||||
// This respects server config (ui.languages) applied by AppConfigLoader
|
// This respects server config (ui.languages) applied by AppConfigLoader
|
||||||
const allowedLanguages = (i18n.options.supportedLngs as string[] || [])
|
const allowedLanguages = (i18n.options.supportedLngs as string[] || [])
|
||||||
@@ -176,12 +194,6 @@ const LanguageSelector: React.FC<LanguageSelectorProps> = ({
|
|||||||
label: name,
|
label: name,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Hide the language selector if there's only one language option
|
|
||||||
// (no point showing a selector when there's nothing to select)
|
|
||||||
if (languageOptions.length <= 1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate dropdown width and grid columns based on number of languages
|
// Calculate dropdown width and grid columns based on number of languages
|
||||||
// 2-4: 300px/2 cols, 5-9: 400px/3 cols, 10+: 600px/4 cols
|
// 2-4: 300px/2 cols, 5-9: 400px/3 cols, 10+: 600px/4 cols
|
||||||
const dropdownWidth = languageOptions.length <= 4 ? 300
|
const dropdownWidth = languageOptions.length <= 4 ? 300
|
||||||
@@ -225,16 +237,14 @@ const LanguageSelector: React.FC<LanguageSelectorProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const currentLanguage = supportedLanguages[i18n.language as keyof typeof supportedLanguages] ||
|
const currentLanguage = supportedLanguages[i18n.language as keyof typeof supportedLanguages] ||
|
||||||
supportedLanguages['en-GB'];
|
supportedLanguages['en-GB'] ||
|
||||||
|
'English'; // Fallback if supportedLanguages lookup fails
|
||||||
|
|
||||||
// Trigger animation when dropdown opens
|
// Hide the language selector if there's only one language option
|
||||||
useEffect(() => {
|
// (no point showing a selector when there's nothing to select)
|
||||||
if (opened) {
|
if (languageOptions.length <= 1) {
|
||||||
setAnimationTriggered(false);
|
return null;
|
||||||
// Small delay to ensure DOM is ready
|
}
|
||||||
setTimeout(() => setAnimationTriggered(true), 20);
|
|
||||||
}
|
|
||||||
}, [opened]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -24,9 +24,8 @@ const ToolChain: React.FC<ToolChainProps> = ({
|
|||||||
size = 'xs',
|
size = 'xs',
|
||||||
color = 'var(--mantine-color-blue-7)'
|
color = 'var(--mantine-color-blue-7)'
|
||||||
}) => {
|
}) => {
|
||||||
if (!toolChain || toolChain.length === 0) return null;
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
if (!toolChain || toolChain.length === 0) return null;
|
||||||
|
|
||||||
const toolIds = toolChain.map(tool => tool.toolId);
|
const toolIds = toolChain.map(tool => tool.toolId);
|
||||||
|
|
||||||
|
|||||||
@@ -74,6 +74,11 @@ const CompareDocumentPane = ({
|
|||||||
}
|
}
|
||||||
}, [zoom]);
|
}, [zoom]);
|
||||||
|
|
||||||
|
const renderedPageNumbers = useMemo(
|
||||||
|
() => new Set(pages.map((p) => p.pageNumber)),
|
||||||
|
[pages]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="compare-pane">
|
<div className="compare-pane">
|
||||||
<div className="compare-header">
|
<div className="compare-header">
|
||||||
@@ -88,7 +93,7 @@ const CompareDocumentPane = ({
|
|||||||
placeholder={dropdownPlaceholder ?? null}
|
placeholder={dropdownPlaceholder ?? null}
|
||||||
className={pane === 'comparison' ? 'compare-changes-select--comparison' : undefined}
|
className={pane === 'comparison' ? 'compare-changes-select--comparison' : undefined}
|
||||||
onNavigate={onNavigateChange}
|
onNavigate={onNavigateChange}
|
||||||
renderedPageNumbers={useMemo(() => new Set(pages.map(p => p.pageNumber)), [pages])}
|
renderedPageNumbers={renderedPageNumbers}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
+10
-11
@@ -43,6 +43,16 @@ interface EditTableOfContentsWorkbenchViewProps {
|
|||||||
const EditTableOfContentsWorkbenchView = ({ data }: EditTableOfContentsWorkbenchViewProps) => {
|
const EditTableOfContentsWorkbenchView = ({ data }: EditTableOfContentsWorkbenchViewProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const terminology = useFileActionTerminology();
|
const terminology = useFileActionTerminology();
|
||||||
|
const files = data?.files ?? [];
|
||||||
|
const thumbnails = data?.thumbnails ?? [];
|
||||||
|
const previewFiles = useMemo(
|
||||||
|
() =>
|
||||||
|
files.map((file, index) => ({
|
||||||
|
file,
|
||||||
|
thumbnail: thumbnails[index],
|
||||||
|
})),
|
||||||
|
[files, thumbnails]
|
||||||
|
);
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return (
|
return (
|
||||||
@@ -63,8 +73,6 @@ const EditTableOfContentsWorkbenchView = ({ data }: EditTableOfContentsWorkbench
|
|||||||
bookmarks,
|
bookmarks,
|
||||||
selectedFileName,
|
selectedFileName,
|
||||||
disabled,
|
disabled,
|
||||||
files,
|
|
||||||
thumbnails,
|
|
||||||
downloadUrl,
|
downloadUrl,
|
||||||
downloadFilename,
|
downloadFilename,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
@@ -78,15 +86,6 @@ const EditTableOfContentsWorkbenchView = ({ data }: EditTableOfContentsWorkbench
|
|||||||
onFileClick,
|
onFileClick,
|
||||||
} = data;
|
} = data;
|
||||||
|
|
||||||
const previewFiles = useMemo(
|
|
||||||
() =>
|
|
||||||
files?.map((file, index) => ({
|
|
||||||
file,
|
|
||||||
thumbnail: thumbnails[index],
|
|
||||||
})) ?? [],
|
|
||||||
[files, thumbnails]
|
|
||||||
);
|
|
||||||
|
|
||||||
const showResults = Boolean(
|
const showResults = Boolean(
|
||||||
previewFiles.length > 0 || downloadUrl || errorMessage
|
previewFiles.length > 0 || downloadUrl || errorMessage
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import FileStatusIndicator from '@app/components/tools/shared/FileStatusIndicator';
|
import FileStatusIndicator from '@app/components/tools/shared/FileStatusIndicator';
|
||||||
import { StirlingFile } from '@app/types/fileContext';
|
import { StirlingFile } from '@app/types/fileContext';
|
||||||
|
import i18n from '@app/i18n';
|
||||||
|
|
||||||
export interface FilesToolStepProps {
|
export interface FilesToolStepProps {
|
||||||
selectedFiles: StirlingFile[];
|
selectedFiles: StirlingFile[];
|
||||||
@@ -14,9 +14,7 @@ export function createFilesToolStep(
|
|||||||
createStep: (title: string, props: any, children?: React.ReactNode) => React.ReactElement,
|
createStep: (title: string, props: any, children?: React.ReactNode) => React.ReactElement,
|
||||||
props: FilesToolStepProps
|
props: FilesToolStepProps
|
||||||
): React.ReactElement {
|
): React.ReactElement {
|
||||||
const { t } = useTranslation();
|
return createStep(i18n.t("files.title", "Files"), {
|
||||||
|
|
||||||
return createStep(t("files.title", "Files"), {
|
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
isCollapsed: props.isCollapsed,
|
isCollapsed: props.isCollapsed,
|
||||||
onCollapsedClick: props.onCollapsedClick
|
onCollapsedClick: props.onCollapsedClick
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { useFileActionIcons } from "@app/hooks/useFileActionIcons";
|
|||||||
import { saveOperationResults } from "@app/services/operationResultsSaveService";
|
import { saveOperationResults } from "@app/services/operationResultsSaveService";
|
||||||
import { useFileActions, useFileState } from "@app/contexts/FileContext";
|
import { useFileActions, useFileState } from "@app/contexts/FileContext";
|
||||||
import { FileId } from "@app/types/fileContext";
|
import { FileId } from "@app/types/fileContext";
|
||||||
|
import i18n from "@app/i18n";
|
||||||
|
|
||||||
export interface ReviewToolStepProps<TParams = unknown> {
|
export interface ReviewToolStepProps<TParams = unknown> {
|
||||||
isVisible: boolean;
|
isVisible: boolean;
|
||||||
@@ -151,10 +152,8 @@ export function createReviewToolStep<TParams = unknown>(
|
|||||||
) => React.ReactElement,
|
) => React.ReactElement,
|
||||||
props: ReviewToolStepProps<TParams>
|
props: ReviewToolStepProps<TParams>
|
||||||
): React.ReactElement {
|
): React.ReactElement {
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return createStep(
|
return createStep(
|
||||||
t("review", "Review"),
|
i18n.t("review", "Review"),
|
||||||
{
|
{
|
||||||
isVisible: props.isVisible,
|
isVisible: props.isVisible,
|
||||||
isCollapsed: props.isCollapsed,
|
isCollapsed: props.isCollapsed,
|
||||||
|
|||||||
@@ -80,8 +80,6 @@ const ToolStep = ({
|
|||||||
alwaysShowTooltip = false,
|
alwaysShowTooltip = false,
|
||||||
tooltip
|
tooltip
|
||||||
}: ToolStepProps) => {
|
}: ToolStepProps) => {
|
||||||
if (!isVisible) return null;
|
|
||||||
|
|
||||||
const parent = useContext(ToolStepContext);
|
const parent = useContext(ToolStepContext);
|
||||||
|
|
||||||
// Auto-detect if we should show numbers based on sibling count or force option
|
// Auto-detect if we should show numbers based on sibling count or force option
|
||||||
@@ -91,6 +89,8 @@ const ToolStep = ({
|
|||||||
return parent ? parent.visibleStepCount >= 3 : false; // Auto-detect
|
return parent ? parent.visibleStepCount >= 3 : false; // Auto-detect
|
||||||
}, [showNumber, parent]);
|
}, [showNumber, parent]);
|
||||||
|
|
||||||
|
if (!isVisible) return null;
|
||||||
|
|
||||||
const stepNumber = _stepNumber;
|
const stepNumber = _stepNumber;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ const FREE_LICENSE_INFO: LicenseInfo = {
|
|||||||
|
|
||||||
const BASE_NO_LOGIN_CONFIG: AppConfig = {
|
const BASE_NO_LOGIN_CONFIG: AppConfig = {
|
||||||
enableAnalytics: true,
|
enableAnalytics: true,
|
||||||
appVersion: '2.5.0',
|
appVersion: '2.5.1',
|
||||||
serverCertificateEnabled: false,
|
serverCertificateEnabled: false,
|
||||||
enableAlphaFunctionality: false,
|
enableAlphaFunctionality: false,
|
||||||
serverPort: 8080,
|
serverPort: 8080,
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ const FREE_LICENSE_INFO: LicenseInfo = {
|
|||||||
|
|
||||||
const BASE_NO_LOGIN_CONFIG: AppConfig = {
|
const BASE_NO_LOGIN_CONFIG: AppConfig = {
|
||||||
enableAnalytics: true,
|
enableAnalytics: true,
|
||||||
appVersion: '2.5.0',
|
appVersion: '2.5.1',
|
||||||
serverCertificateEnabled: false,
|
serverCertificateEnabled: false,
|
||||||
enableAlphaFunctionality: false,
|
enableAlphaFunctionality: false,
|
||||||
enableDesktopInstallSlide: true,
|
enableDesktopInstallSlide: true,
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ services:
|
|||||||
DOCKER_ENABLE_SECURITY: "true"
|
DOCKER_ENABLE_SECURITY: "true"
|
||||||
SECURITY_ENABLELOGIN: "true"
|
SECURITY_ENABLELOGIN: "true"
|
||||||
SECURITY_LOGINMETHOD: "${SECURITY_LOGINMETHOD:-all}"
|
SECURITY_LOGINMETHOD: "${SECURITY_LOGINMETHOD:-all}"
|
||||||
SYSTEM_DEFAULTLOCALE: en-US
|
SYSTEM_DEFAULTLOCALE: "${SYSTEM_DEFAULTLOCALE:-en-US}"
|
||||||
SYSTEM_BACKENDURL: "http://localhost:8080"
|
SYSTEM_BACKENDURL: "http://localhost:8080"
|
||||||
|
|
||||||
# Enterprise License (required for SAML)
|
# Enterprise License (required for SAML)
|
||||||
|
|||||||
@@ -13,24 +13,48 @@ echo -e "${BLUE}╚════════════════════
|
|||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
AUTO_LOGIN=false
|
AUTO_LOGIN=false
|
||||||
|
DEFAULT_LANGUAGE="en-US"
|
||||||
COMPOSE_UP_ARGS=(-d --build)
|
COMPOSE_UP_ARGS=(-d --build)
|
||||||
for arg in "$@"; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$arg" in
|
case "$1" in
|
||||||
--auto)
|
--auto)
|
||||||
AUTO_LOGIN=true
|
AUTO_LOGIN=true
|
||||||
|
shift
|
||||||
;;
|
;;
|
||||||
--nobuild)
|
--nobuild)
|
||||||
COMPOSE_UP_ARGS=(-d)
|
COMPOSE_UP_ARGS=(-d)
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--language)
|
||||||
|
if [[ -z "${2:-}" ]]; then
|
||||||
|
echo -e "${RED}Missing value for --language${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
DEFAULT_LANGUAGE="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--language=*)
|
||||||
|
DEFAULT_LANGUAGE="${1#*=}"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-l)
|
||||||
|
if [[ -z "${2:-}" ]]; then
|
||||||
|
echo -e "${RED}Missing value for -l${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
DEFAULT_LANGUAGE="$2"
|
||||||
|
shift 2
|
||||||
;;
|
;;
|
||||||
-h|--help)
|
-h|--help)
|
||||||
echo "Usage: $0 [--auto] [--nobuild]"
|
echo "Usage: $0 [--auto] [--nobuild] [--language <locale>]"
|
||||||
echo ""
|
echo ""
|
||||||
echo " --auto Enable SSO auto-login and force SAML-only login method"
|
echo " --auto Enable SSO auto-login and force SAML-only login method"
|
||||||
echo " --nobuild Skip building images (use existing images)"
|
echo " --nobuild Skip building images (use existing images)"
|
||||||
|
echo " --language Set system default locale (e.g. de-DE, sv-SE)"
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo -e "${RED}Unknown option: $arg${NC}"
|
echo -e "${RED}Unknown option: $1${NC}"
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
@@ -65,6 +89,10 @@ if [ "$AUTO_LOGIN" = true ]; then
|
|||||||
echo ""
|
echo ""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
export SYSTEM_DEFAULTLOCALE="$DEFAULT_LANGUAGE"
|
||||||
|
echo -e "${GREEN}✓ Default locale set to: ${SYSTEM_DEFAULTLOCALE}${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
echo -e "${YELLOW}▶ Starting Keycloak (SAML) containers...${NC}"
|
echo -e "${YELLOW}▶ Starting Keycloak (SAML) containers...${NC}"
|
||||||
docker-compose -f docker-compose-keycloak-saml.yml up "${COMPOSE_UP_ARGS[@]}" keycloak-saml-db keycloak-saml
|
docker-compose -f docker-compose-keycloak-saml.yml up "${COMPOSE_UP_ARGS[@]}" keycloak-saml-db keycloak-saml
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user