271 lines
7.1 KiB
TypeScript
271 lines
7.1 KiB
TypeScript
import { useI18n } from '@solid-primitives/i18n';
|
|
import {
|
|
Typography,
|
|
Card,
|
|
CardContent,
|
|
CardActions,
|
|
Button,
|
|
Box,
|
|
LinearProgress,
|
|
CircularProgress,
|
|
} from '@suid/material';
|
|
import { Component, createEffect, createSignal, Show } from 'solid-js';
|
|
import { findStartTime } from '../../lib/gpx';
|
|
import GPX from '../../lib/gpx-parser-builder/src/gpx';
|
|
import dispatch from '../../workers/dispatcher-main';
|
|
import GpxChooser from '../gpx-chooser';
|
|
import { currentGpxId } from '../gpx-dialog';
|
|
import {
|
|
Filesystem as CapacitorFileSystem,
|
|
Encoding,
|
|
} from '@capacitor/filesystem';
|
|
import ExifReader from 'exifreader';
|
|
|
|
interface Props {
|
|
file: File;
|
|
}
|
|
|
|
interface StatsAndGpx {
|
|
gpx: Gpx;
|
|
stats: any;
|
|
}
|
|
|
|
interface Picture {
|
|
id: string;
|
|
date: string;
|
|
gpx: { Latitude: number; Longitude: number; Altitude: number };
|
|
thumbnailUrl: string;
|
|
}
|
|
|
|
const analyzeGpx = (gpx: Gpx | undefined) => {
|
|
if (gpx === undefined) {
|
|
return {};
|
|
}
|
|
const stats = {
|
|
creator: gpx.$.creator,
|
|
nbWpts: gpx.wpt ? gpx.wpt?.length : 0,
|
|
nbRtes: gpx.rte ? gpx.rte?.length : 0,
|
|
nbTrks: gpx.trk ? gpx.trk?.length : 0,
|
|
trkMaybeRte:
|
|
gpx.trk &&
|
|
gpx.trk[0].trkseg &&
|
|
gpx.trk[0].trkseg[0] &&
|
|
gpx.trk[0].trkseg[0].trkpt &&
|
|
!gpx.trk[0].trkseg[0].trkpt[0].time,
|
|
startTime: findStartTime(gpx),
|
|
};
|
|
|
|
return stats;
|
|
};
|
|
|
|
const ImportSingleFile: Component<Props> = ({ file: file }) => {
|
|
const [t] = useI18n();
|
|
const [statsAndGpx, setStatsAndGpx] = createSignal<StatsAndGpx>();
|
|
const [picture, setPicture] = createSignal<Picture>();
|
|
const [state, setState] = createSignal('init');
|
|
const [gpxId, setGpxId] = createSignal<string>('new');
|
|
createEffect(() => {
|
|
setGpxId(currentGpxId());
|
|
});
|
|
|
|
const parseGpx = (content: string) => {
|
|
const gpx = GPX.parse(content);
|
|
console.log({
|
|
caller: 'ImportSingleFile / JSON',
|
|
gpxFile: file,
|
|
gpx,
|
|
gpxId: gpxId(),
|
|
});
|
|
setStatsAndGpx({ gpx, stats: analyzeGpx(gpx) });
|
|
};
|
|
|
|
if (typeof file === 'string') {
|
|
CapacitorFileSystem.requestPermissions().then((permissionStatus) => {
|
|
console.log({
|
|
caller: 'ImportSingleFile / content',
|
|
permissionStatus,
|
|
});
|
|
});
|
|
CapacitorFileSystem.readFile({
|
|
path: file,
|
|
encoding: Encoding.UTF8,
|
|
}).then((content) => {
|
|
console.log({
|
|
caller: 'ImportSingleFile / content',
|
|
file,
|
|
content,
|
|
});
|
|
parseGpx(content.data);
|
|
});
|
|
} else {
|
|
const reader = new FileReader();
|
|
if (file.type === 'image/jpeg') {
|
|
reader.readAsArrayBuffer(file);
|
|
reader.addEventListener(
|
|
'load',
|
|
async () => {
|
|
const tags = ExifReader.load(reader.result, { expanded: true });
|
|
const exif = tags.exif;
|
|
const dateSegments = exif?.DateTimeOriginal?.value[0].split(' ');
|
|
const date = `${dateSegments[0].replaceAll(':', '-')}T${
|
|
dateSegments[1]
|
|
}.${exif?.SubSecTimeOriginal?.value[0] || '0'}${
|
|
exif?.OffsetTimeOriginal?.value[0]
|
|
}`;
|
|
const id = exif?.ImageUniqueID?.value[0];
|
|
const gps = tags.gps;
|
|
const thumbnail = tags.Thumbnail;
|
|
const thumbnailUrl = `data:${thumbnail?.type};base64,${thumbnail?.base64}`;
|
|
console.log({
|
|
caller: 'ImportSingleFile / Jpeg',
|
|
file,
|
|
type: file.type,
|
|
result: reader.result,
|
|
tags,
|
|
date,
|
|
id,
|
|
gps,
|
|
thumbnailUrl,
|
|
});
|
|
setPicture({ id, gps, thumbnailUrl, date });
|
|
},
|
|
false
|
|
);
|
|
} else {
|
|
// GPX
|
|
reader.readAsText(file);
|
|
reader.addEventListener(
|
|
'load',
|
|
async () => {
|
|
// this will then display a text gpxfile
|
|
console.log({
|
|
caller: 'ImportSingleFile / XML',
|
|
file,
|
|
type: file.type,
|
|
result: reader.result,
|
|
});
|
|
parseGpx(reader.result);
|
|
},
|
|
false
|
|
);
|
|
}
|
|
}
|
|
const doImport = async () => {
|
|
setState('importing');
|
|
// console.log({ caller: 'GpxImport / JSON', file, gpx });
|
|
// if (gpx) {
|
|
// const startTime = new Date(findStartTime(gpx)!);
|
|
await dispatch({
|
|
action: 'pruneAndSaveImportedGpx',
|
|
params: {
|
|
id: gpxId(),
|
|
gpx: statsAndGpx()?.gpx,
|
|
tech:
|
|
typeof file === 'string'
|
|
? {
|
|
importDate: new Date().toISOString(),
|
|
uri: file,
|
|
}
|
|
: {
|
|
lastModified: new Date(file.lastModified).toISOString(),
|
|
importDate: new Date().toISOString(),
|
|
name: file.name,
|
|
size: file.size,
|
|
type: file.type,
|
|
},
|
|
},
|
|
});
|
|
console.log({
|
|
caller: 'GpxImport / JSON / done',
|
|
gpxFile: file,
|
|
gpx: statsAndGpx()?.gpx,
|
|
});
|
|
setState('done');
|
|
|
|
// } else {
|
|
// console.error({
|
|
// message: "can't parse GPX file",
|
|
// file,
|
|
// xml: fileReader.result,
|
|
// });
|
|
// }
|
|
// // TODO: error handling
|
|
// },
|
|
// false
|
|
// );
|
|
// }
|
|
};
|
|
|
|
const [selectedTrkTransform, setSelectedTrkTransform] = createSignal(
|
|
'importNonTimedTrksAsRtes'
|
|
);
|
|
|
|
const handleTrkTransformChange = (event: any) => {
|
|
setSelectedTrkTransform(event.target.value);
|
|
};
|
|
|
|
const controlTrkTransformProps = (item: string) => ({
|
|
checked: selectedTrkTransform() === item,
|
|
onChange: handleTrkTransformChange,
|
|
value: item,
|
|
name: 'trkTransform',
|
|
label: t(item),
|
|
inputProps: { 'aria-label': item },
|
|
});
|
|
|
|
return (
|
|
<Card raised>
|
|
<CardContent>
|
|
<Typography variant='h6'>
|
|
{t('file')}
|
|
{file.name}
|
|
</Typography>
|
|
</CardContent>
|
|
<CardContent>
|
|
<Show when={statsAndGpx() !== undefined}>
|
|
<Typography variant='body1'>
|
|
{t('gpxStats', statsAndGpx().stats)}{' '}
|
|
{statsAndGpx()?.stats.startTime
|
|
? t('gpxStartTime', statsAndGpx().stats)
|
|
: t('gpxNoStartTime')}
|
|
</Typography>
|
|
<Show when={statsAndGpx().stats.trkMaybeRte}>
|
|
<Typography variant='body1'>{t('trkMaybeRte')}</Typography>
|
|
</Show>
|
|
<Show when={state() === 'importing'}>
|
|
<LinearProgress />
|
|
</Show>
|
|
<CardActions>
|
|
<GpxChooser
|
|
gpxId={gpxId}
|
|
setGpxId={setGpxId}
|
|
disabled={state() != 'init'}
|
|
/>
|
|
<Button
|
|
variant='contained'
|
|
disabled={state() != 'init'}
|
|
onClick={doImport}
|
|
>
|
|
{t('import')}
|
|
</Button>
|
|
</CardActions>
|
|
</Show>
|
|
<Show when={picture() !== undefined}>
|
|
<img src={picture()?.thumbnailUrl} />
|
|
<CardActions>
|
|
<Button
|
|
variant='contained'
|
|
disabled={state() != 'init'}
|
|
onClick={doImport}
|
|
>
|
|
{t('import')}
|
|
</Button>
|
|
</CardActions>
|
|
</Show>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
};
|
|
|
|
export default ImportSingleFile;
|