Skip to content

Commit 041a6af

Browse files
authored
Merge pull request #9 from pythonicode/feat/responsive-layout
Feat/responsive layout
2 parents 99c95b9 + fa08b26 commit 041a6af

51 files changed

Lines changed: 2836 additions & 339 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎src/App.css‎

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -396,18 +396,18 @@
396396

397397
.app-main {
398398
display: grid;
399-
grid-template-columns: 56px 1fr 300px;
399+
grid-template-columns: var(--palette-width) 1fr 300px;
400400
flex: 1;
401401
min-height: 0;
402402
overflow: hidden;
403403
}
404404

405405
.app-shell[data-layout='desktop'] .app-main {
406-
grid-template-columns: 56px 1fr 300px;
406+
grid-template-columns: var(--palette-width) 1fr 300px;
407407
}
408408

409409
.app-shell[data-layout='compact'] .app-main {
410-
grid-template-columns: 56px 1fr;
410+
grid-template-columns: var(--palette-width) 1fr;
411411
}
412412

413413
.app-shell[data-layout='touch'] .app-main {
@@ -2601,3 +2601,23 @@ a.statusbar-legal-link {
26012601
min-width: 0;
26022602
flex: 1;
26032603
}
2604+
2605+
/* The palette keeps a fixed width so the canvas never sits underneath it.
2606+
Longer translated labels are truncated with an ellipsis (full text is in the tooltip). */
2607+
.tool-palette {
2608+
box-sizing: border-box;
2609+
flex: 0 0 var(--palette-width);
2610+
width: var(--palette-width);
2611+
min-width: 0;
2612+
max-width: var(--palette-width);
2613+
overflow: hidden;
2614+
}
2615+
2616+
.tool-palette .tool-label {
2617+
display: block;
2618+
max-width: 100%;
2619+
white-space: nowrap;
2620+
overflow: hidden;
2621+
text-overflow: ellipsis;
2622+
text-align: center;
2623+
}

‎src/components/AnnotationsPanel.tsx‎

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import Tooltip from './Tooltip'
1111
import BottomSheet from './BottomSheet'
1212
import SlideDrawer from './SlideDrawer'
1313
import AnnotationAdvertisement from './AnnotationAdvertisement'
14+
import { useT } from '../i18n'
1415

1516
function AnnotationItem({
1617
annotation,
@@ -23,6 +24,7 @@ function AnnotationItem({
2324
selected: boolean
2425
layoutMode: LayoutMode
2526
}) {
27+
const t = useT()
2628
const setSelectedId = useAppStore((s) => s.setSelectedId)
2729
const updateAnnotation = useAppStore((s) => s.updateAnnotation)
2830
const removeAnnotation = useAppStore((s) => s.removeAnnotation)
@@ -60,7 +62,7 @@ function AnnotationItem({
6062
<button
6163
type="button"
6264
className="annotation-delete"
63-
aria-label={`Delete annotation ${markerLabel(index)}`}
65+
aria-label={t('notes.deleteAria', { label: markerLabel(index) })}
6466
onClick={(e) => {
6567
e.stopPropagation()
6668
removeAnnotation(annotation.id)
@@ -87,7 +89,7 @@ function AnnotationItem({
8789
{layoutMode === 'touch' ? (
8890
markerBadge
8991
) : (
90-
<Tooltip content="Click to center on the map" side="left">
92+
<Tooltip content={t('notes.centerHint')} side="left">
9193
{markerBadge}
9294
</Tooltip>
9395
)}
@@ -96,7 +98,7 @@ function AnnotationItem({
9698
<textarea
9799
ref={commentInputRef}
98100
className="annotation-comment-input"
99-
placeholder="Add a comment…"
101+
placeholder={t('notes.commentPlaceholder')}
100102
rows={1}
101103
value={annotation.comment}
102104
onClick={(e) => e.stopPropagation()}
@@ -108,7 +110,7 @@ function AnnotationItem({
108110
{layoutMode === 'touch' ? (
109111
deleteButton
110112
) : (
111-
<Tooltip content="Remove this marker">{deleteButton}</Tooltip>
113+
<Tooltip content={t('notes.removeHint')}>{deleteButton}</Tooltip>
112114
)}
113115
</div>
114116
</div>
@@ -117,6 +119,7 @@ function AnnotationItem({
117119
}
118120

119121
function MarkerDisplayToggle() {
122+
const t = useT()
120123
const markerDisplayMode = useAppStore((s) => s.markerDisplayMode)
121124
const setMarkerDisplayMode = useAppStore((s) => s.setMarkerDisplayMode)
122125

@@ -127,7 +130,7 @@ function MarkerDisplayToggle() {
127130
aria-pressed={markerDisplayMode === 'labels'}
128131
onClick={() => setMarkerDisplayMode('labels')}
129132
>
130-
Labels
133+
{t('notes.labels')}
131134
</button>
132135
)
133136

@@ -138,21 +141,21 @@ function MarkerDisplayToggle() {
138141
aria-pressed={markerDisplayMode === 'comments'}
139142
onClick={() => setMarkerDisplayMode('comments')}
140143
>
141-
Comments
144+
{t('notes.comments')}
142145
</button>
143146
)
144147

145148
return (
146149
<div
147150
className="marker-display-toggle"
148151
role="group"
149-
aria-label="Map marker display"
152+
aria-label={t('notes.displayAria')}
150153
>
151-
<Tooltip content="Show letter labels on map markers" side="bottom">
154+
<Tooltip content={t('notes.labelsHint')} side="bottom">
152155
{labelsButton}
153156
</Tooltip>
154157
<Tooltip
155-
content="Show inline comments on the map. Drag the right edge of a selected comment to change its width."
158+
content={t('notes.commentsHint')}
156159
side="bottom"
157160
>
158161
{commentsButton}
@@ -166,6 +169,7 @@ function AnnotationsContent({
166169
}: {
167170
layoutMode: LayoutMode
168171
}) {
172+
const t = useT()
169173
const annotations = useAppStore((s) => s.annotations)
170174
const selectedId = useAppStore((s) => s.selectedId)
171175

@@ -174,14 +178,14 @@ function AnnotationsContent({
174178
<div className="panel-header">
175179
<div className="panel-header-title">
176180
<MessageSquareText size={16} aria-hidden />
177-
<h2>Annotations</h2>
181+
<h2>{t('notes.heading')}</h2>
178182
</div>
179183
<MarkerDisplayToggle />
180184
</div>
181185

182186
{annotations.length === 0 ? (
183187
<p className="panel-empty">
184-
No annotations yet. Use the marker tool to add comments to the map.
188+
{t('notes.empty')}
185189
</p>
186190
) : (
187191
<ul className="annotation-list">
@@ -207,6 +211,7 @@ export default function AnnotationsPanel({
207211
variant: 'sidebar' | 'drawer' | 'sheet'
208212
layoutMode: LayoutMode
209213
}) {
214+
const t = useT()
210215
const annotations = useAppStore((s) => s.annotations)
211216
const annotationsOpen = useAppStore((s) => s.annotationsOpen)
212217
const toggleAnnotations = useAppStore((s) => s.toggleAnnotations)
@@ -229,18 +234,18 @@ export default function AnnotationsPanel({
229234
<button
230235
type="button"
231236
className="annotation-toggle"
232-
aria-label="Toggle annotations"
237+
aria-label={t('notes.toggleAria')}
233238
aria-expanded={annotationsOpen}
234239
onClick={toggleAnnotations}
235240
>
236241
<MessageSquareText size={16} aria-hidden />
237-
<span>Notes</span>
242+
<span>{t('notes.toggle')}</span>
238243
{annotations.length > 0 && (
239244
<span className="annotation-toggle-badge">{annotations.length}</span>
240245
)}
241246
</button>
242247
<SlideDrawer
243-
title="Annotations"
248+
title={t('notes.heading')}
244249
open={annotationsOpen}
245250
onClose={closeAnnotations}
246251
>
@@ -259,7 +264,7 @@ export default function AnnotationsPanel({
259264

260265
return (
261266
<BottomSheet
262-
title="Annotations"
267+
title={t('notes.heading')}
263268
onClose={closeAnnotations}
264269
className="bottom-sheet-full"
265270
>

‎src/components/AppErrorBoundary.tsx‎

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Component, type ErrorInfo, type ReactNode } from 'react'
2+
import { t } from '../i18n'
23

34
interface Props {
45
children: ReactNode
@@ -29,17 +30,14 @@ export default class AppErrorBoundary extends Component<Props, State> {
2930
return (
3031
<div className="app-error-boundary" role="alert">
3132
<div className="app-error-boundary-card">
32-
<h2>Something went wrong</h2>
33-
<p>
34-
O-Analysis ran into an unexpected error. Reloading the page usually
35-
fixes this.
36-
</p>
33+
<h2>{t('error.title')}</h2>
34+
<p>{t('error.body')}</p>
3735
<button
3836
type="button"
3937
className="app-error-boundary-reload"
4038
onClick={this.handleReload}
4139
>
42-
Reload page
40+
{t('error.reload')}
4341
</button>
4442
</div>
4543
</div>

‎src/components/BottomSheet.tsx‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useEffect, useId, useRef } from 'react'
22
import { X } from 'lucide-react'
33
import { useEscapeKey } from '../hooks/useEscapeKey'
4+
import { useT } from '../i18n'
45

56
export default function BottomSheet({
67
title,
@@ -13,6 +14,7 @@ export default function BottomSheet({
1314
children: React.ReactNode
1415
className?: string
1516
}) {
17+
const t = useT()
1618
const titleId = useId()
1719
const sheetRef = useRef<HTMLDivElement>(null)
1820

@@ -52,7 +54,7 @@ export default function BottomSheet({
5254
<button
5355
type="button"
5456
className="bottom-sheet-close"
55-
aria-label="Close"
57+
aria-label={t('common.close')}
5658
onClick={onClose}
5759
>
5860
<X size={18} aria-hidden />

‎src/components/BottomToolBar.tsx‎

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import { useAppStore } from '../store'
55
import BottomSheet from './BottomSheet'
66
import StrokeSettings from './StrokeSettings'
77
import CanvasRotationControl from './CanvasRotationControl'
8+
import { useT } from '../i18n'
89
import TextSizeSlider from './TextSizeSlider'
10+
import LanguageSelect from './LanguageSelect'
911

1012
export default function BottomToolBar() {
1113
const activeTool = useAppStore((s) => s.activeTool)
@@ -16,57 +18,59 @@ export default function BottomToolBar() {
1618
const toggleAnnotations = useAppStore((s) => s.toggleAnnotations)
1719

1820
const [settingsOpen, setSettingsOpen] = useState(false)
21+
const t = useT()
1922

2023
return (
2124
<>
22-
<nav className="bottom-toolbar" aria-label="Drawing tools">
25+
<nav className="bottom-toolbar" aria-label={t('toolbar.drawingTools')}>
2326
<div className="bottom-toolbar-tools" role="toolbar">
2427
{TOOLS.map(({ id, label, shortLabel, icon: Icon }) => (
2528
<button
2629
key={id}
2730
type="button"
2831
className={`bottom-tool-button${activeTool === id ? ' active' : ''}`}
29-
aria-label={label}
32+
aria-label={t(label)}
3033
aria-pressed={activeTool === id}
3134
title={getToolTip(id, 'touch')}
3235
disabled={id === 'gpx' && !hasTracks}
3336
onClick={() => setActiveTool(id)}
3437
>
3538
<Icon size={20} aria-hidden />
36-
<span className="bottom-tool-label">{shortLabel}</span>
39+
<span className="bottom-tool-label">{t(shortLabel)}</span>
3740
</button>
3841
))}
3942
</div>
4043

4144
<button
4245
type="button"
4346
className="bottom-tool-button bottom-tool-extra"
44-
aria-label="Stroke settings"
47+
aria-label={t('toolbar.strokeSettings')}
4548
onClick={() => setSettingsOpen(true)}
4649
>
4750
<Settings size={20} aria-hidden />
48-
<span className="bottom-tool-label">Style</span>
51+
<span className="bottom-tool-label">{t('toolbar.style')}</span>
4952
</button>
5053

5154
<button
5255
type="button"
5356
className={`bottom-tool-button bottom-tool-extra${annotationsOpen ? ' active' : ''}`}
54-
aria-label="Annotations"
57+
aria-label={t('toolbar.annotations')}
5558
aria-pressed={annotationsOpen}
5659
onClick={toggleAnnotations}
5760
>
5861
<MessageSquareText size={20} aria-hidden />
59-
<span className="bottom-tool-label">Notes</span>
62+
<span className="bottom-tool-label">{t('toolbar.notes')}</span>
6063
{annotations.length > 0 && (
6164
<span className="bottom-tool-badge">{annotations.length}</span>
6265
)}
6366
</button>
6467
</nav>
6568

6669
{settingsOpen && (
67-
<BottomSheet title="View & stroke settings" onClose={() => setSettingsOpen(false)}>
70+
<BottomSheet title={t('toolbar.viewAndStroke')} onClose={() => setSettingsOpen(false)}>
6871
<CanvasRotationControl compact />
69-
<TextSizeSlider label="Comment text size" className="style-sheet-text-size" />
72+
<LanguageSelect className="style-sheet-language" />
73+
<TextSizeSlider label={t('style.commentTextSize')} className="style-sheet-text-size" />
7074
<StrokeSettings layoutMode="touch" />
7175
</BottomSheet>
7276
)}

‎src/components/CanvasArea.tsx‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
} from '../utils/viewport'
2424
import { getMapPointer } from '../utils/mapPointer'
2525
import type { Point, Viewport } from '../types'
26+
import { useT } from '../i18n'
2627

2728
const ZOOM_FACTOR = 1.06
2829
const WHEEL_COMMIT_MS = 120
@@ -38,6 +39,7 @@ export default function CanvasArea({
3839
}: {
3940
layoutMode: LayoutMode
4041
}) {
42+
const t = useT()
4143
const activeTool = useAppStore((s) => s.activeTool)
4244
const mapImage = useAppStore((s) => s.mapImage)
4345
const setViewport = useAppStore((s) => s.setViewport)
@@ -618,11 +620,9 @@ export default function CanvasArea({
618620
<div className="canvas-empty">
619621
<div className="canvas-empty-card">
620622
<ImagePlus size={40} aria-hidden />
621-
<h2>No map loaded</h2>
623+
<h2>{t('canvas.noMap')}</h2>
622624
<p>
623-
{isTouch
624-
? 'Import a map image to get started.'
625-
: 'Import a map image to get started, or drop a file here.'}
625+
{isTouch ? t('canvas.importHintTouch') : t('canvas.importHintDrop')}
626626
</p>
627627
<input
628628
ref={mapInputRef}
@@ -643,15 +643,15 @@ export default function CanvasArea({
643643
onClick={() => mapInputRef.current?.click()}
644644
>
645645
<ImagePlus size={14} aria-hidden />
646-
Import Map
646+
{t('canvas.importMap')}
647647
</button>
648648
<button
649649
type="button"
650650
className="button button-outline"
651651
disabled={loadingSample}
652652
onClick={() => void handleLoadSample()}
653653
>
654-
{loadingSample ? 'Loading sample…' : 'Open Sample Project'}
654+
{loadingSample ? t('canvas.loadingSample') : t('canvas.openSample')}
655655
</button>
656656
</div>
657657
</div>

0 commit comments

Comments
 (0)