+ {/* Preset Scales Section */}
+
+
+ {t("scaleSettings.presets", "Preset Scales")}
+
+
+ {PRESET_SCALES.map((preset) => (
+
+
+
+ ))}
+
+
+
+ {/* Custom Scale Section */}
+
+
+ {t("scaleSettings.customScale", "Custom Scale")}
+
+
+
+
+
+ e.stopPropagation()}>
+
+
+
+
+ {t(
+ "scaleSettings.ratioHelp",
+ "Ratio: 1 page unit = X real-world units",
+ )}
+
+
+
+
+
+ {/* Active Scale Display */}
+
+
+ {t("scaleSettings.activeScale", "Active Scale")}:{" "}
+ {currentScale && currentScale.ratio
+ ? generateScaleLabel(currentScale.ratio, currentScale.unit)
+ : currentScale && !currentScale.ratio
+ ? `${currentScale.unit} (custom)`
+ : t("scaleSettings.noneSet", "No custom scale set")}
+
+
+
+ {/* Calibration Mode */}
+
+
+ {/* Reset Button */}
+ {currentScale && (
+
+ )}
+
+ );
+}
diff --git a/frontend/editor/src/core/components/viewer/useViewerWorkbenchBarButtons.tsx b/frontend/editor/src/core/components/viewer/useViewerWorkbenchBarButtons.tsx
index 14a308e0f3..d5d39c4c0c 100644
--- a/frontend/editor/src/core/components/viewer/useViewerWorkbenchBarButtons.tsx
+++ b/frontend/editor/src/core/components/viewer/useViewerWorkbenchBarButtons.tsx
@@ -26,11 +26,19 @@ import StraightenIcon from "@mui/icons-material/Straighten";
import LayersIcon from "@mui/icons-material/Layers";
import VolumeUpIcon from "@mui/icons-material/VolumeUp";
import StopIcon from "@mui/icons-material/Stop";
+import SettingsIcon from "@mui/icons-material/Settings";
import { useViewerReadAloud } from "@app/components/viewer/useViewerReadAloud";
+import { RulerScaleSettingsButton } from "@app/components/viewer/RulerScaleSettingsButton";
+import type { MeasureScale } from "@app/utils/measurementTypes";
export function useViewerWorkbenchBarButtons(
isRulerActive?: boolean,
setIsRulerActive?: (v: boolean) => void,
+ customScale?: MeasureScale | null,
+ setCustomScale?: (scale: MeasureScale | null) => void,
+ isScaleCalibrationActive?: boolean,
+ startScaleCalibration?: () => void,
+ cancelScaleCalibration?: () => void,
) {
const { t, i18n } = useTranslation();
const viewer = useViewer();
@@ -118,11 +126,36 @@ export function useViewerWorkbenchBarButtons(
const annotationsLabel = t("workbenchBar.annotations", "Annotations");
const formFillLabel = t("workbenchBar.formFill", "Fill Form");
const rulerLabel = t("workbenchBar.ruler", "Ruler / Measure");
+ const rulerSettingsLabel = t("workbenchBar.rulerSettings", "Scale Settings");
const readAloudLabel = t("workbenchBar.readAloud", "Read Aloud");
const readAloudSpeedLabel = t("workbenchBar.readAloudSpeed", "Speed");
const isFormFillActive = (selectedTool as string) === "formFill";
+ const handleStartScaleCalibration = useCallback(() => {
+ startScaleCalibration?.();
+ setIsRulerActive?.(true);
+ if (isPanning) {
+ viewer.panActions.disablePan();
+ setIsPanning(false);
+ }
+ }, [isPanning, setIsRulerActive, startScaleCalibration, viewer.panActions]);
+
+ const handleCancelScaleCalibration = useCallback(() => {
+ cancelScaleCalibration?.();
+ }, [cancelScaleCalibration]);
+
+ const handleApplyRulerScale = useCallback(
+ (scale: MeasureScale) => {
+ setCustomScale?.(scale);
+ },
+ [setCustomScale],
+ );
+
+ const handleResetRulerScale = useCallback(() => {
+ setCustomScale?.(null);
+ }, [setCustomScale]);
+
// Filter languages based on available voices
const filteredLanguages = useMemo(
() =>
@@ -234,6 +267,32 @@ export function useViewerWorkbenchBarButtons(
}
},
},
+ // Ruler scale settings button - only visible when ruler is active
+ ...(isRulerActive
+ ? [
+ {
+ id: "viewer-ruler-settings",
+ icon: