dyomedea/src/components/import/ImportSingleFile.tsx

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;