Skip to content

Commit 78849df

Browse files
committed
frontend/latex/output: tweaks/i18n for pdf control bar
1 parent 93081c8 commit 78849df

24 files changed

+274
-63
lines changed

src/packages/frontend/frame-editors/frame-tree/commands/generic-commands.tsx

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@ import { SEARCH_COMMANDS } from "./const";
3333
// Predefined zoom percentages for consistent zoom options across the application
3434
export const ZOOM_PERCENTAGES = [50, 85, 100, 115, 125, 150, 200, 400] as const;
3535

36+
// Build on save icon constants - exported for consistent iconography across components
37+
export const BUILD_ON_SAVE_ICON_ENABLED = "delivered-procedure-outlined";
38+
export const BUILD_ON_SAVE_ICON_DISABLED = "stop-filled";
39+
export const BUILD_ON_SAVE_LABEL = defineMessage({
40+
id: "command.generic.build_on_save.label",
41+
defaultMessage:
42+
"Build on Save {enabled, select, true {(Enabled)} other {(Disabled)}}",
43+
});
44+
3645
// Export zoom-related messages for use in other components
3746
export const ZOOM_MESSAGES = {
3847
zoomPageWidth: {
@@ -655,26 +664,19 @@ addCommands({
655664
build_on_save: {
656665
group: "build",
657666
label: ({ intl }) =>
658-
intl.formatMessage(
659-
{
660-
id: "command.generic.build_on_save.label",
661-
defaultMessage:
662-
"Build on Save {enabled, select, true {(Enabled)} other {(Disabled)}}",
663-
},
664-
{
665-
enabled: redux
666-
.getStore("account")
667-
.getIn(["editor_settings", "build_on_save"]),
668-
},
669-
),
667+
intl.formatMessage(BUILD_ON_SAVE_LABEL, {
668+
enabled: redux
669+
.getStore("account")
670+
.getIn(["editor_settings", "build_on_save"]),
671+
}),
670672
title: defineMessage({
671673
id: "command.generic.build_on_save.title",
672674
defaultMessage: "Toggle automatic build on file save.",
673675
}),
674676
icon: () =>
675677
redux.getStore("account").getIn(["editor_settings", "build_on_save"])
676-
? "delivered-procedure-outlined"
677-
: "stop-filled",
678+
? BUILD_ON_SAVE_ICON_ENABLED
679+
: BUILD_ON_SAVE_ICON_DISABLED,
678680
},
679681
force_build: {
680682
group: "build",

src/packages/frontend/frame-editors/latex-editor/pdf-controls.tsx renamed to src/packages/frontend/frame-editors/latex-editor/output-pdf-control.tsx

Lines changed: 100 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,15 @@ import { defineMessage, useIntl } from "react-intl";
1515

1616
import { set_account_table } from "@cocalc/frontend/account/util";
1717
import { useRedux } from "@cocalc/frontend/app-framework";
18-
import { Icon } from "@cocalc/frontend/components";
18+
import { HelpIcon, Icon } from "@cocalc/frontend/components";
1919
import {
20+
BUILD_ON_SAVE_ICON_DISABLED,
21+
BUILD_ON_SAVE_ICON_ENABLED,
22+
BUILD_ON_SAVE_LABEL,
2023
ZOOM_MESSAGES,
2124
ZOOM_PERCENTAGES,
2225
} from "@cocalc/frontend/frame-editors/frame-tree/commands/generic-commands";
23-
import { labels } from "@cocalc/frontend/i18n";
26+
import { editor, labels } from "@cocalc/frontend/i18n";
2427
import { COLORS } from "@cocalc/util/theme";
2528
import { Actions } from "./actions";
2629

@@ -43,14 +46,38 @@ const CONTROL_PAGE_STYLE = {
4346
color: COLORS.GRAY_M,
4447
} as const;
4548

46-
const autoSyncTooltipMessage = defineMessage({
49+
export const AUTO_SYNC_TOOLTIP_MSG = defineMessage({
4750
id: "editor.latex.pdf_controls.auto_sync.tooltip",
4851
defaultMessage:
4952
"Auto-sync between source and PDF: cursor moves follow PDF scrolling, PDF scrolls to cursor position",
5053
description:
5154
"Tooltip explaining bidirectional auto-sync functionality in LaTeX PDF controls",
5255
});
5356

57+
export const SYNC_HELP_MSG = {
58+
title: defineMessage({
59+
id: "editor.latex.pdf_controls.sync_help.title",
60+
defaultMessage: "LaTeX Sync Help",
61+
description: "Title for LaTeX sync help popup",
62+
}),
63+
content: defineMessage({
64+
id: "editor.latex.pdf_controls.sync_help.content",
65+
defaultMessage: `<p><strong>Manual Mode:</strong></p>
66+
<ul>
67+
<li>Use ALT+Return in source document to jump to corresponding PDF location</li>
68+
<li>Double-click in PDF for inverse search to source</li>
69+
</ul>
70+
<p><strong>Automatic Mode:</strong></p>
71+
<ul>
72+
<li>Syncs automatically from cursor changes in source to PDF</li>
73+
<li>Moving the PDF viewport moves the cursor in source</li>
74+
</ul>
75+
<p>This functionality uses SyncTeX to coordinate between LaTeX source and PDF output.</p>`,
76+
description:
77+
"Complete explanation of LaTeX sync functionality including manual and automatic modes",
78+
}),
79+
};
80+
5481
interface PDFControlsProps {
5582
actions: Actions;
5683
id: string;
@@ -62,6 +89,7 @@ interface PDFControlsProps {
6289
y: number;
6390
} | null;
6491
onClearViewportInfo?: () => void;
92+
pageDimensions?: { width: number; height: number }[];
6593
}
6694

6795
export function PDFControls({
@@ -71,6 +99,7 @@ export function PDFControls({
7199
currentPage = 1,
72100
viewportInfo,
73101
onClearViewportInfo,
102+
pageDimensions = [],
74103
}: PDFControlsProps) {
75104
const intl = useIntl();
76105

@@ -91,6 +120,7 @@ export function PDFControls({
91120
const storedAutoSyncEnabled =
92121
useRedux([actions.name, "local_view_state", id, "autoSyncEnabled"]) ??
93122
false; // Default to false
123+
94124
const [localAutoSyncEnabled, setLocalAutoSyncEnabled] = useState(
95125
storedAutoSyncEnabled,
96126
);
@@ -122,6 +152,23 @@ export function PDFControls({
122152
[actions, autoSyncInProgress, onClearViewportInfo],
123153
);
124154

155+
// Handle manual sync from middle of current page
156+
const handleManualSync = useCallback(() => {
157+
if (
158+
pageDimensions.length === 0 ||
159+
storedCurrentPage < 1 ||
160+
storedCurrentPage > pageDimensions.length
161+
) {
162+
return; // No page dimensions available or invalid page
163+
}
164+
const pageDim = pageDimensions[storedCurrentPage - 1]; // pages are 1-indexed
165+
handleViewportSync(
166+
storedCurrentPage,
167+
pageDim.width / 2,
168+
pageDim.height / 2,
169+
);
170+
}, [handleViewportSync, storedCurrentPage, pageDimensions]);
171+
125172
// Sync state with stored values when they change
126173
useEffect(() => {
127174
setLocalAutoSyncEnabled(storedAutoSyncEnabled);
@@ -267,8 +314,16 @@ export function PDFControls({
267314
key: "auto-build",
268315
label: (
269316
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
270-
<Icon name={buildOnSave ? "check-square" : "square"} />
271-
Auto Build
317+
<Icon
318+
name={
319+
buildOnSave
320+
? BUILD_ON_SAVE_ICON_ENABLED
321+
: BUILD_ON_SAVE_ICON_DISABLED
322+
}
323+
/>
324+
{intl.formatMessage(BUILD_ON_SAVE_LABEL, {
325+
enabled: buildOnSave,
326+
})}
272327
</div>
273328
),
274329
onClick: toggleBuildOnSave,
@@ -279,7 +334,15 @@ export function PDFControls({
279334
{
280335
key: "custom-zoom",
281336
label: (
282-
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
337+
<div
338+
style={{ display: "flex", alignItems: "center", gap: "8px" }}
339+
onClick={(e) => {
340+
e.stopPropagation();
341+
}}
342+
onMouseDown={(e) => {
343+
e.stopPropagation();
344+
}}
345+
>
283346
<InputNumber
284347
size="small"
285348
style={{ width: "80px" }}
@@ -337,8 +400,8 @@ export function PDFControls({
337400
return (
338401
<div style={CONTROL_STYLE}>
339402
{/* Left side controls */}
340-
<div style={{ display: "flex", gap: "10px", alignItems: "center" }}>
341-
{/* Build Controls */}
403+
{/* Build Controls */}
404+
<div style={{ display: "flex", alignItems: "center", gap: "5px" }}>
342405
<Dropdown.Button
343406
type="primary"
344407
size="small"
@@ -348,24 +411,40 @@ export function PDFControls({
348411
onClick={handleBuild}
349412
>
350413
<Icon name="play-circle" />
351-
Build
414+
{intl.formatMessage(editor.build_control_and_log_title_short)}
352415
</Dropdown.Button>
416+
</div>
353417

354-
{/* Auto-Sync Control */}
355-
<Tooltip title={intl.formatMessage(autoSyncTooltipMessage)}>
356-
<div style={{ display: "flex", alignItems: "center", gap: "6px" }}>
357-
<Switch
358-
size="small"
359-
checked={localAutoSyncEnabled}
360-
onChange={handleAutoSyncChange}
361-
/>
362-
<Icon name="exchange" />
363-
<span style={{ fontSize: "13px" }}>Sync</span>
364-
</div>
418+
{/* Auto-Sync Control */}
419+
<div style={{ display: "flex", alignItems: "center", gap: "5px" }}>
420+
<Icon name="exchange" />
421+
<Tooltip title={intl.formatMessage(AUTO_SYNC_TOOLTIP_MSG)}>
422+
<Switch
423+
size="small"
424+
checked={localAutoSyncEnabled}
425+
onChange={handleAutoSyncChange}
426+
checkedChildren={intl.formatMessage(labels.on)}
427+
unCheckedChildren={intl.formatMessage(labels.off)}
428+
/>
365429
</Tooltip>
430+
<Button
431+
type="text"
432+
size="small"
433+
style={{ fontSize: "13px", padding: "0 4px", height: "auto" }}
434+
onClick={handleManualSync}
435+
disabled={pageDimensions.length === 0}
436+
>
437+
Sync
438+
</Button>
439+
<HelpIcon
440+
title={intl.formatMessage(SYNC_HELP_MSG.title)}
441+
placement="bottomLeft"
442+
>
443+
{intl.formatMessage(SYNC_HELP_MSG.content)}
444+
</HelpIcon>
366445
</div>
367446

368-
{/* Right side page navigation */}
447+
{/* middle: page navigation */}
369448
{totalPages > 0 && (
370449
<div style={CONTROL_PAGE_STYLE}>
371450
<InputNumber

src/packages/frontend/frame-editors/latex-editor/output.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ import { Actions } from "./actions";
4343
import { Build } from "./build";
4444
import { ErrorsAndWarnings } from "./errors-and-warnings";
4545
import { use_build_logs } from "./hooks";
46-
import { PDFControls } from "./pdf-controls";
46+
import { PDFControls } from "./output-pdf-control";
4747
import { PDFJS } from "./pdfjs";
4848
import { BuildLogs } from "./types";
4949
import { useFileSummaries } from "./summarize-tex";
@@ -111,6 +111,9 @@ export function Output(props: OutputProps) {
111111
y: number;
112112
} | null>(null);
113113

114+
// Track page dimensions for manual sync
115+
const [pageDimensions, setPageDimensions] = useState<{ width: number; height: number }[]>([]);
116+
114117
// Callback to clear viewport info after successful sync
115118
const clearViewportInfo = useCallback(() => {
116119
setViewportInfo(null);
@@ -271,6 +274,7 @@ export function Output(props: OutputProps) {
271274
currentPage={currentPage}
272275
viewportInfo={viewportInfo}
273276
onClearViewportInfo={clearViewportInfo}
277+
pageDimensions={pageDimensions}
274278
/>
275279
<PDFJS
276280
id={id}
@@ -316,6 +320,7 @@ export function Output(props: OutputProps) {
316320
}, 500); // Wait longer to ensure scrolling has stabilized
317321
}
318322
}}
323+
onPageDimensions={setPageDimensions}
319324
/>
320325
</div>
321326
),

src/packages/frontend/frame-editors/latex-editor/pdfjs.tsx

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ interface PDFJSProps {
5151
initialPage?: number;
5252
onPageInfo?: (currentPage: number, totalPages: number) => void;
5353
onViewportInfo?: (page: number, x: number, y: number) => void;
54+
onPageDimensions?: (pageDimensions: { width: number; height: number }[]) => void;
5455
zoom?: number; // Optional zoom override (when provided, overrides font_size-based zoom)
5556
onZoom?: (data: Data) => void; // Called when zoom changes via pinch/wheel gestures
5657
}
@@ -70,6 +71,7 @@ export function PDFJS({
7071
initialPage,
7172
onPageInfo,
7273
onViewportInfo,
74+
onPageDimensions,
7375
zoom,
7476
onZoom,
7577
}: PDFJSProps) {
@@ -284,12 +286,21 @@ export function PDFJS({
284286
const page = doc.getPage(n) as unknown as Promise<PDFPageProxy>;
285287
v.push(page);
286288
}
287-
const pages: PDFPageProxy[] = await Promise.all(v);
288-
if (!isMounted.current) return;
289-
setDoc(doc);
290-
setLoaded(true);
291-
setPages(pages);
292-
setMissing(false);
289+
const pages: PDFPageProxy[] = await Promise.all(v);
290+
if (!isMounted.current) return;
291+
setDoc(doc);
292+
setLoaded(true);
293+
setPages(pages);
294+
setMissing(false);
295+
296+
// Extract page dimensions and call callback
297+
if (onPageDimensions) {
298+
const pageDimensions = pages.map(page => {
299+
const viewport = page.getViewport({ scale: 1.0 });
300+
return { width: viewport.width, height: viewport.height };
301+
});
302+
onPageDimensions(pageDimensions);
303+
}
293304

294305
// documents often don't have pageLabels, but when they do, they are
295306
// good to show (e.g., in a book the content at the beginning might

src/packages/frontend/i18n/bin/delete.sh

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,8 @@ echo "Deleting translation keys from SimpleLocalize..."
2222

2323
# Loop through all provided keys
2424
for key in "$@"; do
25-
echo
26-
echo "Deleting '$key':"
2725
curl \
26+
-s \
2827
--location \
2928
--request DELETE "https://api.simplelocalize.io/api/v1/translation-keys?key=$key" \
3029
--header "X-SimpleLocalize-Token: $SIMPLELOCALIZE_KEY"

src/packages/frontend/i18n/common.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,15 @@ export const labels = defineMessages({
4040
},
4141
on: {
4242
id: "labels.on",
43-
defaultMessage: "on",
44-
description: "single word, something 'on' something else",
43+
defaultMessage: "On",
44+
description:
45+
"short single word, just a few characters long, for a label. Should mean 'enabled'.",
46+
},
47+
off: {
48+
id: "labels.off",
49+
defaultMessage: "Off",
50+
description:
51+
"short single word, just a few characters long, for a label. Should mean 'disabled'.",
4552
},
4653
yes: {
4754
id: "labels.yes",

src/packages/frontend/i18n/trans/ar_EG.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,9 @@
268268
"command.generic.new_frame_of_type.button": "الإطار",
269269
"command.generic.new_frame_of_type.label": "إطار جديد",
270270
"command.generic.new_frame_of_type.title": "إنشاء إطار جديد مع محرر من النوع المحدد",
271+
"command.generic.new_layout.button": "جديد",
272+
"command.generic.new_layout.label": "تخطيط جديد",
273+
"command.generic.new_layout.title": "التحويل إلى تخطيط مبسط مع محرر مصدر LaTeX ولوحة إخراج متعددة الأغراض",
271274
"command.generic.open_recent.label": "افتح الملفات الأخيرة",
272275
"command.generic.open_recent.title": "افتح ملفًا تم فتحه مؤخرًا",
273276
"command.generic.open.label": "افتح الملف",
@@ -583,6 +586,8 @@
583586
"editor.latex.command.print.label": "طباعة مصدر LaTeX",
584587
"editor.latex.command.print.tooltip": "اطبع الشيفرة المصدرية لهذا المستند. استخدم الطباعة من إطار معاينة PDF لطباعة المستند المُعرض.",
585588
"editor.latex.pdf_controls.auto_sync.tooltip": "المزامنة التلقائية بين المصدر وPDF: يتحرك المؤشر مع تمرير PDF، يتم تمرير PDF إلى موضع المؤشر",
589+
"editor.latex.pdf_controls.sync_help.content": "<p><strong>الوضع اليدوي:</strong></p> <ul> <li>استخدم ALT+Return في المستند المصدر للقفز إلى الموقع المقابل في PDF</li> <li>انقر نقرًا مزدوجًا في PDF للبحث العكسي إلى المصدر</li> </ul> <p><strong>الوضع التلقائي:</strong></p> <ul> <li>يقوم بالمزامنة تلقائيًا من تغييرات المؤشر في المصدر إلى PDF</li> <li>تحريك نافذة عرض PDF يحرك المؤشر في المصدر</li> </ul> <p>تستخدم هذه الوظيفة SyncTeX للتنسيق بين مصدر LaTeX وإخراج PDF.</p>",
590+
"editor.latex.pdf_controls.sync_help.title": "مساعدة مزامنة LaTeX",
586591
"editor.latex.pdf_embed.title": "PDF - أصلي",
587592
"editor.latex.pdf_embed.title.short": "PDF (native)",
588593
"editor.latex.source_code.name": "مصدر كود LaTeX",
@@ -1021,7 +1026,8 @@
10211026
"labels.no_description": "لا يوجد وصف",
10221027
"labels.not_implemented": "لم يتم التنفيذ",
10231028
"labels.notifications": "الإشعارات",
1024-
"labels.on": "على",
1029+
"labels.off": "إيقاف",
1030+
"labels.on": "تشغيل",
10251031
"labels.open": "افتح",
10261032
"labels.other": "أخرى",
10271033
"labels.overview": "نظرة عامة",

0 commit comments

Comments
 (0)