Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,16 @@
"dom-to-image": "^2.6.0",
"electron-window-state": "^5.0.3",
"font-gis": "^1.0.6",
"i18next": "^24.2.3",
"i18next-browser-languagedetector": "^8.0.4",
"leaflet": "^1.9.4",
"leaflet.nauticscale": "^1.1.0",
"leaflet.polylinemeasure": "^3.0.0",
"lodash": "^4.17.21",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-error-boundary": "^5.0.0",
"react-i18next": "^15.4.1",
"react-leaflet": "^5.0.0",
"react-leaflet-custom-control": "^1.4.0",
"react-leaflet-geoman-v2": "^1.0.1",
Expand Down
20 changes: 11 additions & 9 deletions src/components/BackdropForm/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Feature, Geometry } from 'geojson'
import { Checkbox, Form, Input } from 'antd'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { BackdropProps } from '../../types'
import './index.css'

Expand All @@ -12,6 +13,7 @@ export interface BackdropFormProps {


export const BackdropForm: React.FC<BackdropFormProps> = ({backdrop, onChange, create = false}) => {
const { t } = useTranslation()
const [state, setState] = useState<BackdropProps | null>(null)
const [form] = Form.useForm()

Expand Down Expand Up @@ -49,39 +51,39 @@ export const BackdropForm: React.FC<BackdropFormProps> = ({backdrop, onChange, c
onValuesChange={localChange}
size='small'>
<Form.Item<BackdropProps>
label='Name'
label={t('forms.common.name')}
name='name'
style={itemStyle}
rules={[{ required: true, message: 'Please enter backdrop name!' }]}>
rules={[{ required: true, message: t('forms.common.nameRequired') }]}>
<Input/>
</Form.Item>
<Form.Item<BackdropProps>
label='Visible'
label={t('forms.common.visible')}
name={'visible'}
style={itemStyle}
valuePropName="checked" >
<Checkbox />
</Form.Item>

{ create && <><Form.Item<BackdropProps>
label='URL'
label={t('forms.common.url')}
name='url'
style={itemStyle}
rules={[{ required: true, message: 'Please enter backdrop URL!' }]}>
rules={[{ required: true, message: t('forms.common.urlRequired') }]}>
<Input.TextArea rows={3}/>
</Form.Item>
<Form.Item<BackdropProps>
label='Max Native Zoom'
label={t('forms.common.maxNativeZoom')}
name= 'maxNativeZoom'
style={itemStyle}
rules={[{ required: true, message: 'Please enter backdrop max native zoom!' }]}>
rules={[{ required: true, message: t('forms.common.maxNativeZoomRequired') }]}>
<Input/>
</Form.Item>
<Form.Item<BackdropProps>
label='Max Zoom'
label={t('forms.common.maxZoom')}
name='maxZoom'
style={itemStyle}
rules={[{ required: true, message: 'Please enter backdrop max zoom!' }]}>
rules={[{ required: true, message: t('forms.common.maxZoomRequired') }]}>
<Input/>
</Form.Item>
</>}
Expand Down
22 changes: 12 additions & 10 deletions src/components/BuoyFieldForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from 'antd'
import { Color } from 'antd/es/color-picker'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { BuoyFieldProps } from '../../types'
import { presetColors } from '../../helpers/standardShades'
import './index.css'
Expand All @@ -29,6 +30,7 @@ export const BuoyFieldForm: React.FC<FieldFormProps> = ({
field,
onChange,
}) => {
const { t } = useTranslation()
const [state, setState] = useState<BuoyFieldProps | null>(null)

useEffect(() => {
Expand Down Expand Up @@ -96,34 +98,34 @@ export const BuoyFieldForm: React.FC<FieldFormProps> = ({
size='small'
>
<Form.Item<FormTypeProps>
label='Name'
label={t('forms.common.name')}
name='name'
style={itemStyle}
rules={[{ required: true, message: 'Please enter track name!' }]}
rules={[{ required: true, message: t('forms.common.nameRequired') }]}
>
<Input />
</Form.Item>
<Form.Item<FormTypeProps>
label='Short name'
label={t('forms.common.shortName')}
name='shortName'
style={itemStyle}
rules={[{ required: true, message: 'Please enter short name!' }]}
rules={[{ required: true, message: t('forms.common.shortNameRequired') }]}
>
<Input />
</Form.Item>
<Form.Item<FormTypeProps>
label='Visible'
label={t('forms.common.visible')}
name={'visible'}
style={itemStyle}
valuePropName='checked'
>
<Checkbox />
</Form.Item>
<Form.Item<FormTypeProps>
label='Color'
label={t('forms.common.color')}
name='marker-color'
style={itemStyle}
rules={[{ required: true, message: 'color is required!' }]}
rules={[{ required: true, message: t('forms.common.colorRequired') }]}
>
<ColorPicker
style={{ marginLeft: 0 }}
Expand All @@ -132,11 +134,11 @@ export const BuoyFieldForm: React.FC<FieldFormProps> = ({
presets={presetColors}
/>
</Form.Item>
<Form.Item<FormTypeProps> label='Time' style={itemStyle} name='dTime'>
<Form.Item<FormTypeProps> label={t('forms.common.time')} style={itemStyle} name='dTime'>
<DatePicker showTime format={'MMM DDHHmm'} />
</Form.Item>
<Form.Item<FormTypeProps>
label='Time end'
label={t('forms.common.timeEnd')}
style={itemStyle}
// validate that dTimeEnd is after dTime
rules={[
Expand All @@ -145,7 +147,7 @@ export const BuoyFieldForm: React.FC<FieldFormProps> = ({
// if there is a value, check if it's after the dTime
return !value || getFieldValue('dTime') < value
? Promise.resolve()
: Promise.reject(new Error('Time-end must be after time!'))
: Promise.reject(new Error(t('forms.common.timeEndAfterTime')))
},
}),
]}
Expand Down
16 changes: 9 additions & 7 deletions src/components/ControlPanel/TimeControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import { TimeSupport } from '../../helpers/time-support'
import { formatInTimeZone } from 'date-fns-tz'
import { useDocContext } from '../../state/DocContext'
import { useTranslation } from 'react-i18next'

interface TimeControlsProps {
bounds: [number, number] | null
Expand Down Expand Up @@ -66,6 +67,7 @@ const TimeButton: React.FC<TimeButtonProps> = ({
}

const TimeControls: FC<TimeControlsProps> = ({ bounds }) => {
const { t } = useTranslation()
const { time, setTime, interval, setInterval } = useDocContext()
const [stepTxt, setStepTxt] = useState<string>('01h00m')

Expand Down Expand Up @@ -140,9 +142,9 @@ const TimeControls: FC<TimeControlsProps> = ({ bounds }) => {
>
<thead>
<tr>
<th>Start</th>
<th>Step</th>
<th>End</th>
<th>{t('controlPanel.start')}</th>
<th>{t('controlPanel.step')}</th>
<th>{t('controlPanel.end')}</th>
</tr>
</thead>
<tbody>
Expand All @@ -164,15 +166,15 @@ const TimeControls: FC<TimeControlsProps> = ({ bounds }) => {
<tr style={{ fontFamily: 'monospace' }}>
<td>
<TimeButton
tooltip='Jump to start'
tooltip={t('controlPanel.jumpToStart')}
icon={<FastBackwardOutlined style={largeIcon} />}
forward={false}
large={true}
doStep={doStep}
disabled={!time.filterApplied}
/>
<TimeButton
tooltip='Step backward'
tooltip={t('controlPanel.stepBackward')}
icon={<StepBackwardOutlined style={largeIcon} />}
forward={false}
large={false}
Expand All @@ -183,15 +185,15 @@ const TimeControls: FC<TimeControlsProps> = ({ bounds }) => {
<td></td>
<td>
<TimeButton
tooltip='Step forward'
tooltip={t('controlPanel.stepForward')}
icon={<StepForwardOutlined style={largeIcon} />}
forward={true}
large={false}
doStep={doStep}
disabled={!time.filterApplied}
/>
<TimeButton
tooltip='Jump to end'
tooltip={t('controlPanel.jumpToEnd')}
icon={<FastForwardOutlined style={largeIcon} />}
forward={true}
large={true}
Expand Down
30 changes: 16 additions & 14 deletions src/components/ControlPanel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import React, { useMemo, useState } from 'react'
import { useAppSelector } from '../../state/hooks'
import { UndoModal } from '../UndoModal'
import { useDocContext } from '../../state/DocContext'
import { useTranslation } from 'react-i18next'


import { SampleDataLoader } from '../SampleDataLoader'
Expand All @@ -27,32 +28,33 @@ export interface TimeProps {


const ControlPanel: React.FC<TimeProps> = ({ bounds, handleSave, isDirty }) => {
const { t } = useTranslation()
const canUndo = useAppSelector(state => state.fColl.past.length > 1)
const canRedo = useAppSelector(state => state.fColl.future.length > 0)
const { viewportFrozen, setViewportFrozen, copyMapToClipboard, time, setTime } = useDocContext()
const [undoModalVisible, setUndoModalVisible] = useState(false)

const undoRedoTitle = useMemo(() => {
if(canUndo && canRedo) {
return 'Undo/Redo ...'
return t('controlPanel.undoRedo')
} else if (canUndo) {
return 'Undo ...'
return t('controlPanel.undo')
} else if (canRedo) {
return 'Redo ...'
return t('controlPanel.redo')
} else {
return null
}
}, [canUndo, canRedo])
}, [canUndo, canRedo, t])

const toggleFreezeViewport = () => {
setViewportFrozen(!viewportFrozen)
}

const copyTooltip = useMemo(() => {
return viewportFrozen
? 'Copy snapshot of map to the clipboard'
: 'Lock the viewport in order to take a snapshot of the map'
}, [viewportFrozen])
? t('controlPanel.copySnapshot')
: t('controlPanel.lockViewportFirst')
}, [viewportFrozen, t])

const toggleFilterApplied = () => {
setTime(prevTime => ({ ...prevTime, filterApplied: !prevTime.filterApplied }))
Expand All @@ -61,18 +63,18 @@ const ControlPanel: React.FC<TimeProps> = ({ bounds, handleSave, isDirty }) => {
const buttonStyle = { margin: '0 5px' }

const saveButton = useMemo(() => {
return <Tooltip placement='bottom' title={isDirty ? 'Save changes' : 'Document unchanged'}>
return <Tooltip placement='bottom' title={isDirty ? t('controlPanel.saveChanges') : t('controlPanel.documentUnchanged')}>
<Button onClick={handleSave} disabled={!isDirty} variant='outlined' >
<SaveOutlined/>
</Button>
</Tooltip>
}, [handleSave, isDirty])
}, [handleSave, isDirty, t])

const enableTip = useMemo(() => {
return bounds
? 'Enable time controls, to filter tracks by time'
: 'No time data available'
}, [bounds])
? t('controlPanel.enableTimeControls')
: t('controlPanel.noTimeData')
}, [bounds, t])

return (
<>
Expand All @@ -82,7 +84,7 @@ const ControlPanel: React.FC<TimeProps> = ({ bounds, handleSave, isDirty }) => {
<Col span={20} style={{ textAlign: 'left' , display: 'flex', alignItems: 'center'}}>
<Tooltip
mouseEnterDelay={0.8}
title='Lock viewport to prevent accidental map movement. When time filtering, mouse wheel updates time'
title={t('controlPanel.lockViewport')}
>
<Button
style={buttonStyle}
Expand All @@ -107,7 +109,7 @@ const ControlPanel: React.FC<TimeProps> = ({ bounds, handleSave, isDirty }) => {
{time.filterApplied ? <FilterFilled /> : <FilterOutlined />}
</Button>
</Tooltip>
<Tooltip placement='bottom' title={undoRedoTitle || 'Nothing to undo/redo'}>
<Tooltip placement='bottom' title={undoRedoTitle || t('controlPanel.nothingToUndoRedo')}>
<Button
style={buttonStyle}
className='undo-redo-button'
Expand Down
16 changes: 9 additions & 7 deletions src/components/Document/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Alert, Card, ConfigProvider, Modal, Splitter, Tabs } from 'antd'
import { useCallback, useEffect, useRef, useState, useMemo } from 'react'
import { Feature, Geometry, GeoJsonProperties } from 'geojson'
import { useTranslation } from 'react-i18next'
import { useAppDispatch, useAppSelector } from '../../state/hooks'
import { useDocContext } from '../../state/DocContext'
import { useAppContext } from '../../state/AppContext'
Expand Down Expand Up @@ -51,6 +52,7 @@ function Document({ filePath, withSampleData }: { filePath?: string, withSampleD
const features = useAppSelector(selectFeatures)
const documentContents = useAppSelector(state => state.fColl.present.data)
const dispatch = useAppDispatch()
const { t } = useTranslation()
const { setTime, time, message, setMessage, interval } = useDocContext()
const [timeBounds, setTimeBounds] = useState<[number, number] | null>(null)
const [graphOpen, setGraphOpen] = useState(false)
Expand Down Expand Up @@ -148,7 +150,7 @@ function Document({ filePath, withSampleData }: { filePath?: string, withSampleD
handler.handle(await file.text(), features, dispatch)
} catch (e) {
console.error('handler error', file, handler, e)
setMessage({ title: 'Error', severity: 'error', message: 'Handling error: ' + e })
setMessage({ title: t('documents.error'), severity: 'error', message: t('documents.handlingError') + e })
}
}
}
Expand Down Expand Up @@ -185,18 +187,18 @@ function Document({ filePath, withSampleData }: { filePath?: string, withSampleD
const doc = JSON.stringify(documentContents)
await window.electron.saveFile(filePath, doc)
} else {
window.alert('Local save not supportedin browser')
window.alert(t('documents.localSaveNotSupported'))
}
}, [filePath, documentContents])
}, [filePath, documentContents, t])

const detailTabs = [ {
key: '1',
label: 'Detail',
label: t('document.detail'),
children: <Properties />
},
{
key: '2',
label: 'Graphs',
label: t('document.graphs'),
children: <GraphsPanel width={splitterWidths ? splitterWidths[0] : 300} height={splitterHeights ? splitterHeights[2] : 400} />
}]

Expand Down Expand Up @@ -240,12 +242,12 @@ function Document({ filePath, withSampleData }: { filePath?: string, withSampleD
<Splitter.Panel key='left' collapsible defaultSize='300' min='200' max='600'>
<Splitter layout="vertical" style={{ height: '100%', boxShadow: '0 0 10px rgba(0, 0, 0, 0.1)' }} onResizeEnd={handleSplitterVerticalResize}>
<Splitter.Panel style={{minHeight: '170px'}} defaultSize='170' min='170' max='170' resizable={false}>
<Card title='Control Panel'>
<Card title={t('document.controlPanel')}>
<ControlPanel isDirty={dirty} handleSave={doSave} bounds={timeBounds}/>
</Card>
</Splitter.Panel>
<Splitter.Panel style={{overflow: 'visible'}} >
<Card title='Layers' style={{width: '100%', height: '100%'}}>
<Card title={t('document.layers')} style={{width: '100%', height: '100%'}}>
{features && <Layers splitterWidths={splitterHeights ? splitterHeights[1] : 330} openGraph={() => setGraphOpen(true)} />}
</Card>
</Splitter.Panel>
Expand Down
Loading
Loading