Kenneth Bruen
3 years ago
commit
5a4c97ba58
11 changed files with 1776 additions and 0 deletions
@ -0,0 +1,6 @@
|
||||
/node_modules/ |
||||
/public/build/ |
||||
|
||||
.DS_Store |
||||
|
||||
.vscode |
@ -0,0 +1,32 @@
|
||||
{ |
||||
"name": "svelte-app", |
||||
"version": "1.0.0", |
||||
"private": true, |
||||
"scripts": { |
||||
"build": "rollup -c", |
||||
"dev": "rollup -c -w", |
||||
"start": "sirv public --no-clear", |
||||
"check": "svelte-check --tsconfig ./tsconfig.json" |
||||
}, |
||||
"devDependencies": { |
||||
"@rollup/plugin-commonjs": "^17.0.0", |
||||
"@rollup/plugin-node-resolve": "^11.0.0", |
||||
"@rollup/plugin-typescript": "^8.0.0", |
||||
"@tsconfig/svelte": "^2.0.0", |
||||
"@types/leaflet": "^1.7.4", |
||||
"eslint": "^7.32.0", |
||||
"rollup": "^2.3.4", |
||||
"rollup-plugin-css-only": "^3.1.0", |
||||
"rollup-plugin-livereload": "^2.0.0", |
||||
"rollup-plugin-svelte": "^7.0.0", |
||||
"rollup-plugin-terser": "^7.0.0", |
||||
"svelte": "^3.0.0", |
||||
"svelte-check": "^2.0.0", |
||||
"svelte-preprocess": "^4.0.0", |
||||
"tslib": "^2.0.0", |
||||
"typescript": "^4.0.0" |
||||
}, |
||||
"dependencies": { |
||||
"sirv-cli": "^1.0.0" |
||||
} |
||||
} |
After Width: | Height: | Size: 3.1 KiB |
@ -0,0 +1,63 @@
|
||||
html, body { |
||||
position: relative; |
||||
width: 100%; |
||||
height: 100%; |
||||
} |
||||
|
||||
body { |
||||
color: #333; |
||||
margin: 0; |
||||
padding: 8px; |
||||
box-sizing: border-box; |
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; |
||||
} |
||||
|
||||
a { |
||||
color: rgb(0,100,200); |
||||
text-decoration: none; |
||||
} |
||||
|
||||
a:hover { |
||||
text-decoration: underline; |
||||
} |
||||
|
||||
a:visited { |
||||
color: rgb(0,80,160); |
||||
} |
||||
|
||||
label { |
||||
display: block; |
||||
} |
||||
|
||||
input, button, select, textarea { |
||||
font-family: inherit; |
||||
font-size: inherit; |
||||
-webkit-padding: 0.4em 0; |
||||
padding: 0.4em; |
||||
margin: 0 0 0.5em 0; |
||||
box-sizing: border-box; |
||||
border: 1px solid #ccc; |
||||
border-radius: 2px; |
||||
} |
||||
|
||||
input:disabled { |
||||
color: #ccc; |
||||
} |
||||
|
||||
button { |
||||
color: #333; |
||||
background-color: #f4f4f4; |
||||
outline: none; |
||||
} |
||||
|
||||
button:disabled { |
||||
color: #999; |
||||
} |
||||
|
||||
button:not(:disabled):active { |
||||
background-color: #ddd; |
||||
} |
||||
|
||||
button:focus { |
||||
border-color: #666; |
||||
} |
@ -0,0 +1,28 @@
|
||||
<!DOCTYPE html> |
||||
<html lang="en"> |
||||
<head> |
||||
<meta charset='utf-8'> |
||||
<meta name='viewport' content='width=device-width,initial-scale=1'> |
||||
|
||||
<title>Location Preview</title> |
||||
|
||||
<link rel='icon' type='image/png' href='/favicon.png'> |
||||
<link rel='stylesheet' href='/global.css'> |
||||
<link rel='stylesheet' href='/build/bundle.css'> |
||||
|
||||
<!-- leaflet --> |
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" |
||||
integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A==" |
||||
crossorigin=""/> |
||||
|
||||
<script defer src='/build/bundle.js'></script> |
||||
|
||||
<!-- leaflet --> |
||||
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js" |
||||
integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA==" |
||||
crossorigin=""></script> |
||||
</head> |
||||
|
||||
<body> |
||||
</body> |
||||
</html> |
@ -0,0 +1,84 @@
|
||||
import svelte from 'rollup-plugin-svelte'; |
||||
import commonjs from '@rollup/plugin-commonjs'; |
||||
import resolve from '@rollup/plugin-node-resolve'; |
||||
import livereload from 'rollup-plugin-livereload'; |
||||
import { terser } from 'rollup-plugin-terser'; |
||||
import sveltePreprocess from 'svelte-preprocess'; |
||||
import typescript from '@rollup/plugin-typescript'; |
||||
import css from 'rollup-plugin-css-only'; |
||||
|
||||
const production = !process.env.ROLLUP_WATCH; |
||||
|
||||
function serve() { |
||||
let server; |
||||
|
||||
function toExit() { |
||||
if (server) server.kill(0); |
||||
} |
||||
|
||||
return { |
||||
writeBundle() { |
||||
if (server) return; |
||||
server = require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], { |
||||
stdio: ['ignore', 'inherit', 'inherit'], |
||||
shell: true |
||||
}); |
||||
|
||||
process.on('SIGTERM', toExit); |
||||
process.on('exit', toExit); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
export default { |
||||
input: 'src/main.ts', |
||||
output: { |
||||
sourcemap: true, |
||||
format: 'iife', |
||||
name: 'app', |
||||
file: 'public/build/bundle.js', |
||||
}, |
||||
plugins: [ |
||||
svelte({ |
||||
preprocess: sveltePreprocess({ sourceMap: !production }), |
||||
compilerOptions: { |
||||
// enable run-time checks when not in production
|
||||
dev: !production |
||||
} |
||||
}), |
||||
// we'll extract any component CSS out into
|
||||
// a separate file - better for performance
|
||||
css({ output: 'bundle.css' }), |
||||
|
||||
// If you have external dependencies installed from
|
||||
// npm, you'll most likely need these plugins. In
|
||||
// some cases you'll need additional configuration -
|
||||
// consult the documentation for details:
|
||||
// https://github.com/rollup/plugins/tree/master/packages/commonjs
|
||||
resolve({ |
||||
browser: true, |
||||
dedupe: ['svelte'] |
||||
}), |
||||
commonjs(), |
||||
typescript({ |
||||
sourceMap: !production, |
||||
inlineSources: !production |
||||
}), |
||||
|
||||
// In dev mode, call `npm run start` once
|
||||
// the bundle has been generated
|
||||
!production && serve(), |
||||
|
||||
// Watch the `public` directory and refresh the
|
||||
// browser on changes when not in production
|
||||
!production && livereload('public'), |
||||
|
||||
// If we're building for production (npm run build
|
||||
// instead of npm run dev), minify
|
||||
production && terser() |
||||
], |
||||
watch: { |
||||
clearScreen: false |
||||
}, |
||||
external: ['leaflet'], |
||||
}; |
@ -0,0 +1,219 @@
|
||||
<script lang="ts"> |
||||
import L from 'leaflet'; |
||||
import { onMount } from 'svelte'; |
||||
|
||||
let MAPBOX_ACCESS_TOKEN: string | undefined = void 0; |
||||
|
||||
let error: string | undefined = void 0; |
||||
let followLocation = true; |
||||
|
||||
let darkMode = false; |
||||
let map: L.Map | undefined = void 0; |
||||
const mapLayers = { |
||||
dark: L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', { |
||||
attribution: 'Map data © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>', |
||||
maxZoom: 18, |
||||
id: 'mapbox/dark-v10', |
||||
tileSize: 512, |
||||
zoomOffset: -1, |
||||
accessToken: MAPBOX_ACCESS_TOKEN, |
||||
}), |
||||
light: L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', { |
||||
attribution: 'Map data © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>', |
||||
maxZoom: 18, |
||||
id: 'mapbox/light-v10', |
||||
tileSize: 512, |
||||
zoomOffset: -1, |
||||
accessToken: MAPBOX_ACCESS_TOKEN, |
||||
}), |
||||
}; |
||||
const locationCircleLayerGroup = L.layerGroup(); |
||||
$: if (map) { |
||||
locationCircleLayerGroup.addTo(map); |
||||
} |
||||
|
||||
const geolocation = navigator.geolocation; |
||||
if (!geolocation) { |
||||
error = 'Geolocation not available'; |
||||
} |
||||
let geoWatchHandle: ReturnType<InstanceType<typeof Geolocation>["watchPosition"]> | undefined = void 0; |
||||
|
||||
if (window.matchMedia) { |
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => { |
||||
if (!darkMode && e.matches) { |
||||
darkMode = true; |
||||
} |
||||
else if (darkMode && !e.matches) { |
||||
darkMode = false; |
||||
} |
||||
}); |
||||
} |
||||
|
||||
onMount(async () => { |
||||
const matResp = await fetch('/MAPBOX_ACCESS_TOKEN.txt'); |
||||
MAPBOX_ACCESS_TOKEN = (await matResp.text()).trim(); |
||||
|
||||
map = L.map('mapid', { |
||||
zoomDelta: 1, |
||||
zoomSnap: 0.25, |
||||
}).setView([0, 0], 1); |
||||
}); |
||||
|
||||
// Set light or dark mode MapBox layer |
||||
$: { |
||||
if (map) { |
||||
if (!darkMode) { |
||||
mapLayers.light.addTo(map); |
||||
} |
||||
else { |
||||
mapLayers.dark.addTo(map); |
||||
} |
||||
} |
||||
} |
||||
|
||||
let coords: GeolocationCoordinates | undefined = void 0; |
||||
$: if (coords) { |
||||
locationCircleLayerGroup.clearLayers(); |
||||
L.circle({lat: coords.latitude, lng: coords.longitude, alt: coords.altitude}, { |
||||
radius: coords.accuracy, |
||||
opacity: 0.2, |
||||
}).addTo(locationCircleLayerGroup); |
||||
L.circle([coords.latitude, coords.longitude], { |
||||
radius: 10, |
||||
opacity: 0.75, |
||||
}).addTo(locationCircleLayerGroup); |
||||
} |
||||
else { |
||||
locationCircleLayerGroup.clearLayers(); |
||||
} |
||||
$: if (coords && followLocation) { |
||||
map.fitBounds( |
||||
L.latLng(coords.latitude, coords.longitude).toBounds(coords.accuracy * 2), |
||||
{ |
||||
animate: true, |
||||
duration: 1, |
||||
}, |
||||
); |
||||
} else if (map) { |
||||
map.setZoom(1, { |
||||
animate: true, |
||||
duration: 5, |
||||
}); |
||||
} |
||||
|
||||
function onPosition(e: GeolocationPosition) { |
||||
error = void 0; |
||||
coords = e.coords; |
||||
} |
||||
|
||||
function onLocationButtonClick() { |
||||
if (geoWatchHandle === void 0) { |
||||
geoWatchHandle = geolocation.watchPosition(onPosition, (e) => { |
||||
setTimeout(() => {geoWatchHandle = void 0;}, 0); |
||||
if (e.code === e.PERMISSION_DENIED) { |
||||
error = 'Geolocation Permission Denied'; |
||||
} |
||||
else if (e.code === e.POSITION_UNAVAILABLE) { |
||||
error = 'Geolocation Unavailable'; |
||||
} |
||||
else { |
||||
error = `Unknown Error: ${e}`; |
||||
} |
||||
}, { |
||||
enableHighAccuracy: true, |
||||
}); |
||||
} |
||||
else { |
||||
geolocation.clearWatch(geoWatchHandle); |
||||
geoWatchHandle = void 0; |
||||
setTimeout(() => { |
||||
coords = void 0; |
||||
}, 1000); |
||||
} |
||||
} |
||||
|
||||
function toggleFollowLocation() { |
||||
followLocation = !followLocation; |
||||
} |
||||
</script> |
||||
|
||||
<main> |
||||
<h1>Location Preview</h1> |
||||
{#if error} |
||||
<p class="error">{error}</p> |
||||
{/if} |
||||
{#if coords} |
||||
<div id="coordsDiv"> |
||||
<table> |
||||
<tr> |
||||
<th>Field</th> |
||||
<th>Value</th> |
||||
</tr> |
||||
<tr> |
||||
<td>Latitude</td> |
||||
<td>{coords.latitude}</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Longitude</td> |
||||
<td>{coords.longitude}</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Accuracy</td> |
||||
<td>{Math.round(coords.accuracy)} m</td> |
||||
</tr> |
||||
</table> |
||||
</div> |
||||
{/if} |
||||
<div id="button-area"> |
||||
{#key geoWatchHandle} |
||||
<button on:click={onLocationButtonClick}>{geoWatchHandle ? "Stop Watching" : "Find Location"}</button> |
||||
{/key} |
||||
{#key followLocation} |
||||
<button on:click={toggleFollowLocation}>{followLocation ? "Stop Zooming to Location" : "Zoom to Location"}</button> |
||||
{/key} |
||||
</div> |
||||
|
||||
<div id="mapid"></div> |
||||
</main> |
||||
|
||||
<style> |
||||
main { |
||||
text-align: center; |
||||
padding: 1em; |
||||
margin: 0 auto; |
||||
} |
||||
|
||||
h1 { |
||||
color: #ff3e00; |
||||
text-transform: uppercase; |
||||
font-size: 2em; |
||||
font-weight: 100; |
||||
} |
||||
|
||||
p.error { |
||||
font-size: 1.25em; |
||||
color: #D32F2F; |
||||
border-left: 5px #D32F2F solid; |
||||
margin-left: auto; |
||||
margin-right: auto; |
||||
} |
||||
|
||||
#mapid { |
||||
height: 300px; |
||||
max-width: 700px; |
||||
margin-left: auto; |
||||
margin-right: auto; |
||||
} |
||||
|
||||
#coordsDiv { |
||||
display: flex; |
||||
justify-content: center; |
||||
} |
||||
#coordsDiv table { |
||||
border-collapse: collapse; |
||||
} |
||||
#coordsDiv td { |
||||
border: 1px solid black; |
||||
padding: 8px; |
||||
} |
||||
</style> |
@ -0,0 +1,9 @@
|
||||
import App from './App.svelte'; |
||||
|
||||
const app = new App({ |
||||
target: document.body, |
||||
props: { |
||||
} |
||||
}); |
||||
|
||||
export default app; |
@ -0,0 +1,9 @@
|
||||
{ |
||||
"extends": "@tsconfig/svelte/tsconfig.json", |
||||
|
||||
"include": ["src/**/*"], |
||||
"exclude": ["node_modules/*", "__sapper__/*", "public/*"], |
||||
"compilerOptions": { |
||||
"sourceMap": true |
||||
} |
||||
} |
Loading…
Reference in new issue