implemented export

main
Inga 🏳‍🌈 1 year ago
parent 26410e5a07
commit 26b03b75db
  1. 134
      package-lock.json
  2. 3
      package.json
  3. 19
      src/exporters/gpx.ts
  4. 14
      src/routePlanner/export.tsx
  5. 11
      src/routePlanner/index.tsx
  6. 15
      src/routePlanner/style.css
  7. 7
      src/routePlanner/types.ts
  8. 7
      src/shared/types.ts
  9. 2
      src/style.css

134
package-lock.json generated

@ -5,6 +5,8 @@
"packages": { "packages": {
"": { "": {
"dependencies": { "dependencies": {
"file-saver": "^2.0.5",
"gpx-builder": "^5.2.1",
"leaflet": "^1.9.4", "leaflet": "^1.9.4",
"nanoid": "^5.0.3", "nanoid": "^5.0.3",
"preact": "^10.13.1" "preact": "^10.13.1"
@ -17,6 +19,7 @@
"@babel/plugin-syntax-jsx": "^7.23.3", "@babel/plugin-syntax-jsx": "^7.23.3",
"@preact/preset-vite": "^2.5.0", "@preact/preset-vite": "^2.5.0",
"@tsconfig/strictest": "^2.0.2", "@tsconfig/strictest": "^2.0.2",
"@types/file-saver": "^2.0.7",
"@types/leaflet": "^1.9.8", "@types/leaflet": "^1.9.8",
"@typescript-eslint/eslint-plugin": "^6.11.0", "@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.0", "@typescript-eslint/parser": "^6.11.0",
@ -416,6 +419,17 @@
"@babel/core": "^7.0.0-0" "@babel/core": "^7.0.0-0"
} }
}, },
"node_modules/@babel/runtime": {
"version": "7.23.2",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz",
"integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==",
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/template": { "node_modules/@babel/template": {
"version": "7.22.15", "version": "7.22.15",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
@ -1041,6 +1055,50 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/@oozcitak/dom": {
"version": "1.15.10",
"resolved": "https://registry.npmjs.org/@oozcitak/dom/-/dom-1.15.10.tgz",
"integrity": "sha512-0JT29/LaxVgRcGKvHmSrUTEvZ8BXvZhGl2LASRUgHqDTC1M5g1pLmVv56IYNyt3bG2CUjDkc67wnyZC14pbQrQ==",
"dependencies": {
"@oozcitak/infra": "1.0.8",
"@oozcitak/url": "1.0.4",
"@oozcitak/util": "8.3.8"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/@oozcitak/infra": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@oozcitak/infra/-/infra-1.0.8.tgz",
"integrity": "sha512-JRAUc9VR6IGHOL7OGF+yrvs0LO8SlqGnPAMqyzOuFZPSZSXI7Xf2O9+awQPSMXgIWGtgUf/dA6Hs6X6ySEaWTg==",
"dependencies": {
"@oozcitak/util": "8.3.8"
},
"engines": {
"node": ">=6.0"
}
},
"node_modules/@oozcitak/url": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@oozcitak/url/-/url-1.0.4.tgz",
"integrity": "sha512-kDcD8y+y3FCSOvnBI6HJgl00viO/nGbQoCINmQ0h98OhnGITrWR3bOGfwYCthgcrV8AnTJz8MzslTQbC3SOAmw==",
"dependencies": {
"@oozcitak/infra": "1.0.8",
"@oozcitak/util": "8.3.8"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/@oozcitak/util": {
"version": "8.3.8",
"resolved": "https://registry.npmjs.org/@oozcitak/util/-/util-8.3.8.tgz",
"integrity": "sha512-T8TbSnGsxo6TDBJx/Sgv/BlVJL3tshxZP7Aq5R1mSnM5OcHY2dQaxLMu2+E8u3gN0MLOzdjurqN4ZRVuzQycOQ==",
"engines": {
"node": ">=8.0"
}
},
"node_modules/@pkgr/utils": { "node_modules/@pkgr/utils": {
"version": "2.4.2", "version": "2.4.2",
"resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz", "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz",
@ -1144,6 +1202,12 @@
"integrity": "sha512-jt4jIsWKvUvuY6adJnQJlb/UR7DdjC8CjHI/OaSQruj2yX9/K6+KOvDt/vD6udqos/FUk5Op66CvYT7TBLYO5Q==", "integrity": "sha512-jt4jIsWKvUvuY6adJnQJlb/UR7DdjC8CjHI/OaSQruj2yX9/K6+KOvDt/vD6udqos/FUk5Op66CvYT7TBLYO5Q==",
"dev": true "dev": true
}, },
"node_modules/@types/file-saver": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.7.tgz",
"integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==",
"dev": true
},
"node_modules/@types/geojson": { "node_modules/@types/geojson": {
"version": "7946.0.13", "version": "7946.0.13",
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.13.tgz", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.13.tgz",
@ -2955,6 +3019,18 @@
"url": "https://opencollective.com/eslint" "url": "https://opencollective.com/eslint"
} }
}, },
"node_modules/esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"bin": {
"esparse": "bin/esparse.js",
"esvalidate": "bin/esvalidate.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/esquery": { "node_modules/esquery": {
"version": "1.5.0", "version": "1.5.0",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz",
@ -3114,6 +3190,11 @@
"node": "^10.12.0 || >=12.0.0" "node": "^10.12.0 || >=12.0.0"
} }
}, },
"node_modules/file-saver": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
"integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
},
"node_modules/fill-range": { "node_modules/fill-range": {
"version": "7.0.1", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@ -3367,6 +3448,15 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/gpx-builder": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/gpx-builder/-/gpx-builder-5.2.1.tgz",
"integrity": "sha512-j7g9/bWQncZ2TaHXxjNSnNI+EaFWLQqm03A2si0dvzc78H/lWFVF770tdwEzdIFbd5eZeT4d99/UGbPmkplLBg==",
"dependencies": {
"@babel/runtime": "^7.20.7",
"xmlbuilder2": "^3.0.2"
}
},
"node_modules/graphemer": { "node_modules/graphemer": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
@ -4753,6 +4843,11 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/regenerator-runtime": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
"integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA=="
},
"node_modules/regexp.prototype.flags": { "node_modules/regexp.prototype.flags": {
"version": "1.5.1", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz",
@ -5226,6 +5321,11 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="
},
"node_modules/string-width": { "node_modules/string-width": {
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
@ -5875,6 +5975,40 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true "dev": true
}, },
"node_modules/xmlbuilder2": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-3.1.1.tgz",
"integrity": "sha512-WCSfbfZnQDdLQLiMdGUQpMxxckeQ4oZNMNhLVkcekTu7xhD4tuUDyAPoY8CwXvBYE6LwBHd6QW2WZXlOWr1vCw==",
"dependencies": {
"@oozcitak/dom": "1.15.10",
"@oozcitak/infra": "1.0.8",
"@oozcitak/util": "8.3.8",
"js-yaml": "3.14.1"
},
"engines": {
"node": ">=12.0"
}
},
"node_modules/xmlbuilder2/node_modules/argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dependencies": {
"sprintf-js": "~1.0.2"
}
},
"node_modules/xmlbuilder2/node_modules/js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"dependencies": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/yallist": { "node_modules/yallist": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",

@ -11,6 +11,8 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"file-saver": "^2.0.5",
"gpx-builder": "^5.2.1",
"leaflet": "^1.9.4", "leaflet": "^1.9.4",
"nanoid": "^5.0.3", "nanoid": "^5.0.3",
"preact": "^10.13.1" "preact": "^10.13.1"
@ -23,6 +25,7 @@
"@babel/plugin-syntax-jsx": "^7.23.3", "@babel/plugin-syntax-jsx": "^7.23.3",
"@preact/preset-vite": "^2.5.0", "@preact/preset-vite": "^2.5.0",
"@tsconfig/strictest": "^2.0.2", "@tsconfig/strictest": "^2.0.2",
"@types/file-saver": "^2.0.7",
"@types/leaflet": "^1.9.8", "@types/leaflet": "^1.9.8",
"@typescript-eslint/eslint-plugin": "^6.11.0", "@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.0", "@typescript-eslint/parser": "^6.11.0",

@ -0,0 +1,19 @@
import { saveAs } from 'file-saver';
import { GarminBuilder, buildGPX } from 'gpx-builder';
import { Waypoint } from '../shared/types';
export const exportMarkersToGpx = (waypoints: Waypoint[]) => {
const builder = new GarminBuilder();
builder.setSegmentPoints(
waypoints.map(
({ coordinates }) =>
new GarminBuilder.MODELS.Point(
coordinates.lat,
coordinates.lng,
),
),
);
const gpx = buildGPX(builder.toObject());
const gpxBlob = new Blob([gpx], { type: 'application/gpx+xml' });
saveAs(gpxBlob, 'route.gpx');
};

@ -0,0 +1,14 @@
import { exportMarkersToGpx } from '../exporters/gpx';
import { InternalProps } from './types';
import 'leaflet/dist/leaflet.css';
export const ExportComponent = ({ markers }: InternalProps) => {
return (
<>
<button onClick={() => exportMarkersToGpx(markers)}>
Download your route
</button>
</>
);
};

@ -1,10 +1,12 @@
import { nanoid } from 'nanoid';
import { useMemo, useState } from 'preact/hooks'; import { useMemo, useState } from 'preact/hooks';
import { Coordinates } from '../shared/types';
import { ExportComponent } from './export';
import { MapComponent } from './map'; import { MapComponent } from './map';
import { Coordinates, Marker } from './types'; import { MarkersComponent } from './markers';
import { Marker } from './types';
import './style.css'; import './style.css';
import { MarkersComponent } from './markers';
import { nanoid } from 'nanoid';
export const RoutePlanner = () => { export const RoutePlanner = () => {
const [markers, setMarkers] = useState<Marker[]>([]); const [markers, setMarkers] = useState<Marker[]>([]);
@ -41,6 +43,9 @@ export const RoutePlanner = () => {
<section class="markers"> <section class="markers">
<MarkersComponent markers={markers} /> <MarkersComponent markers={markers} />
</section> </section>
<section class="export">
<ExportComponent markers={markers} />
</section>
<section class="map"> <section class="map">
<MapComponent onMapClick={onMapClick} markers={markers} /> <MapComponent onMapClick={onMapClick} markers={markers} />
</section> </section>

@ -1,15 +1,23 @@
section.route-planner { section.route-planner {
width: 100%; width: 100%;
display: grid; display: grid;
grid-template-columns: 1fr 2fr;
column-gap: 1rem; column-gap: 1rem;
grid-template-areas: "markers map"; row-gap: 1rem;
grid-template-columns: 1fr 2fr;
grid-template-rows: 1fr min-content;
grid-template-areas:
"markers map"
"export map";
} }
section.route-planner > section.markers { section.route-planner > section.markers {
grid-area: markers; grid-area: markers;
} }
section.route-planner > section.export {
grid-area: export;
}
section.route-planner > section.map { section.route-planner > section.map {
grid-area: map; grid-area: map;
} }
@ -30,9 +38,10 @@ section.route-planner .map-container .leaflet-tooltip-pane .text {
@media (max-width: 639px) { @media (max-width: 639px) {
section.route-planner { section.route-planner {
grid-template-columns: 1fr; grid-template-columns: 1fr;
row-gap: 1rem; grid-template-rows: 1fr min-content 2fr;
grid-template-areas: grid-template-areas:
"markers" "markers"
"export"
"map"; "map";
} }

@ -1,12 +1,9 @@
import type { LatLng } from 'leaflet'; import { Coordinates, Waypoint } from '../shared/types';
export type Coordinates = LatLng; export type Marker = Waypoint & {
export type Marker = {
key: string; key: string;
remove: () => void; remove: () => void;
index: number; index: number;
coordinates: Coordinates;
}; };
export type InternalProps = { export type InternalProps = {

@ -0,0 +1,7 @@
import type { LatLng } from 'leaflet';
export type Coordinates = LatLng;
export type Waypoint = {
coordinates: Coordinates;
};

@ -14,7 +14,7 @@
} }
body { body {
padding: 1em; padding: 1rem;
display: flex; display: flex;
align-items: center; align-items: center;
} }

Loading…
Cancel
Save