mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-14 10:34:06 +02:00
fix many UI issues (#6569)
# Description of Changes
- Tool action button truncation - fixed by allowing Mantine <Button>
label to wrap (whiteSpace: normal, height: auto) instead of clipping
- Role badge truncation on People page - fixed by dropping the column's
fixed w={100} and letting the badge size to its content
- Settings nav item wraps to 3 lines - fixed by hiding the inline ALPHA
badge by default and revealing it on :hover/:focus-within/.active
- Zoom slider cramped on narrow desktop - fixed by removing the
toolbar's hardcoded minWidth: 30rem and giving the slider flexShrink: 0
+ minWidth: 6rem
- "Swipe left or right" hint on desktop - fixed by adding a useIsTouch()
hook (pointer: coarse) and gating the hint on isMobile && isTouch
- Logout doesn't redirect - fixed by replacing navigate('/login') with
window.location.assign('/login') in a finally block so auth context
fully re-bootstraps
- Viewer top toolbar clips icons on mobile - fixed by switching the
wrapped state to justify-content: flex-start + overflow-x: auto so the
icon strip is momentum-scrollable
- Mobile bottom toolbar overflows - fixed by gating layout on
useIsPhone() and reducing the inline bar to prev / page / next / ⋮ only
- Lost controls when shrinking mobile toolbar - fixed by adding a
Mantine <Menu> behind ⋮ that groups First/Last page, Zoom in/out (with
%), Dual-page, Dark/Sepia filter under Page navigation / Zoom / View
labels
- "Upload from computer" label clipped on hover - fixed by unmounting
the Add Files button entirely while Upload is hovered, so Upload claims
width: 100%
- Settings rows clip controls off-screen - fixed by adding flex: 1,
minWidth: 0 to the inner text-block <div> on 44 rows across 10 -files,
so labels shrink and wrap while controls stay anchored to the right
---
Screenshots
[report-before-after.html](https://github.com/user-attachments/files/28687621/report-before-after.html)
## 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/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 run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
This commit is contained in:
@@ -8643,12 +8643,16 @@ enableDarkFilter = "Enable Dark Filter"
|
||||
enableSepiaFilter = "Enable Sepia Filter"
|
||||
firstPage = "First Page"
|
||||
lastPage = "Last Page"
|
||||
moreOptions = "More"
|
||||
nextPage = "Next Page"
|
||||
onlyPdfSupported = "This file format is not supported for preview."
|
||||
pageNavigation = "Page navigation"
|
||||
previousPage = "Previous Page"
|
||||
resetZoom = "Reset zoom"
|
||||
singlePageView = "Single Page View"
|
||||
unknownFile = "Unknown file"
|
||||
view = "View"
|
||||
zoom = "Zoom"
|
||||
zoomIn = "Zoom In"
|
||||
zoomOut = "Zoom Out"
|
||||
|
||||
|
||||
@@ -104,44 +104,44 @@ const AddFileCard = ({
|
||||
}}
|
||||
onMouseLeave={() => setIsUploadHover(false)}
|
||||
>
|
||||
<Button
|
||||
style={{
|
||||
backgroundColor: "var(--landing-button-bg)",
|
||||
color: "var(--landing-button-color)",
|
||||
border: "1px solid var(--landing-button-border)",
|
||||
borderRadius: "2rem",
|
||||
height: "38px",
|
||||
paddingLeft: isUploadHover ? 0 : "1rem",
|
||||
paddingRight: isUploadHover ? 0 : "1rem",
|
||||
width: isUploadHover ? "58px" : "calc(100% - 58px - 0.6rem)",
|
||||
minWidth: isUploadHover ? "58px" : undefined,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
transition: "width .5s ease, padding .5s ease",
|
||||
}}
|
||||
onClick={handleOpenFilesModal}
|
||||
onMouseEnter={() => setIsUploadHover(false)}
|
||||
>
|
||||
<LocalIcon
|
||||
icon="add"
|
||||
width="1.5rem"
|
||||
height="1.5rem"
|
||||
className="text-[var(--accent-interactive)]"
|
||||
/>
|
||||
{!isUploadHover && (
|
||||
{!isUploadHover && (
|
||||
<Button
|
||||
style={{
|
||||
backgroundColor: "var(--landing-button-bg)",
|
||||
color: "var(--landing-button-color)",
|
||||
border: "1px solid var(--landing-button-border)",
|
||||
borderRadius: "2rem",
|
||||
height: "38px",
|
||||
paddingLeft: "1rem",
|
||||
paddingRight: "1rem",
|
||||
width: "calc(100% - 58px - 0.6rem)",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
transition: "width .5s ease, padding .5s ease",
|
||||
}}
|
||||
onClick={handleOpenFilesModal}
|
||||
onMouseEnter={() => setIsUploadHover(false)}
|
||||
>
|
||||
<LocalIcon
|
||||
icon="add"
|
||||
width="1.5rem"
|
||||
height="1.5rem"
|
||||
className="text-[var(--accent-interactive)]"
|
||||
/>
|
||||
<span>{t("landing.addFiles", "Add Files")}</span>
|
||||
)}
|
||||
</Button>
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
aria-label={t("addFileCard.upload", "Upload")}
|
||||
title={terminology.uploadFromComputer}
|
||||
style={{
|
||||
backgroundColor: "var(--landing-button-bg)",
|
||||
color: "var(--landing-button-color)",
|
||||
border: "1px solid var(--landing-button-border)",
|
||||
borderRadius: "1rem",
|
||||
height: "38px",
|
||||
width: isUploadHover ? "calc(100% - 58px - 0.6rem)" : "58px",
|
||||
width: isUploadHover ? "100%" : "58px",
|
||||
minWidth: "58px",
|
||||
paddingLeft: isUploadHover ? "1rem" : 0,
|
||||
paddingRight: isUploadHover ? "1rem" : 0,
|
||||
@@ -149,6 +149,7 @@ const AddFileCard = ({
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
transition: "width .5s ease, padding .5s ease",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
onClick={handleNativeUploadClick}
|
||||
onMouseEnter={() => setIsUploadHover(true)}
|
||||
@@ -157,10 +158,18 @@ const AddFileCard = ({
|
||||
icon={icons.uploadIconName}
|
||||
width="1.25rem"
|
||||
height="1.25rem"
|
||||
style={{ color: "var(--accent-interactive)" }}
|
||||
style={{ color: "var(--accent-interactive)", flexShrink: 0 }}
|
||||
/>
|
||||
{isUploadHover && (
|
||||
<span style={{ marginLeft: ".5rem" }}>
|
||||
<span
|
||||
style={{
|
||||
marginLeft: ".5rem",
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
minWidth: 0,
|
||||
}}
|
||||
>
|
||||
{terminology.uploadFromComputer}
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -106,6 +106,15 @@
|
||||
margin-bottom: 0.25rem; /* 4px */
|
||||
}
|
||||
|
||||
.modal-nav-item-badge {
|
||||
display: none;
|
||||
}
|
||||
.modal-nav-item:hover .modal-nav-item-badge,
|
||||
.modal-nav-item:focus-within .modal-nav-item-badge,
|
||||
.modal-nav-item.active .modal-nav-item-badge {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
|
||||
@@ -242,7 +242,7 @@ const AppConfigModalInner: React.FC<AppConfigModalProps> = ({
|
||||
handleNavigation(item.key);
|
||||
}
|
||||
}}
|
||||
className={`modal-nav-item ${isMobile ? "mobile" : ""}`}
|
||||
className={`modal-nav-item ${isActive ? "active" : ""} ${isMobile ? "mobile" : ""}`}
|
||||
style={{
|
||||
background: isActive
|
||||
? colors.navItemActiveBg
|
||||
@@ -259,8 +259,19 @@ const AppConfigModalInner: React.FC<AppConfigModalProps> = ({
|
||||
style={{ color }}
|
||||
/>
|
||||
{!isMobile && (
|
||||
<Group gap={4} align="center" wrap="nowrap">
|
||||
<Text size="sm" fw={500} style={{ color }}>
|
||||
<Group
|
||||
gap={4}
|
||||
align="center"
|
||||
wrap="nowrap"
|
||||
style={{ minWidth: 0, flex: 1 }}
|
||||
>
|
||||
<Text
|
||||
size="sm"
|
||||
fw={500}
|
||||
truncate
|
||||
style={{ color, minWidth: 0, flex: 1 }}
|
||||
title={item.label}
|
||||
>
|
||||
{item.label}
|
||||
</Text>
|
||||
{item.badge && (
|
||||
@@ -268,6 +279,7 @@ const AppConfigModalInner: React.FC<AppConfigModalProps> = ({
|
||||
size="xs"
|
||||
variant="light"
|
||||
color={item.badgeColor ?? "orange"}
|
||||
className="modal-nav-item-badge"
|
||||
style={{ flexShrink: 0 }}
|
||||
>
|
||||
{item.badge}
|
||||
|
||||
@@ -97,9 +97,16 @@
|
||||
order: 3;
|
||||
flex: 0 0 100%;
|
||||
height: auto;
|
||||
overflow: visible;
|
||||
padding: 4px 0;
|
||||
border-top: 1px solid var(--border-subtle);
|
||||
justify-content: flex-start;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
scrollbar-width: thin;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
.workbench-bar[data-wrapped="true"] .workbench-bar-center > * {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ---- Right: global buttons (theme / language / download) ---- */
|
||||
|
||||
@@ -492,7 +492,7 @@ const GeneralSection: React.FC<GeneralSectionProps> = ({
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t("settings.general.theme", "Theme")}
|
||||
</Text>
|
||||
@@ -527,7 +527,7 @@ const GeneralSection: React.FC<GeneralSectionProps> = ({
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t("settings.general.language", "Language")}
|
||||
</Text>
|
||||
@@ -552,7 +552,7 @@ const GeneralSection: React.FC<GeneralSectionProps> = ({
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"settings.general.defaultToolPickerMode",
|
||||
@@ -590,7 +590,7 @@ const GeneralSection: React.FC<GeneralSectionProps> = ({
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"settings.general.defaultStartupView",
|
||||
@@ -632,7 +632,7 @@ const GeneralSection: React.FC<GeneralSectionProps> = ({
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t("settings.general.defaultViewerZoom", "Default reader zoom")}
|
||||
</Text>
|
||||
@@ -687,7 +687,7 @@ const GeneralSection: React.FC<GeneralSectionProps> = ({
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"settings.general.hideUnavailableTools",
|
||||
@@ -718,7 +718,7 @@ const GeneralSection: React.FC<GeneralSectionProps> = ({
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"settings.general.hideUnavailableConversions",
|
||||
@@ -759,7 +759,7 @@ const GeneralSection: React.FC<GeneralSectionProps> = ({
|
||||
cursor: "help",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t("settings.general.autoUnzip", "Auto-unzip API responses")}
|
||||
</Text>
|
||||
@@ -796,7 +796,7 @@ const GeneralSection: React.FC<GeneralSectionProps> = ({
|
||||
cursor: "help",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"settings.general.autoUnzipFileLimit",
|
||||
|
||||
@@ -101,7 +101,7 @@ export default function ProviderCard({
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{field.label}
|
||||
</Text>
|
||||
|
||||
@@ -91,7 +91,18 @@ const OperationButton = ({
|
||||
color={color}
|
||||
data-testid={dataTestId}
|
||||
data-tour={dataTour}
|
||||
style={{ minHeight: "2.5rem", position: "relative" }}
|
||||
style={{ minHeight: "2.5rem", height: "auto", position: "relative" }}
|
||||
styles={{
|
||||
label: {
|
||||
whiteSpace: "normal",
|
||||
textAlign: "center",
|
||||
lineHeight: 1.2,
|
||||
},
|
||||
inner: {
|
||||
paddingTop: "0.25rem",
|
||||
paddingBottom: "0.25rem",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{isLoading
|
||||
? loadingText || t("loading", "Loading...")
|
||||
|
||||
@@ -4,11 +4,13 @@ import {
|
||||
Button,
|
||||
Paper,
|
||||
Group,
|
||||
Menu,
|
||||
NumberInput,
|
||||
Slider,
|
||||
} from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useViewer } from "@app/contexts/ViewerContext";
|
||||
import { useIsPhone } from "@app/hooks/useIsMobile";
|
||||
import { Tooltip } from "@app/components/shared/Tooltip";
|
||||
import FirstPageIcon from "@mui/icons-material/FirstPage";
|
||||
import ArrowBackIosIcon from "@mui/icons-material/ArrowBackIos";
|
||||
@@ -21,6 +23,7 @@ import WbSunnyIcon from "@mui/icons-material/WbSunny";
|
||||
import WbTwilightIcon from "@mui/icons-material/WbTwilight";
|
||||
import ZoomInIcon from "@mui/icons-material/ZoomIn";
|
||||
import ZoomOutIcon from "@mui/icons-material/ZoomOut";
|
||||
import MoreVertIcon from "@mui/icons-material/MoreVert";
|
||||
|
||||
interface PdfViewerToolbarProps {
|
||||
// Page navigation props (placeholders for now)
|
||||
@@ -35,6 +38,9 @@ export function PdfViewerToolbar({
|
||||
onPageChange,
|
||||
}: PdfViewerToolbarProps) {
|
||||
const { t } = useTranslation();
|
||||
const isPhone = useIsPhone();
|
||||
const buttonMinWidth = isPhone ? "3rem" : "2.5rem";
|
||||
const buttonSize = isPhone ? "lg" : "md";
|
||||
const {
|
||||
getScrollState,
|
||||
getZoomState,
|
||||
@@ -141,41 +147,45 @@ export function PdfViewerToolbar({
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flexWrap: "wrap",
|
||||
rowGap: 8,
|
||||
gap: 10,
|
||||
justifyContent: "center",
|
||||
borderTopLeftRadius: 16,
|
||||
borderTopRightRadius: 16,
|
||||
borderBottomLeftRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
boxShadow: "0 -2px 8px rgba(0,0,0,0.04)",
|
||||
pointerEvents: "auto",
|
||||
minWidth: "30rem",
|
||||
}}
|
||||
>
|
||||
{/* First Page Button */}
|
||||
<Button
|
||||
variant="subtle"
|
||||
color="blue"
|
||||
size="md"
|
||||
px={8}
|
||||
radius="xl"
|
||||
onClick={handleFirstPage}
|
||||
disabled={scrollState.currentPage === 1}
|
||||
style={{ minWidth: "2.5rem" }}
|
||||
title={t("viewer.firstPage", "First Page")}
|
||||
>
|
||||
<FirstPageIcon fontSize="small" />
|
||||
</Button>
|
||||
{!isPhone && (
|
||||
<Button
|
||||
variant="subtle"
|
||||
color="blue"
|
||||
size={buttonSize}
|
||||
px={8}
|
||||
radius="xl"
|
||||
onClick={handleFirstPage}
|
||||
disabled={scrollState.currentPage === 1}
|
||||
style={{ minWidth: buttonMinWidth }}
|
||||
title={t("viewer.firstPage", "First Page")}
|
||||
>
|
||||
<FirstPageIcon fontSize="small" />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{/* Previous Page Button */}
|
||||
<Button
|
||||
variant="subtle"
|
||||
color="blue"
|
||||
size="md"
|
||||
size={buttonSize}
|
||||
px={8}
|
||||
radius="xl"
|
||||
onClick={handlePreviousPage}
|
||||
disabled={scrollState.currentPage === 1}
|
||||
style={{ minWidth: "2.5rem" }}
|
||||
style={{ minWidth: buttonMinWidth }}
|
||||
title={t("viewer.previousPage", "Previous Page")}
|
||||
>
|
||||
<ArrowBackIosIcon fontSize="small" />
|
||||
@@ -212,137 +222,242 @@ export function PdfViewerToolbar({
|
||||
<Button
|
||||
variant="subtle"
|
||||
color="blue"
|
||||
size="md"
|
||||
size={buttonSize}
|
||||
px={8}
|
||||
radius="xl"
|
||||
onClick={handleNextPage}
|
||||
disabled={scrollState.currentPage === scrollState.totalPages}
|
||||
style={{ minWidth: "2.5rem" }}
|
||||
style={{ minWidth: buttonMinWidth }}
|
||||
title={t("viewer.nextPage", "Next Page")}
|
||||
>
|
||||
<ArrowForwardIosIcon fontSize="small" />
|
||||
</Button>
|
||||
|
||||
{/* Last Page Button */}
|
||||
<Button
|
||||
variant="subtle"
|
||||
color="blue"
|
||||
size="md"
|
||||
px={8}
|
||||
radius="xl"
|
||||
onClick={handleLastPage}
|
||||
disabled={scrollState.currentPage === scrollState.totalPages}
|
||||
style={{ minWidth: "2.5rem" }}
|
||||
title={t("viewer.lastPage", "Last Page")}
|
||||
>
|
||||
<LastPageIcon fontSize="small" />
|
||||
</Button>
|
||||
{!isPhone && (
|
||||
<Button
|
||||
variant="subtle"
|
||||
color="blue"
|
||||
size={buttonSize}
|
||||
px={8}
|
||||
radius="xl"
|
||||
onClick={handleLastPage}
|
||||
disabled={scrollState.currentPage === scrollState.totalPages}
|
||||
style={{ minWidth: buttonMinWidth }}
|
||||
title={t("viewer.lastPage", "Last Page")}
|
||||
>
|
||||
<LastPageIcon fontSize="small" />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{/* Dual Page Toggle */}
|
||||
<Tooltip
|
||||
content={
|
||||
isDualPageActive
|
||||
? t("viewer.singlePageView", "Single Page View")
|
||||
: t("viewer.dualPageView", "Dual Page View")
|
||||
}
|
||||
position="top"
|
||||
arrow
|
||||
>
|
||||
<Button
|
||||
variant={isDualPageActive ? "filled" : "light"}
|
||||
color="blue"
|
||||
size="md"
|
||||
radius="xl"
|
||||
onClick={handleDualPageToggle}
|
||||
disabled={scrollState.totalPages <= 1}
|
||||
style={{ minWidth: "2.5rem" }}
|
||||
{!isPhone && (
|
||||
<Tooltip
|
||||
content={
|
||||
isDualPageActive
|
||||
? t("viewer.singlePageView", "Single Page View")
|
||||
: t("viewer.dualPageView", "Dual Page View")
|
||||
}
|
||||
position="top"
|
||||
arrow
|
||||
>
|
||||
{isDualPageActive ? (
|
||||
<DescriptionIcon fontSize="small" />
|
||||
) : (
|
||||
<ViewWeekIcon fontSize="small" />
|
||||
)}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Button
|
||||
variant={isDualPageActive ? "filled" : "light"}
|
||||
color="blue"
|
||||
size={buttonSize}
|
||||
radius="xl"
|
||||
onClick={handleDualPageToggle}
|
||||
disabled={scrollState.totalPages <= 1}
|
||||
style={{ minWidth: buttonMinWidth }}
|
||||
>
|
||||
{isDualPageActive ? (
|
||||
<DescriptionIcon fontSize="small" />
|
||||
) : (
|
||||
<ViewWeekIcon fontSize="small" />
|
||||
)}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{/* PDF Render Mode Toggle */}
|
||||
<Tooltip
|
||||
content={
|
||||
pdfRenderMode === "normal"
|
||||
? t("viewer.enableDarkFilter", "Enable Dark Filter")
|
||||
: pdfRenderMode === "dark"
|
||||
? t("viewer.enableSepiaFilter", "Enable Sepia Filter")
|
||||
: t("viewer.disableColorFilter", "Disable Color Filter")
|
||||
}
|
||||
position="top"
|
||||
arrow
|
||||
>
|
||||
<Button
|
||||
variant={pdfRenderMode !== "normal" ? "filled" : "light"}
|
||||
color="blue"
|
||||
size="md"
|
||||
radius="xl"
|
||||
onClick={cyclePdfRenderMode}
|
||||
style={{ minWidth: "2.5rem" }}
|
||||
aria-label={
|
||||
{!isPhone && (
|
||||
<Tooltip
|
||||
content={
|
||||
pdfRenderMode === "normal"
|
||||
? t("viewer.enableDarkFilter", "Enable Dark Filter")
|
||||
: pdfRenderMode === "dark"
|
||||
? t("viewer.enableSepiaFilter", "Enable Sepia Filter")
|
||||
: t("viewer.disableColorFilter", "Disable Color Filter")
|
||||
}
|
||||
position="top"
|
||||
arrow
|
||||
>
|
||||
{pdfRenderMode === "normal" && <DarkModeIcon fontSize="small" />}
|
||||
{pdfRenderMode === "dark" && <WbTwilightIcon fontSize="small" />}
|
||||
{pdfRenderMode === "sepia" && <WbSunnyIcon fontSize="small" />}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Button
|
||||
variant={pdfRenderMode !== "normal" ? "filled" : "light"}
|
||||
color="blue"
|
||||
size={buttonSize}
|
||||
radius="xl"
|
||||
onClick={cyclePdfRenderMode}
|
||||
style={{ minWidth: buttonMinWidth }}
|
||||
aria-label={
|
||||
pdfRenderMode === "normal"
|
||||
? t("viewer.enableDarkFilter", "Enable Dark Filter")
|
||||
: pdfRenderMode === "dark"
|
||||
? t("viewer.enableSepiaFilter", "Enable Sepia Filter")
|
||||
: t("viewer.disableColorFilter", "Disable Color Filter")
|
||||
}
|
||||
>
|
||||
{pdfRenderMode === "normal" && <DarkModeIcon fontSize="small" />}
|
||||
{pdfRenderMode === "dark" && <WbTwilightIcon fontSize="small" />}
|
||||
{pdfRenderMode === "sepia" && <WbSunnyIcon fontSize="small" />}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{/* Zoom Controls */}
|
||||
<Group gap={4} align="center" style={{ marginLeft: 16 }}>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="blue"
|
||||
radius="md"
|
||||
onClick={handleZoomOut}
|
||||
aria-label={t("viewer.zoomOut", "Zoom out")}
|
||||
{/* Desktop zoom controls (slider + buttons) */}
|
||||
{!isPhone && (
|
||||
<Group
|
||||
gap={4}
|
||||
align="center"
|
||||
wrap="nowrap"
|
||||
style={{ marginLeft: 16, flexShrink: 0 }}
|
||||
>
|
||||
<ZoomOutIcon fontSize="small" />
|
||||
</ActionIcon>
|
||||
<Slider
|
||||
value={Math.min(Math.max(displayZoomPercent, 20), 500)}
|
||||
min={20}
|
||||
max={500}
|
||||
step={5}
|
||||
onChange={(val) => zoomActions.setZoomLevel?.(val / 100)}
|
||||
size="xs"
|
||||
styles={{
|
||||
root: { width: "6rem" },
|
||||
thumb: { width: 14, height: 14 },
|
||||
track: { height: 3 },
|
||||
}}
|
||||
label={null}
|
||||
/>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="blue"
|
||||
radius="md"
|
||||
onClick={handleZoomIn}
|
||||
aria-label={t("viewer.zoomIn", "Zoom in")}
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="blue"
|
||||
radius="md"
|
||||
onClick={handleZoomOut}
|
||||
aria-label={t("viewer.zoomOut", "Zoom out")}
|
||||
>
|
||||
<ZoomOutIcon fontSize="small" />
|
||||
</ActionIcon>
|
||||
<Slider
|
||||
value={Math.min(Math.max(displayZoomPercent, 20), 500)}
|
||||
min={20}
|
||||
max={500}
|
||||
step={5}
|
||||
onChange={(val) => zoomActions.setZoomLevel?.(val / 100)}
|
||||
size="xs"
|
||||
styles={{
|
||||
root: { minWidth: "6rem", width: "6rem", flexShrink: 0 },
|
||||
thumb: { width: 14, height: 14 },
|
||||
track: { height: 3 },
|
||||
}}
|
||||
label={null}
|
||||
/>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="blue"
|
||||
radius="md"
|
||||
onClick={handleZoomIn}
|
||||
aria-label={t("viewer.zoomIn", "Zoom in")}
|
||||
>
|
||||
<ZoomInIcon fontSize="small" />
|
||||
</ActionIcon>
|
||||
<span
|
||||
style={{
|
||||
minWidth: "2.5rem",
|
||||
textAlign: "center",
|
||||
fontSize: 12,
|
||||
color: "var(--text-muted)",
|
||||
}}
|
||||
>
|
||||
{displayZoomPercent}%
|
||||
</span>
|
||||
</Group>
|
||||
)}
|
||||
|
||||
{isPhone && (
|
||||
<Menu
|
||||
shadow="md"
|
||||
width={240}
|
||||
position="top-end"
|
||||
closeOnItemClick={false}
|
||||
>
|
||||
<ZoomInIcon fontSize="small" />
|
||||
</ActionIcon>
|
||||
<span
|
||||
style={{
|
||||
minWidth: "2.5rem",
|
||||
textAlign: "center",
|
||||
fontSize: 12,
|
||||
color: "var(--text-muted)",
|
||||
}}
|
||||
>
|
||||
{displayZoomPercent}%
|
||||
</span>
|
||||
</Group>
|
||||
<Menu.Target>
|
||||
<ActionIcon
|
||||
variant="light"
|
||||
color="blue"
|
||||
radius="md"
|
||||
size="lg"
|
||||
aria-label={t("viewer.moreOptions", "More")}
|
||||
style={{ marginLeft: 4 }}
|
||||
>
|
||||
<MoreVertIcon fontSize="small" />
|
||||
</ActionIcon>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown>
|
||||
<Menu.Label>
|
||||
{t("viewer.pageNavigation", "Page navigation")}
|
||||
</Menu.Label>
|
||||
<Menu.Item
|
||||
leftSection={<FirstPageIcon fontSize="small" />}
|
||||
disabled={scrollState.currentPage === 1}
|
||||
onClick={handleFirstPage}
|
||||
>
|
||||
{t("viewer.firstPage", "First page")}
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
leftSection={<LastPageIcon fontSize="small" />}
|
||||
disabled={scrollState.currentPage === scrollState.totalPages}
|
||||
onClick={handleLastPage}
|
||||
>
|
||||
{t("viewer.lastPage", "Last page")}
|
||||
</Menu.Item>
|
||||
|
||||
<Menu.Divider />
|
||||
<Menu.Label>{t("viewer.zoom", "Zoom")}</Menu.Label>
|
||||
<Menu.Item
|
||||
leftSection={<ZoomOutIcon fontSize="small" />}
|
||||
onClick={handleZoomOut}
|
||||
>
|
||||
{t("viewer.zoomOut", "Zoom out")}
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
leftSection={<ZoomInIcon fontSize="small" />}
|
||||
onClick={handleZoomIn}
|
||||
>
|
||||
{t("viewer.zoomIn", "Zoom in")} ({displayZoomPercent}%)
|
||||
</Menu.Item>
|
||||
|
||||
<Menu.Divider />
|
||||
<Menu.Label>{t("viewer.view", "View")}</Menu.Label>
|
||||
<Menu.Item
|
||||
leftSection={
|
||||
isDualPageActive ? (
|
||||
<DescriptionIcon fontSize="small" />
|
||||
) : (
|
||||
<ViewWeekIcon fontSize="small" />
|
||||
)
|
||||
}
|
||||
disabled={scrollState.totalPages <= 1}
|
||||
onClick={handleDualPageToggle}
|
||||
>
|
||||
{isDualPageActive
|
||||
? t("viewer.singlePageView", "Single Page View")
|
||||
: t("viewer.dualPageView", "Dual Page View")}
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
leftSection={
|
||||
pdfRenderMode === "normal" ? (
|
||||
<DarkModeIcon fontSize="small" />
|
||||
) : pdfRenderMode === "dark" ? (
|
||||
<WbTwilightIcon fontSize="small" />
|
||||
) : (
|
||||
<WbSunnyIcon fontSize="small" />
|
||||
)
|
||||
}
|
||||
onClick={cyclePdfRenderMode}
|
||||
>
|
||||
{pdfRenderMode === "normal"
|
||||
? t("viewer.enableDarkFilter", "Enable Dark Filter")
|
||||
: pdfRenderMode === "dark"
|
||||
? t("viewer.enableSepiaFilter", "Enable Sepia Filter")
|
||||
: t("viewer.disableColorFilter", "Disable Color Filter")}
|
||||
</Menu.Item>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
)}
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,3 +15,11 @@ export const useIsMobile = (): boolean => {
|
||||
export const useIsPhone = (): boolean => {
|
||||
return useMediaQuery("(max-width: 768px)") ?? false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Custom hook to detect a coarse pointer (touch device)
|
||||
* Use to gate touch-only UI hints when combined with useIsMobile
|
||||
*/
|
||||
export const useIsTouch = (): boolean => {
|
||||
return useMediaQuery("(pointer: coarse)") ?? false;
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Group } from "@mantine/core";
|
||||
import { useSidebarContext } from "@app/contexts/SidebarContext";
|
||||
import { useDocumentMeta } from "@app/hooks/useDocumentMeta";
|
||||
import { useBaseUrl } from "@app/hooks/useBaseUrl";
|
||||
import { useIsMobile } from "@app/hooks/useIsMobile";
|
||||
import { useIsMobile, useIsTouch } from "@app/hooks/useIsMobile";
|
||||
import { useAppConfig } from "@app/contexts/AppConfigContext";
|
||||
import { LogoIcon } from "@app/components/shared/LogoIcon";
|
||||
import { Wordmark } from "@app/components/shared/Wordmark";
|
||||
@@ -84,6 +84,7 @@ export default function HomePage() {
|
||||
const navigate = useNavigate();
|
||||
const { config } = useAppConfig();
|
||||
const isMobile = useIsMobile();
|
||||
const isTouch = useIsTouch();
|
||||
const sliderRef = useRef<HTMLDivElement | null>(null);
|
||||
const [activeMobileView, setActiveMobileView] = useState<MobileView>("tools");
|
||||
const isProgrammaticScroll = useRef(false);
|
||||
@@ -352,12 +353,14 @@ export default function HomePage() {
|
||||
{t("home.mobile.workspace", "Workspace")}
|
||||
</button>
|
||||
</div>
|
||||
<span className="mobile-toggle-hint">
|
||||
{t(
|
||||
"home.mobile.swipeHint",
|
||||
"Swipe left or right to switch views",
|
||||
)}
|
||||
</span>
|
||||
{isTouch && (
|
||||
<span className="mobile-toggle-hint">
|
||||
{t(
|
||||
"home.mobile.swipeHint",
|
||||
"Swipe left or right to switch views",
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{navigationState.workbench === "myFiles" ? (
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
import { Text, Button } from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAuth } from "@app/auth/UseSession";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
export function OverviewHeader() {
|
||||
const { t } = useTranslation();
|
||||
const { signOut, user } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
await signOut();
|
||||
navigate("/login");
|
||||
} catch (error) {
|
||||
console.error("Logout error:", error);
|
||||
} finally {
|
||||
window.location.assign("/login");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+5
-5
@@ -503,7 +503,7 @@ export default function AdminAdvancedSection() {
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"admin.settings.advanced.enableAlphaFunctionality.label",
|
||||
@@ -543,7 +543,7 @@ export default function AdminAdvancedSection() {
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"admin.settings.advanced.enableUrlToPDF.label",
|
||||
@@ -581,7 +581,7 @@ export default function AdminAdvancedSection() {
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"admin.settings.advanced.disableSanitize.label",
|
||||
@@ -959,7 +959,7 @@ export default function AdminAdvancedSection() {
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"admin.settings.advanced.tempFileManagement.startupCleanup.label",
|
||||
@@ -1002,7 +1002,7 @@ export default function AdminAdvancedSection() {
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"admin.settings.advanced.tempFileManagement.cleanupSystemTemp.label",
|
||||
|
||||
+2
-2
@@ -603,7 +603,7 @@ export default function AdminConnectionsSection() {
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"admin.settings.connections.ssoAutoLogin.enable",
|
||||
@@ -674,7 +674,7 @@ export default function AdminConnectionsSection() {
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"admin.settings.connections.mobileScanner.enable",
|
||||
|
||||
+1
-1
@@ -496,7 +496,7 @@ export default function AdminDatabaseSection() {
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"admin.settings.database.enableCustom.label",
|
||||
|
||||
+2
-2
@@ -200,7 +200,7 @@ export default function AdminFeaturesSection() {
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"admin.settings.features.serverCertificate.enabled.label",
|
||||
@@ -316,7 +316,7 @@ export default function AdminFeaturesSection() {
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"admin.settings.features.serverCertificate.regenerateOnStartup.label",
|
||||
|
||||
+6
-6
@@ -703,7 +703,7 @@ export default function AdminGeneralSection() {
|
||||
marginBottom: "1rem",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"admin.settings.general.hideDisabledTools.googleDrive.label",
|
||||
@@ -747,7 +747,7 @@ export default function AdminGeneralSection() {
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"admin.settings.general.hideDisabledTools.mobileScanner.label",
|
||||
@@ -793,7 +793,7 @@ export default function AdminGeneralSection() {
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"admin.settings.general.showUpdate.label",
|
||||
@@ -832,7 +832,7 @@ export default function AdminGeneralSection() {
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"admin.settings.general.showUpdateOnlyAdmin.label",
|
||||
@@ -873,7 +873,7 @@ export default function AdminGeneralSection() {
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"admin.settings.general.customHTMLFiles.label",
|
||||
@@ -938,7 +938,7 @@ export default function AdminGeneralSection() {
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"admin.settings.general.customMetadata.autoUpdate.label",
|
||||
|
||||
+1
-1
@@ -197,7 +197,7 @@ export default function AdminPremiumSection() {
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"admin.settings.premium.enabled.label",
|
||||
|
||||
+3
-3
@@ -165,7 +165,7 @@ export default function AdminPrivacySection() {
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"admin.settings.privacy.enableAnalytics.label",
|
||||
@@ -203,7 +203,7 @@ export default function AdminPrivacySection() {
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"admin.settings.privacy.metricsEnabled.label",
|
||||
@@ -253,7 +253,7 @@ export default function AdminPrivacySection() {
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"admin.settings.privacy.googleVisibility.label",
|
||||
|
||||
+14
-14
@@ -292,7 +292,7 @@ export default function AdminSecuritySection() {
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"admin.settings.security.enableLogin.label",
|
||||
@@ -519,7 +519,7 @@ export default function AdminSecuritySection() {
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"admin.settings.security.jwt.persistence.label",
|
||||
@@ -556,7 +556,7 @@ export default function AdminSecuritySection() {
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"admin.settings.security.jwt.enableKeyRotation.label",
|
||||
@@ -596,7 +596,7 @@ export default function AdminSecuritySection() {
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"admin.settings.security.jwt.enableKeyCleanup.label",
|
||||
@@ -780,7 +780,7 @@ export default function AdminSecuritySection() {
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"admin.settings.security.jwt.secureCookie.label",
|
||||
@@ -840,7 +840,7 @@ export default function AdminSecuritySection() {
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"admin.settings.security.audit.enabled.label",
|
||||
@@ -955,7 +955,7 @@ export default function AdminSecuritySection() {
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"admin.settings.security.audit.captureFileHash.label",
|
||||
@@ -995,7 +995,7 @@ export default function AdminSecuritySection() {
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"admin.settings.security.audit.capturePdfAuthor.label",
|
||||
@@ -1035,7 +1035,7 @@ export default function AdminSecuritySection() {
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"admin.settings.security.audit.captureOperationResults.label",
|
||||
@@ -1097,7 +1097,7 @@ export default function AdminSecuritySection() {
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"admin.settings.security.htmlUrlSecurity.enabled.label",
|
||||
@@ -1374,7 +1374,7 @@ export default function AdminSecuritySection() {
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"admin.settings.security.htmlUrlSecurity.blockPrivateNetworks.label",
|
||||
@@ -1424,7 +1424,7 @@ export default function AdminSecuritySection() {
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"admin.settings.security.htmlUrlSecurity.blockLocalhost.label",
|
||||
@@ -1473,7 +1473,7 @@ export default function AdminSecuritySection() {
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"admin.settings.security.htmlUrlSecurity.blockLinkLocal.label",
|
||||
@@ -1522,7 +1522,7 @@ export default function AdminSecuritySection() {
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} size="sm">
|
||||
{t(
|
||||
"admin.settings.security.htmlUrlSecurity.blockCloudMetadata.label",
|
||||
|
||||
+10
-3
@@ -585,9 +585,12 @@ export default function PeopleSection() {
|
||||
{t("workspace.people.user")}
|
||||
</Table.Th>
|
||||
<Table.Th
|
||||
style={{ fontWeight: 600, color: "var(--mantine-color-gray-7)" }}
|
||||
style={{
|
||||
fontWeight: 600,
|
||||
color: "var(--mantine-color-gray-7)",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
fz="sm"
|
||||
w={100}
|
||||
>
|
||||
{t("workspace.people.role")}
|
||||
</Table.Th>
|
||||
@@ -690,7 +693,7 @@ export default function PeopleSection() {
|
||||
</Box>
|
||||
</Group>
|
||||
</Table.Td>
|
||||
<Table.Td w={100}>
|
||||
<Table.Td style={{ whiteSpace: "nowrap" }}>
|
||||
<Badge
|
||||
size="sm"
|
||||
variant="light"
|
||||
@@ -699,6 +702,10 @@ export default function PeopleSection() {
|
||||
? "blue"
|
||||
: "cyan"
|
||||
}
|
||||
styles={{
|
||||
root: { maxWidth: "none" },
|
||||
label: { overflow: "visible" },
|
||||
}}
|
||||
>
|
||||
{(user.rolesAsString || "").includes("ROLE_ADMIN")
|
||||
? t("workspace.people.admin", "Admin")
|
||||
|
||||
Reference in New Issue
Block a user