mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-16 19:33:11 +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"
|
enableSepiaFilter = "Enable Sepia Filter"
|
||||||
firstPage = "First Page"
|
firstPage = "First Page"
|
||||||
lastPage = "Last Page"
|
lastPage = "Last Page"
|
||||||
|
moreOptions = "More"
|
||||||
nextPage = "Next Page"
|
nextPage = "Next Page"
|
||||||
onlyPdfSupported = "This file format is not supported for preview."
|
onlyPdfSupported = "This file format is not supported for preview."
|
||||||
|
pageNavigation = "Page navigation"
|
||||||
previousPage = "Previous Page"
|
previousPage = "Previous Page"
|
||||||
resetZoom = "Reset zoom"
|
resetZoom = "Reset zoom"
|
||||||
singlePageView = "Single Page View"
|
singlePageView = "Single Page View"
|
||||||
unknownFile = "Unknown file"
|
unknownFile = "Unknown file"
|
||||||
|
view = "View"
|
||||||
|
zoom = "Zoom"
|
||||||
zoomIn = "Zoom In"
|
zoomIn = "Zoom In"
|
||||||
zoomOut = "Zoom Out"
|
zoomOut = "Zoom Out"
|
||||||
|
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ const AddFileCard = ({
|
|||||||
}}
|
}}
|
||||||
onMouseLeave={() => setIsUploadHover(false)}
|
onMouseLeave={() => setIsUploadHover(false)}
|
||||||
>
|
>
|
||||||
|
{!isUploadHover && (
|
||||||
<Button
|
<Button
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: "var(--landing-button-bg)",
|
backgroundColor: "var(--landing-button-bg)",
|
||||||
@@ -111,10 +112,9 @@ const AddFileCard = ({
|
|||||||
border: "1px solid var(--landing-button-border)",
|
border: "1px solid var(--landing-button-border)",
|
||||||
borderRadius: "2rem",
|
borderRadius: "2rem",
|
||||||
height: "38px",
|
height: "38px",
|
||||||
paddingLeft: isUploadHover ? 0 : "1rem",
|
paddingLeft: "1rem",
|
||||||
paddingRight: isUploadHover ? 0 : "1rem",
|
paddingRight: "1rem",
|
||||||
width: isUploadHover ? "58px" : "calc(100% - 58px - 0.6rem)",
|
width: "calc(100% - 58px - 0.6rem)",
|
||||||
minWidth: isUploadHover ? "58px" : undefined,
|
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
@@ -129,19 +129,19 @@ const AddFileCard = ({
|
|||||||
height="1.5rem"
|
height="1.5rem"
|
||||||
className="text-[var(--accent-interactive)]"
|
className="text-[var(--accent-interactive)]"
|
||||||
/>
|
/>
|
||||||
{!isUploadHover && (
|
|
||||||
<span>{t("landing.addFiles", "Add Files")}</span>
|
<span>{t("landing.addFiles", "Add Files")}</span>
|
||||||
)}
|
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
<Button
|
<Button
|
||||||
aria-label={t("addFileCard.upload", "Upload")}
|
aria-label={t("addFileCard.upload", "Upload")}
|
||||||
|
title={terminology.uploadFromComputer}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: "var(--landing-button-bg)",
|
backgroundColor: "var(--landing-button-bg)",
|
||||||
color: "var(--landing-button-color)",
|
color: "var(--landing-button-color)",
|
||||||
border: "1px solid var(--landing-button-border)",
|
border: "1px solid var(--landing-button-border)",
|
||||||
borderRadius: "1rem",
|
borderRadius: "1rem",
|
||||||
height: "38px",
|
height: "38px",
|
||||||
width: isUploadHover ? "calc(100% - 58px - 0.6rem)" : "58px",
|
width: isUploadHover ? "100%" : "58px",
|
||||||
minWidth: "58px",
|
minWidth: "58px",
|
||||||
paddingLeft: isUploadHover ? "1rem" : 0,
|
paddingLeft: isUploadHover ? "1rem" : 0,
|
||||||
paddingRight: isUploadHover ? "1rem" : 0,
|
paddingRight: isUploadHover ? "1rem" : 0,
|
||||||
@@ -149,6 +149,7 @@ const AddFileCard = ({
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
transition: "width .5s ease, padding .5s ease",
|
transition: "width .5s ease, padding .5s ease",
|
||||||
|
overflow: "hidden",
|
||||||
}}
|
}}
|
||||||
onClick={handleNativeUploadClick}
|
onClick={handleNativeUploadClick}
|
||||||
onMouseEnter={() => setIsUploadHover(true)}
|
onMouseEnter={() => setIsUploadHover(true)}
|
||||||
@@ -157,10 +158,18 @@ const AddFileCard = ({
|
|||||||
icon={icons.uploadIconName}
|
icon={icons.uploadIconName}
|
||||||
width="1.25rem"
|
width="1.25rem"
|
||||||
height="1.25rem"
|
height="1.25rem"
|
||||||
style={{ color: "var(--accent-interactive)" }}
|
style={{ color: "var(--accent-interactive)", flexShrink: 0 }}
|
||||||
/>
|
/>
|
||||||
{isUploadHover && (
|
{isUploadHover && (
|
||||||
<span style={{ marginLeft: ".5rem" }}>
|
<span
|
||||||
|
style={{
|
||||||
|
marginLeft: ".5rem",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
minWidth: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{terminology.uploadFromComputer}
|
{terminology.uploadFromComputer}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -106,6 +106,15 @@
|
|||||||
margin-bottom: 0.25rem; /* 4px */
|
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 {
|
.modal-content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -242,7 +242,7 @@ const AppConfigModalInner: React.FC<AppConfigModalProps> = ({
|
|||||||
handleNavigation(item.key);
|
handleNavigation(item.key);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className={`modal-nav-item ${isMobile ? "mobile" : ""}`}
|
className={`modal-nav-item ${isActive ? "active" : ""} ${isMobile ? "mobile" : ""}`}
|
||||||
style={{
|
style={{
|
||||||
background: isActive
|
background: isActive
|
||||||
? colors.navItemActiveBg
|
? colors.navItemActiveBg
|
||||||
@@ -259,8 +259,19 @@ const AppConfigModalInner: React.FC<AppConfigModalProps> = ({
|
|||||||
style={{ color }}
|
style={{ color }}
|
||||||
/>
|
/>
|
||||||
{!isMobile && (
|
{!isMobile && (
|
||||||
<Group gap={4} align="center" wrap="nowrap">
|
<Group
|
||||||
<Text size="sm" fw={500} style={{ color }}>
|
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}
|
{item.label}
|
||||||
</Text>
|
</Text>
|
||||||
{item.badge && (
|
{item.badge && (
|
||||||
@@ -268,6 +279,7 @@ const AppConfigModalInner: React.FC<AppConfigModalProps> = ({
|
|||||||
size="xs"
|
size="xs"
|
||||||
variant="light"
|
variant="light"
|
||||||
color={item.badgeColor ?? "orange"}
|
color={item.badgeColor ?? "orange"}
|
||||||
|
className="modal-nav-item-badge"
|
||||||
style={{ flexShrink: 0 }}
|
style={{ flexShrink: 0 }}
|
||||||
>
|
>
|
||||||
{item.badge}
|
{item.badge}
|
||||||
|
|||||||
@@ -97,9 +97,16 @@
|
|||||||
order: 3;
|
order: 3;
|
||||||
flex: 0 0 100%;
|
flex: 0 0 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
overflow: visible;
|
|
||||||
padding: 4px 0;
|
padding: 4px 0;
|
||||||
border-top: 1px solid var(--border-subtle);
|
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) ---- */
|
/* ---- Right: global buttons (theme / language / download) ---- */
|
||||||
|
|||||||
@@ -492,7 +492,7 @@ const GeneralSection: React.FC<GeneralSectionProps> = ({
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t("settings.general.theme", "Theme")}
|
{t("settings.general.theme", "Theme")}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -527,7 +527,7 @@ const GeneralSection: React.FC<GeneralSectionProps> = ({
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t("settings.general.language", "Language")}
|
{t("settings.general.language", "Language")}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -552,7 +552,7 @@ const GeneralSection: React.FC<GeneralSectionProps> = ({
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"settings.general.defaultToolPickerMode",
|
"settings.general.defaultToolPickerMode",
|
||||||
@@ -590,7 +590,7 @@ const GeneralSection: React.FC<GeneralSectionProps> = ({
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"settings.general.defaultStartupView",
|
"settings.general.defaultStartupView",
|
||||||
@@ -632,7 +632,7 @@ const GeneralSection: React.FC<GeneralSectionProps> = ({
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t("settings.general.defaultViewerZoom", "Default reader zoom")}
|
{t("settings.general.defaultViewerZoom", "Default reader zoom")}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -687,7 +687,7 @@ const GeneralSection: React.FC<GeneralSectionProps> = ({
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"settings.general.hideUnavailableTools",
|
"settings.general.hideUnavailableTools",
|
||||||
@@ -718,7 +718,7 @@ const GeneralSection: React.FC<GeneralSectionProps> = ({
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"settings.general.hideUnavailableConversions",
|
"settings.general.hideUnavailableConversions",
|
||||||
@@ -759,7 +759,7 @@ const GeneralSection: React.FC<GeneralSectionProps> = ({
|
|||||||
cursor: "help",
|
cursor: "help",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t("settings.general.autoUnzip", "Auto-unzip API responses")}
|
{t("settings.general.autoUnzip", "Auto-unzip API responses")}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -796,7 +796,7 @@ const GeneralSection: React.FC<GeneralSectionProps> = ({
|
|||||||
cursor: "help",
|
cursor: "help",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"settings.general.autoUnzipFileLimit",
|
"settings.general.autoUnzipFileLimit",
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ export default function ProviderCard({
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{field.label}
|
{field.label}
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -91,7 +91,18 @@ const OperationButton = ({
|
|||||||
color={color}
|
color={color}
|
||||||
data-testid={dataTestId}
|
data-testid={dataTestId}
|
||||||
data-tour={dataTour}
|
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
|
{isLoading
|
||||||
? loadingText || t("loading", "Loading...")
|
? loadingText || t("loading", "Loading...")
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Paper,
|
Paper,
|
||||||
Group,
|
Group,
|
||||||
|
Menu,
|
||||||
NumberInput,
|
NumberInput,
|
||||||
Slider,
|
Slider,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useViewer } from "@app/contexts/ViewerContext";
|
import { useViewer } from "@app/contexts/ViewerContext";
|
||||||
|
import { useIsPhone } from "@app/hooks/useIsMobile";
|
||||||
import { Tooltip } from "@app/components/shared/Tooltip";
|
import { Tooltip } from "@app/components/shared/Tooltip";
|
||||||
import FirstPageIcon from "@mui/icons-material/FirstPage";
|
import FirstPageIcon from "@mui/icons-material/FirstPage";
|
||||||
import ArrowBackIosIcon from "@mui/icons-material/ArrowBackIos";
|
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 WbTwilightIcon from "@mui/icons-material/WbTwilight";
|
||||||
import ZoomInIcon from "@mui/icons-material/ZoomIn";
|
import ZoomInIcon from "@mui/icons-material/ZoomIn";
|
||||||
import ZoomOutIcon from "@mui/icons-material/ZoomOut";
|
import ZoomOutIcon from "@mui/icons-material/ZoomOut";
|
||||||
|
import MoreVertIcon from "@mui/icons-material/MoreVert";
|
||||||
|
|
||||||
interface PdfViewerToolbarProps {
|
interface PdfViewerToolbarProps {
|
||||||
// Page navigation props (placeholders for now)
|
// Page navigation props (placeholders for now)
|
||||||
@@ -35,6 +38,9 @@ export function PdfViewerToolbar({
|
|||||||
onPageChange,
|
onPageChange,
|
||||||
}: PdfViewerToolbarProps) {
|
}: PdfViewerToolbarProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const isPhone = useIsPhone();
|
||||||
|
const buttonMinWidth = isPhone ? "3rem" : "2.5rem";
|
||||||
|
const buttonSize = isPhone ? "lg" : "md";
|
||||||
const {
|
const {
|
||||||
getScrollState,
|
getScrollState,
|
||||||
getZoomState,
|
getZoomState,
|
||||||
@@ -141,41 +147,45 @@ export function PdfViewerToolbar({
|
|||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
rowGap: 8,
|
||||||
gap: 10,
|
gap: 10,
|
||||||
|
justifyContent: "center",
|
||||||
borderTopLeftRadius: 16,
|
borderTopLeftRadius: 16,
|
||||||
borderTopRightRadius: 16,
|
borderTopRightRadius: 16,
|
||||||
borderBottomLeftRadius: 0,
|
borderBottomLeftRadius: 0,
|
||||||
borderBottomRightRadius: 0,
|
borderBottomRightRadius: 0,
|
||||||
boxShadow: "0 -2px 8px rgba(0,0,0,0.04)",
|
boxShadow: "0 -2px 8px rgba(0,0,0,0.04)",
|
||||||
pointerEvents: "auto",
|
pointerEvents: "auto",
|
||||||
minWidth: "30rem",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* First Page Button */}
|
{/* First Page Button */}
|
||||||
|
{!isPhone && (
|
||||||
<Button
|
<Button
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
color="blue"
|
color="blue"
|
||||||
size="md"
|
size={buttonSize}
|
||||||
px={8}
|
px={8}
|
||||||
radius="xl"
|
radius="xl"
|
||||||
onClick={handleFirstPage}
|
onClick={handleFirstPage}
|
||||||
disabled={scrollState.currentPage === 1}
|
disabled={scrollState.currentPage === 1}
|
||||||
style={{ minWidth: "2.5rem" }}
|
style={{ minWidth: buttonMinWidth }}
|
||||||
title={t("viewer.firstPage", "First Page")}
|
title={t("viewer.firstPage", "First Page")}
|
||||||
>
|
>
|
||||||
<FirstPageIcon fontSize="small" />
|
<FirstPageIcon fontSize="small" />
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Previous Page Button */}
|
{/* Previous Page Button */}
|
||||||
<Button
|
<Button
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
color="blue"
|
color="blue"
|
||||||
size="md"
|
size={buttonSize}
|
||||||
px={8}
|
px={8}
|
||||||
radius="xl"
|
radius="xl"
|
||||||
onClick={handlePreviousPage}
|
onClick={handlePreviousPage}
|
||||||
disabled={scrollState.currentPage === 1}
|
disabled={scrollState.currentPage === 1}
|
||||||
style={{ minWidth: "2.5rem" }}
|
style={{ minWidth: buttonMinWidth }}
|
||||||
title={t("viewer.previousPage", "Previous Page")}
|
title={t("viewer.previousPage", "Previous Page")}
|
||||||
>
|
>
|
||||||
<ArrowBackIosIcon fontSize="small" />
|
<ArrowBackIosIcon fontSize="small" />
|
||||||
@@ -212,33 +222,36 @@ export function PdfViewerToolbar({
|
|||||||
<Button
|
<Button
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
color="blue"
|
color="blue"
|
||||||
size="md"
|
size={buttonSize}
|
||||||
px={8}
|
px={8}
|
||||||
radius="xl"
|
radius="xl"
|
||||||
onClick={handleNextPage}
|
onClick={handleNextPage}
|
||||||
disabled={scrollState.currentPage === scrollState.totalPages}
|
disabled={scrollState.currentPage === scrollState.totalPages}
|
||||||
style={{ minWidth: "2.5rem" }}
|
style={{ minWidth: buttonMinWidth }}
|
||||||
title={t("viewer.nextPage", "Next Page")}
|
title={t("viewer.nextPage", "Next Page")}
|
||||||
>
|
>
|
||||||
<ArrowForwardIosIcon fontSize="small" />
|
<ArrowForwardIosIcon fontSize="small" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{/* Last Page Button */}
|
{/* Last Page Button */}
|
||||||
|
{!isPhone && (
|
||||||
<Button
|
<Button
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
color="blue"
|
color="blue"
|
||||||
size="md"
|
size={buttonSize}
|
||||||
px={8}
|
px={8}
|
||||||
radius="xl"
|
radius="xl"
|
||||||
onClick={handleLastPage}
|
onClick={handleLastPage}
|
||||||
disabled={scrollState.currentPage === scrollState.totalPages}
|
disabled={scrollState.currentPage === scrollState.totalPages}
|
||||||
style={{ minWidth: "2.5rem" }}
|
style={{ minWidth: buttonMinWidth }}
|
||||||
title={t("viewer.lastPage", "Last Page")}
|
title={t("viewer.lastPage", "Last Page")}
|
||||||
>
|
>
|
||||||
<LastPageIcon fontSize="small" />
|
<LastPageIcon fontSize="small" />
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Dual Page Toggle */}
|
{/* Dual Page Toggle */}
|
||||||
|
{!isPhone && (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content={
|
content={
|
||||||
isDualPageActive
|
isDualPageActive
|
||||||
@@ -251,11 +264,11 @@ export function PdfViewerToolbar({
|
|||||||
<Button
|
<Button
|
||||||
variant={isDualPageActive ? "filled" : "light"}
|
variant={isDualPageActive ? "filled" : "light"}
|
||||||
color="blue"
|
color="blue"
|
||||||
size="md"
|
size={buttonSize}
|
||||||
radius="xl"
|
radius="xl"
|
||||||
onClick={handleDualPageToggle}
|
onClick={handleDualPageToggle}
|
||||||
disabled={scrollState.totalPages <= 1}
|
disabled={scrollState.totalPages <= 1}
|
||||||
style={{ minWidth: "2.5rem" }}
|
style={{ minWidth: buttonMinWidth }}
|
||||||
>
|
>
|
||||||
{isDualPageActive ? (
|
{isDualPageActive ? (
|
||||||
<DescriptionIcon fontSize="small" />
|
<DescriptionIcon fontSize="small" />
|
||||||
@@ -264,8 +277,10 @@ export function PdfViewerToolbar({
|
|||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* PDF Render Mode Toggle */}
|
{/* PDF Render Mode Toggle */}
|
||||||
|
{!isPhone && (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content={
|
content={
|
||||||
pdfRenderMode === "normal"
|
pdfRenderMode === "normal"
|
||||||
@@ -280,10 +295,10 @@ export function PdfViewerToolbar({
|
|||||||
<Button
|
<Button
|
||||||
variant={pdfRenderMode !== "normal" ? "filled" : "light"}
|
variant={pdfRenderMode !== "normal" ? "filled" : "light"}
|
||||||
color="blue"
|
color="blue"
|
||||||
size="md"
|
size={buttonSize}
|
||||||
radius="xl"
|
radius="xl"
|
||||||
onClick={cyclePdfRenderMode}
|
onClick={cyclePdfRenderMode}
|
||||||
style={{ minWidth: "2.5rem" }}
|
style={{ minWidth: buttonMinWidth }}
|
||||||
aria-label={
|
aria-label={
|
||||||
pdfRenderMode === "normal"
|
pdfRenderMode === "normal"
|
||||||
? t("viewer.enableDarkFilter", "Enable Dark Filter")
|
? t("viewer.enableDarkFilter", "Enable Dark Filter")
|
||||||
@@ -297,9 +312,16 @@ export function PdfViewerToolbar({
|
|||||||
{pdfRenderMode === "sepia" && <WbSunnyIcon fontSize="small" />}
|
{pdfRenderMode === "sepia" && <WbSunnyIcon fontSize="small" />}
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Zoom Controls */}
|
{/* Desktop zoom controls (slider + buttons) */}
|
||||||
<Group gap={4} align="center" style={{ marginLeft: 16 }}>
|
{!isPhone && (
|
||||||
|
<Group
|
||||||
|
gap={4}
|
||||||
|
align="center"
|
||||||
|
wrap="nowrap"
|
||||||
|
style={{ marginLeft: 16, flexShrink: 0 }}
|
||||||
|
>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
color="blue"
|
color="blue"
|
||||||
@@ -317,7 +339,7 @@ export function PdfViewerToolbar({
|
|||||||
onChange={(val) => zoomActions.setZoomLevel?.(val / 100)}
|
onChange={(val) => zoomActions.setZoomLevel?.(val / 100)}
|
||||||
size="xs"
|
size="xs"
|
||||||
styles={{
|
styles={{
|
||||||
root: { width: "6rem" },
|
root: { minWidth: "6rem", width: "6rem", flexShrink: 0 },
|
||||||
thumb: { width: 14, height: 14 },
|
thumb: { width: 14, height: 14 },
|
||||||
track: { height: 3 },
|
track: { height: 3 },
|
||||||
}}
|
}}
|
||||||
@@ -343,6 +365,99 @@ export function PdfViewerToolbar({
|
|||||||
{displayZoomPercent}%
|
{displayZoomPercent}%
|
||||||
</span>
|
</span>
|
||||||
</Group>
|
</Group>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isPhone && (
|
||||||
|
<Menu
|
||||||
|
shadow="md"
|
||||||
|
width={240}
|
||||||
|
position="top-end"
|
||||||
|
closeOnItemClick={false}
|
||||||
|
>
|
||||||
|
<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>
|
</Paper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,3 +15,11 @@ export const useIsMobile = (): boolean => {
|
|||||||
export const useIsPhone = (): boolean => {
|
export const useIsPhone = (): boolean => {
|
||||||
return useMediaQuery("(max-width: 768px)") ?? false;
|
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 { useSidebarContext } from "@app/contexts/SidebarContext";
|
||||||
import { useDocumentMeta } from "@app/hooks/useDocumentMeta";
|
import { useDocumentMeta } from "@app/hooks/useDocumentMeta";
|
||||||
import { useBaseUrl } from "@app/hooks/useBaseUrl";
|
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 { useAppConfig } from "@app/contexts/AppConfigContext";
|
||||||
import { LogoIcon } from "@app/components/shared/LogoIcon";
|
import { LogoIcon } from "@app/components/shared/LogoIcon";
|
||||||
import { Wordmark } from "@app/components/shared/Wordmark";
|
import { Wordmark } from "@app/components/shared/Wordmark";
|
||||||
@@ -84,6 +84,7 @@ export default function HomePage() {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { config } = useAppConfig();
|
const { config } = useAppConfig();
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
const isTouch = useIsTouch();
|
||||||
const sliderRef = useRef<HTMLDivElement | null>(null);
|
const sliderRef = useRef<HTMLDivElement | null>(null);
|
||||||
const [activeMobileView, setActiveMobileView] = useState<MobileView>("tools");
|
const [activeMobileView, setActiveMobileView] = useState<MobileView>("tools");
|
||||||
const isProgrammaticScroll = useRef(false);
|
const isProgrammaticScroll = useRef(false);
|
||||||
@@ -352,12 +353,14 @@ export default function HomePage() {
|
|||||||
{t("home.mobile.workspace", "Workspace")}
|
{t("home.mobile.workspace", "Workspace")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
{isTouch && (
|
||||||
<span className="mobile-toggle-hint">
|
<span className="mobile-toggle-hint">
|
||||||
{t(
|
{t(
|
||||||
"home.mobile.swipeHint",
|
"home.mobile.swipeHint",
|
||||||
"Swipe left or right to switch views",
|
"Swipe left or right to switch views",
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{navigationState.workbench === "myFiles" ? (
|
{navigationState.workbench === "myFiles" ? (
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
import { Text, Button } from "@mantine/core";
|
import { Text, Button } from "@mantine/core";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useAuth } from "@app/auth/UseSession";
|
import { useAuth } from "@app/auth/UseSession";
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
|
|
||||||
export function OverviewHeader() {
|
export function OverviewHeader() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { signOut, user } = useAuth();
|
const { signOut, user } = useAuth();
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
try {
|
try {
|
||||||
await signOut();
|
await signOut();
|
||||||
navigate("/login");
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Logout error:", error);
|
console.error("Logout error:", error);
|
||||||
|
} finally {
|
||||||
|
window.location.assign("/login");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -503,7 +503,7 @@ export default function AdminAdvancedSection() {
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"admin.settings.advanced.enableAlphaFunctionality.label",
|
"admin.settings.advanced.enableAlphaFunctionality.label",
|
||||||
@@ -543,7 +543,7 @@ export default function AdminAdvancedSection() {
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"admin.settings.advanced.enableUrlToPDF.label",
|
"admin.settings.advanced.enableUrlToPDF.label",
|
||||||
@@ -581,7 +581,7 @@ export default function AdminAdvancedSection() {
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"admin.settings.advanced.disableSanitize.label",
|
"admin.settings.advanced.disableSanitize.label",
|
||||||
@@ -959,7 +959,7 @@ export default function AdminAdvancedSection() {
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"admin.settings.advanced.tempFileManagement.startupCleanup.label",
|
"admin.settings.advanced.tempFileManagement.startupCleanup.label",
|
||||||
@@ -1002,7 +1002,7 @@ export default function AdminAdvancedSection() {
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"admin.settings.advanced.tempFileManagement.cleanupSystemTemp.label",
|
"admin.settings.advanced.tempFileManagement.cleanupSystemTemp.label",
|
||||||
|
|||||||
+2
-2
@@ -603,7 +603,7 @@ export default function AdminConnectionsSection() {
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"admin.settings.connections.ssoAutoLogin.enable",
|
"admin.settings.connections.ssoAutoLogin.enable",
|
||||||
@@ -674,7 +674,7 @@ export default function AdminConnectionsSection() {
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"admin.settings.connections.mobileScanner.enable",
|
"admin.settings.connections.mobileScanner.enable",
|
||||||
|
|||||||
+1
-1
@@ -496,7 +496,7 @@ export default function AdminDatabaseSection() {
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"admin.settings.database.enableCustom.label",
|
"admin.settings.database.enableCustom.label",
|
||||||
|
|||||||
+2
-2
@@ -200,7 +200,7 @@ export default function AdminFeaturesSection() {
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"admin.settings.features.serverCertificate.enabled.label",
|
"admin.settings.features.serverCertificate.enabled.label",
|
||||||
@@ -316,7 +316,7 @@ export default function AdminFeaturesSection() {
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"admin.settings.features.serverCertificate.regenerateOnStartup.label",
|
"admin.settings.features.serverCertificate.regenerateOnStartup.label",
|
||||||
|
|||||||
+6
-6
@@ -703,7 +703,7 @@ export default function AdminGeneralSection() {
|
|||||||
marginBottom: "1rem",
|
marginBottom: "1rem",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"admin.settings.general.hideDisabledTools.googleDrive.label",
|
"admin.settings.general.hideDisabledTools.googleDrive.label",
|
||||||
@@ -747,7 +747,7 @@ export default function AdminGeneralSection() {
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"admin.settings.general.hideDisabledTools.mobileScanner.label",
|
"admin.settings.general.hideDisabledTools.mobileScanner.label",
|
||||||
@@ -793,7 +793,7 @@ export default function AdminGeneralSection() {
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"admin.settings.general.showUpdate.label",
|
"admin.settings.general.showUpdate.label",
|
||||||
@@ -832,7 +832,7 @@ export default function AdminGeneralSection() {
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"admin.settings.general.showUpdateOnlyAdmin.label",
|
"admin.settings.general.showUpdateOnlyAdmin.label",
|
||||||
@@ -873,7 +873,7 @@ export default function AdminGeneralSection() {
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"admin.settings.general.customHTMLFiles.label",
|
"admin.settings.general.customHTMLFiles.label",
|
||||||
@@ -938,7 +938,7 @@ export default function AdminGeneralSection() {
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"admin.settings.general.customMetadata.autoUpdate.label",
|
"admin.settings.general.customMetadata.autoUpdate.label",
|
||||||
|
|||||||
+1
-1
@@ -197,7 +197,7 @@ export default function AdminPremiumSection() {
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"admin.settings.premium.enabled.label",
|
"admin.settings.premium.enabled.label",
|
||||||
|
|||||||
+3
-3
@@ -165,7 +165,7 @@ export default function AdminPrivacySection() {
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"admin.settings.privacy.enableAnalytics.label",
|
"admin.settings.privacy.enableAnalytics.label",
|
||||||
@@ -203,7 +203,7 @@ export default function AdminPrivacySection() {
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"admin.settings.privacy.metricsEnabled.label",
|
"admin.settings.privacy.metricsEnabled.label",
|
||||||
@@ -253,7 +253,7 @@ export default function AdminPrivacySection() {
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"admin.settings.privacy.googleVisibility.label",
|
"admin.settings.privacy.googleVisibility.label",
|
||||||
|
|||||||
+14
-14
@@ -292,7 +292,7 @@ export default function AdminSecuritySection() {
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"admin.settings.security.enableLogin.label",
|
"admin.settings.security.enableLogin.label",
|
||||||
@@ -519,7 +519,7 @@ export default function AdminSecuritySection() {
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"admin.settings.security.jwt.persistence.label",
|
"admin.settings.security.jwt.persistence.label",
|
||||||
@@ -556,7 +556,7 @@ export default function AdminSecuritySection() {
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"admin.settings.security.jwt.enableKeyRotation.label",
|
"admin.settings.security.jwt.enableKeyRotation.label",
|
||||||
@@ -596,7 +596,7 @@ export default function AdminSecuritySection() {
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"admin.settings.security.jwt.enableKeyCleanup.label",
|
"admin.settings.security.jwt.enableKeyCleanup.label",
|
||||||
@@ -780,7 +780,7 @@ export default function AdminSecuritySection() {
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"admin.settings.security.jwt.secureCookie.label",
|
"admin.settings.security.jwt.secureCookie.label",
|
||||||
@@ -840,7 +840,7 @@ export default function AdminSecuritySection() {
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"admin.settings.security.audit.enabled.label",
|
"admin.settings.security.audit.enabled.label",
|
||||||
@@ -955,7 +955,7 @@ export default function AdminSecuritySection() {
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"admin.settings.security.audit.captureFileHash.label",
|
"admin.settings.security.audit.captureFileHash.label",
|
||||||
@@ -995,7 +995,7 @@ export default function AdminSecuritySection() {
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"admin.settings.security.audit.capturePdfAuthor.label",
|
"admin.settings.security.audit.capturePdfAuthor.label",
|
||||||
@@ -1035,7 +1035,7 @@ export default function AdminSecuritySection() {
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"admin.settings.security.audit.captureOperationResults.label",
|
"admin.settings.security.audit.captureOperationResults.label",
|
||||||
@@ -1097,7 +1097,7 @@ export default function AdminSecuritySection() {
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"admin.settings.security.htmlUrlSecurity.enabled.label",
|
"admin.settings.security.htmlUrlSecurity.enabled.label",
|
||||||
@@ -1374,7 +1374,7 @@ export default function AdminSecuritySection() {
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"admin.settings.security.htmlUrlSecurity.blockPrivateNetworks.label",
|
"admin.settings.security.htmlUrlSecurity.blockPrivateNetworks.label",
|
||||||
@@ -1424,7 +1424,7 @@ export default function AdminSecuritySection() {
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"admin.settings.security.htmlUrlSecurity.blockLocalhost.label",
|
"admin.settings.security.htmlUrlSecurity.blockLocalhost.label",
|
||||||
@@ -1473,7 +1473,7 @@ export default function AdminSecuritySection() {
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"admin.settings.security.htmlUrlSecurity.blockLinkLocal.label",
|
"admin.settings.security.htmlUrlSecurity.blockLinkLocal.label",
|
||||||
@@ -1522,7 +1522,7 @@ export default function AdminSecuritySection() {
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{t(
|
{t(
|
||||||
"admin.settings.security.htmlUrlSecurity.blockCloudMetadata.label",
|
"admin.settings.security.htmlUrlSecurity.blockCloudMetadata.label",
|
||||||
|
|||||||
+10
-3
@@ -585,9 +585,12 @@ export default function PeopleSection() {
|
|||||||
{t("workspace.people.user")}
|
{t("workspace.people.user")}
|
||||||
</Table.Th>
|
</Table.Th>
|
||||||
<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"
|
fz="sm"
|
||||||
w={100}
|
|
||||||
>
|
>
|
||||||
{t("workspace.people.role")}
|
{t("workspace.people.role")}
|
||||||
</Table.Th>
|
</Table.Th>
|
||||||
@@ -690,7 +693,7 @@ export default function PeopleSection() {
|
|||||||
</Box>
|
</Box>
|
||||||
</Group>
|
</Group>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
<Table.Td w={100}>
|
<Table.Td style={{ whiteSpace: "nowrap" }}>
|
||||||
<Badge
|
<Badge
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="light"
|
variant="light"
|
||||||
@@ -699,6 +702,10 @@ export default function PeopleSection() {
|
|||||||
? "blue"
|
? "blue"
|
||||||
: "cyan"
|
: "cyan"
|
||||||
}
|
}
|
||||||
|
styles={{
|
||||||
|
root: { maxWidth: "none" },
|
||||||
|
label: { overflow: "visible" },
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{(user.rolesAsString || "").includes("ROLE_ADMIN")
|
{(user.rolesAsString || "").includes("ROLE_ADMIN")
|
||||||
? t("workspace.people.admin", "Admin")
|
? t("workspace.people.admin", "Admin")
|
||||||
|
|||||||
Reference in New Issue
Block a user