Reintroducing WebWorkers (pfeeew, Vite makes it tough !)

This commit is contained in:
Eric van der Vlist 2022-11-26 19:36:55 +01:00
parent 02f02984e5
commit 5de30283b1
52 changed files with 3362 additions and 47 deletions

710
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -27,10 +27,16 @@
"@capacitor/android": "^3.4.3",
"@capacitor/core": "^3.4.3",
"@capacitor/ios": "^3.4.3",
"@esbuild-plugins/node-globals-polyfill": "^0.1.1",
"@esbuild-plugins/node-modules-polyfill": "^0.1.4",
"@solidjs/router": "^0.5.1",
"@suid/icons-material": "^0.5.1",
"@suid/material": "^0.8.0",
"lodash": "^4.17.21",
"ol": "^7.1.0",
"pouchdb": "^7.3.1",
"pouchdb-browser": "^7.3.1",
"pouchdb-find": "^7.3.1",
"solid-js": "^1.3.12"
}
}

86
src/db/gpx.test.ts Normal file
View File

@ -0,0 +1,86 @@
import { initDb } from '.';
import { existsGpx, putNewGpx } from './gpx';
declare global {
var db: any;
var dbReady: boolean;
}
const originalDb = globalThis.db;
const originalDateNow = globalThis.Date.now;
describe('The gpx module', () => {
beforeEach(() => {
globalThis.db = { put: jest.fn() };
globalThis.Date.now = () => 0;
});
afterEach(() => {
globalThis.db = originalDb;
globalThis.Date.now = originalDateNow;
});
test('db.put() a new Gpx when required', async () => {
await putNewGpx({ gpx: 0 });
expect(globalThis.db.put).toBeCalledWith({
_id: 'gpx/0000000000000000',
_rev: undefined,
doc: {
$: {
creator: 'dyomedea version 0.000002',
version: '1.1',
xmlns: 'http://www.topografix.com/GPX/1/1',
'xmlns:dyo': 'http://xmlns.dyomedea.com/',
'xmlns:gpxtpx':
'http://www.garmin.com/xmlschemas/TrackPointExtension/v1',
'xmlns:gpxx': 'http://www.garmin.com/xmlschemas/GpxExtensions/v3',
'xmlns:wptx1':
'http://www.garmin.com/xmlschemas/WaypointExtension/v1',
'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
'xsi:schemaLocation':
'http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www8.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/WaypointExtension/v1 http://www8.garmin.com/xmlschemas/WaypointExtensionv1.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd',
},
extensions: undefined,
metadata: {
author: undefined,
bounds: undefined,
copyright: undefined,
desc: undefined,
extensions: undefined,
keywords: undefined,
link: undefined,
name: undefined,
time: '1970-01-01T00:00:00.000Z',
},
rte: undefined,
trk: undefined,
wpt: undefined,
},
type: 'gpx',
});
});
test('db.put() generates an id if needed', async () => {
const id = await putNewGpx();
expect(id).toEqual({ gpx: 4320000000000000 });
});
});
describe('The gpx module with a real db', () => {
beforeEach(async () => {
await initDb({});
globalThis.Date.now = () => 0;
});
afterEach(async () => {
await db.destroy();
db = undefined;
globalThis.Date.now = originalDateNow;
});
test("existsGpx returns false if the GPX doesn't exist", async () => {
const exists = await existsGpx({ gpx: 1 });
expect(exists).toBeFalsy();
});
test('existsGpx returns false if the GPX exists', async () => {
const id = { gpx: 1 };
await putNewGpx(id);
const exists = await existsGpx(id);
expect(exists).toBeTruthy();
});
});

269
src/db/gpx.ts Normal file
View File

@ -0,0 +1,269 @@
import { PureComponent } from 'react';
import { Point, Rectangle } from '../components/map/types';
import { lat2tile, lon2tile, rectanglesIntersect } from '../lib/geo';
import getUri, { intToGpxId } from '../lib/ids';
import { get, getDocsByType, getFamily, put, putAll } from './lib';
const emptyGpx: Gpx = {
$: {
version: '1.1',
creator: 'dyomedea version 0.000002',
xmlns: 'http://www.topografix.com/GPX/1/1',
'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
'xsi:schemaLocation':
'http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www8.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/WaypointExtension/v1 http://www8.garmin.com/xmlschemas/WaypointExtensionv1.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd',
'xmlns:gpxx': 'http://www.garmin.com/xmlschemas/GpxExtensions/v3',
'xmlns:wptx1': 'http://www.garmin.com/xmlschemas/WaypointExtension/v1',
'xmlns:gpxtpx': 'http://www.garmin.com/xmlschemas/TrackPointExtension/v1',
'xmlns:dyo': 'http://xmlns.dyomedea.com/',
},
metadata: {
name: undefined,
desc: undefined,
author: undefined,
copyright: undefined,
link: undefined,
time: undefined,
keywords: undefined,
bounds: undefined,
extensions: undefined,
},
wpt: undefined,
rte: undefined,
trk: undefined,
extensions: undefined,
};
export const putNewGpx = async (
id: IdGpx = { gpx: intToGpxId(Date.now()) }
) => {
const uri = getUri('gpx', id);
await put(
uri,
'gpx',
(gpx) => {
(gpx.metadata ??= {}).time = new Date(Date.now()).toISOString();
return gpx;
},
emptyGpx
);
return id;
};
export const existsGpx = async (id: IdGpx) => {
const uri = getUri('gpx', id);
try {
await get(uri);
return true;
} catch {
return false;
}
};
const prune = (id: any, object: any, docs: any[]) => {
if (typeof object === 'object') {
for (const key in object) {
if (
key === 'wpt' ||
key === 'rte' ||
key === 'rtept' ||
key === 'trk' ||
key === 'trkseg' ||
key === 'trkpt'
) {
const subObjects = object[key];
for (const index in subObjects) {
const subId = { ...id };
subId[key] = index;
docs.push({
_id: getUri(key, subId),
type: key,
doc: subObjects[index],
});
prune(subId, subObjects[index], docs);
}
object[key] = undefined;
} else prune(id, object[key], docs);
}
}
};
const extensionsFromObject = (
object: any,
extensions = {
viewport: { topLeft: <Point>{}, bottomRight: <Point>{} },
bbox: {
minLon: <number | undefined>undefined,
minLat: <number | undefined>undefined,
maxLon: <number | undefined>undefined,
maxLat: <number | undefined>undefined,
},
}
) => {
if (typeof object === 'object') {
if ('$' in object) {
const attributes = object.$;
if ('lat' in attributes) {
const lat = +attributes.lat;
if (
extensions.bbox.minLat === undefined ||
lat < extensions.bbox.minLat
) {
extensions.bbox.minLat = lat;
}
if (
extensions.bbox.maxLat === undefined ||
lat > extensions.bbox.maxLat
) {
extensions.bbox.maxLat = lat;
}
}
if ('lon' in attributes) {
const lon = +attributes.lon;
if (
extensions.bbox.minLon === undefined ||
lon < extensions.bbox.minLon
) {
extensions.bbox.minLon = lon;
}
if (
extensions.bbox.maxLon === undefined ||
lon > extensions.bbox.maxLon
) {
extensions.bbox.maxLon = lon;
}
}
}
for (const key in object) {
extensionsFromObject(object[key], extensions);
}
}
return extensions;
};
const extensionsFromGpx = (gpx: Gpx) => {
const extensions = { ...gpx.extensions, ...extensionsFromObject(gpx) };
gpx.extensions = undefined;
if (
extensions.bbox.maxLat !== undefined &&
extensions.bbox.minLon !== undefined
) {
extensions.viewport.topLeft = {
x: lon2tile(extensions.bbox.minLon, 0),
y: lat2tile(extensions.bbox.maxLat, 0),
};
}
if (
extensions.bbox.minLat !== undefined &&
extensions.bbox.maxLon !== undefined
) {
extensions.viewport.bottomRight = {
x: lon2tile(extensions.bbox.maxLon, 0),
y: lat2tile(extensions.bbox.minLat, 0),
};
}
return extensions;
};
export const pruneAndSaveImportedGpx = async (params: any) => {
const { id, gpx, extensions } = params;
let docs: any[] = [
{ _id: getUri('gpx', id), type: 'gpx', doc: gpx },
{
_id: getUri('extensions', id),
type: 'extensions',
doc: { ...extensions, ...extensionsFromGpx(gpx) },
},
];
prune(id, gpx, docs);
console.log(JSON.stringify(docs));
try {
const result = await putAll(docs);
console.log(JSON.stringify(result));
} catch (err) {
console.error(`error: ${err}`);
}
};
export const getGpxesForViewport = async (params: any) => {
const { viewport, zoomLevel } = params;
const zoomedViewport: Rectangle = {
topLeft: {
x: viewport.topLeft.x / 2 ** zoomLevel,
y: viewport.topLeft.y / 2 ** zoomLevel,
},
bottomRight: {
x: (viewport.bottomRight.x + 1) / 2 ** zoomLevel,
y: (viewport.bottomRight.y + 1) / 2 ** zoomLevel,
},
};
const allExtensions = await getDocsByType('extensions');
console.log(
`getGpxesForViewport, allExtensions: ${JSON.stringify(allExtensions)}`
);
return allExtensions
.filter((extensions: any) => {
return rectanglesIntersect(zoomedViewport, extensions.doc.viewport);
})
.map((extensions: any) =>
getUri('gpx', getUri('extensions', extensions._id))
);
};
const appendToArray = (target: any, key: string, value: any) => {
if (!(key in target)) {
target[key] = <any>[];
}
target[key].push(value);
};
export const getFullGpx = async (params: any) => {
const { id } = params;
const docs = await getFamily(id, { include_docs: true });
let target: any[];
let gpx: Gpx | undefined = undefined;
docs.rows.forEach((row: any) => {
// level 0
if (row.doc.type === 'gpx') {
target = [row.doc.doc];
gpx = row.doc.doc;
}
//level 1
if (
row.doc.type === 'wpt' ||
row.doc.type === 'rte' ||
row.doc.type === 'trk' ||
row.doc.type === 'extensions'
) {
target.splice(1);
appendToArray(target.at(-1), row.doc.type, row.doc.doc);
target.push(row.doc.doc);
}
// level 2
if (row.doc.type === 'rtept' || row.doc.type === 'trkseg') {
target.splice(2);
appendToArray(target.at(-1), row.doc.type, row.doc.doc);
target.push(row.doc.doc);
}
// level 3
if (row.doc.type === 'trkpt') {
appendToArray(target.at(-1), row.doc.type, row.doc.doc);
}
});
return gpx;
};
export const getGpx = async (params: any) => {
const { id } = params;
const docs = await getFamily(id, { include_docs: true });
console.log(`getGpx, uri: ${id} docs: ${JSON.stringify(docs)}`);
return docs.rows.filter(
(row: any) =>
row.doc.type === 'gpx' ||
row.doc.type === 'wpt' ||
row.doc.type === 'rte' ||
row.doc.type === 'trk' ||
row.doc.type === 'extensions'
);
};

117
src/db/index.ts Normal file
View File

@ -0,0 +1,117 @@
import _ from 'lodash';
import PouchDB from 'pouchdb';
import PouchDBFind from 'pouchdb-find';
import uri from '../lib/ids';
PouchDB.plugin(PouchDBFind);
const dbDefinitionId = uri('dbdef', {});
const currentDbDefinition = {
_id: dbDefinitionId,
type: dbDefinitionId,
def: { version: '0.000001' },
};
declare global {
var db: any;
var dbReady: boolean;
}
export const initDb = async (params: any) => {
if (globalThis.db === undefined) {
globalThis.db = new PouchDB('dyomedea', {
auto_compaction: true,
revs_limit: 10,
});
}
const db = globalThis.db;
var previousDbDefinition = {
_id: dbDefinitionId,
type: dbDefinitionId,
def: { version: '0' },
};
try {
previousDbDefinition = await db.get(dbDefinitionId);
} catch (error: any) {
if (error.status !== 404) {
console.log(
`Unexpected error fetching db definition: ${JSON.stringify(error)}`
);
return;
}
}
if (previousDbDefinition.def.version < currentDbDefinition.def.version) {
previousDbDefinition.def = currentDbDefinition.def;
db.put(previousDbDefinition);
// TODO: support migrations
}
await await db.compact();
await db.viewCleanup();
// WARNING: defs must use the canonical form and be identical to what will be returned by db.getIndexes
const requiredIndexes: any = [
{
name: 'type',
def: {
fields: [{ type: 'asc' }],
},
},
];
const existingIndexes = (await db.getIndexes()).indexes;
const pruneIndex = ({ name, def }: any) => ({ name, def });
const isSameIndex = (idx1: any, idx2: any) => {
return _.isEqual(pruneIndex(idx1), pruneIndex(idx2));
};
const findIndex = (targetIndexes: any, index: any) =>
targetIndexes.find((targetIndex: any) => {
return isSameIndex(targetIndex, index);
});
for (var index of existingIndexes) {
if (index.type === 'json') {
// Non system indexes
// console.log(`Checking existing index :${JSON.stringify(index)}`);
if (!findIndex(requiredIndexes, index)) {
// console.log(`db.deleteIndex(${JSON.stringify(index)})`);
await db.deleteIndex(index);
}
}
}
for (index of requiredIndexes) {
if (!findIndex(existingIndexes, index)) {
// console.log(`db.createIndex(${JSON.stringify(index)})`);
await db.createIndex({ name: index.name, ...index.def });
}
}
globalThis.dbReady = true;
/* const indexes = await db.getIndexes();
console.log(`indexes: ${JSON.stringify(indexes)}`);
const explain1 = await db.explain({
selector: {
type: 'trkpt',
gpx: 'xxxx',
},
// sort: ['trkpt.time'],
// use_index: 'type-trkpt-gpx-time',
});
console.log(`explain1: ${JSON.stringify(explain1)}`);
const explain2 = await db.explain({
selector: {
type: 'gpx',
},
// sort: ['trkpt.time'],
// use_index: 'type-trkpt-gpx-time',
});
console.log(`explain2: ${JSON.stringify(explain2)}`);
*/
};

299
src/db/lib.test.ts Normal file
View File

@ -0,0 +1,299 @@
import { initDb } from '.';
import uri from '../lib/ids';
import { getDocsByType, getFamily } from './lib';
import { putNewRte } from './rte';
import { putNewRtept } from './rtept';
import { putNewTrk } from './trk';
import { putNewTrkpt } from './trkpt';
import { putNewTrkseg } from './trkseg';
import { putNewWpt } from './wpt';
declare global {
var db: any;
}
const originalDateNow = globalThis.Date.now;
describe('getFamily', () => {
beforeEach(async () => {
await initDb({});
globalThis.Date.now = () => 0;
});
afterEach(async () => {
await db.destroy();
db = undefined;
globalThis.Date.now = originalDateNow;
});
test('returns two rows after a gpx and a track have been inserted.', async () => {
await putNewTrk();
const allDocs: any = await getFamily('gpx/');
expect(allDocs).toMatchInlineSnapshot(`
Object {
"offset": 0,
"rows": Array [
Object {
"id": "gpx/4320000000000000",
"key": "gpx/4320000000000000",
"value": Object {
"rev": "1-49baa096ec0c89962f2cafd3ff50b80b",
},
},
Object {
"id": "gpx/4320000000000000/3trk/000000",
"key": "gpx/4320000000000000/3trk/000000",
"value": Object {
"rev": "1-4c114f3ae0073151e4082ff1d220c2a4",
},
},
],
"total_rows": 4,
}
`);
});
test('also returns the docs if required.', async () => {
await putNewTrk();
const allDocs: any = await getFamily('gpx/', {
include_docs: true,
});
expect(allDocs).toMatchInlineSnapshot(`
Object {
"offset": 0,
"rows": Array [
Object {
"doc": Object {
"_id": "gpx/4320000000000000",
"_rev": "1-49baa096ec0c89962f2cafd3ff50b80b",
"doc": Object {
"$": Object {
"creator": "dyomedea version 0.000002",
"version": "1.1",
"xmlns": "http://www.topografix.com/GPX/1/1",
"xmlns:dyo": "http://xmlns.dyomedea.com/",
"xmlns:gpxtpx": "http://www.garmin.com/xmlschemas/TrackPointExtension/v1",
"xmlns:gpxx": "http://www.garmin.com/xmlschemas/GpxExtensions/v3",
"xmlns:wptx1": "http://www.garmin.com/xmlschemas/WaypointExtension/v1",
"xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
"xsi:schemaLocation": "http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www8.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/WaypointExtension/v1 http://www8.garmin.com/xmlschemas/WaypointExtensionv1.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd",
},
"metadata": Object {
"time": "1970-01-01T00:00:00.000Z",
},
},
"type": "gpx",
},
"id": "gpx/4320000000000000",
"key": "gpx/4320000000000000",
"value": Object {
"rev": "1-49baa096ec0c89962f2cafd3ff50b80b",
},
},
Object {
"doc": Object {
"_id": "gpx/4320000000000000/3trk/000000",
"_rev": "1-4c114f3ae0073151e4082ff1d220c2a4",
"doc": Object {
"number": 0,
},
"type": "trk",
},
"id": "gpx/4320000000000000/3trk/000000",
"key": "gpx/4320000000000000/3trk/000000",
"value": Object {
"rev": "1-4c114f3ae0073151e4082ff1d220c2a4",
},
},
],
"total_rows": 4,
}
`);
});
test('returns three rows after a gpx and a track segment have been inserted.', async () => {
await putNewTrkseg();
const allDocs: any = await getFamily('gpx/');
expect(allDocs).toMatchInlineSnapshot(`
Object {
"offset": 0,
"rows": Array [
Object {
"id": "gpx/4320000000000000",
"key": "gpx/4320000000000000",
"value": Object {
"rev": "1-49baa096ec0c89962f2cafd3ff50b80b",
},
},
Object {
"id": "gpx/4320000000000000/3trk/000000",
"key": "gpx/4320000000000000/3trk/000000",
"value": Object {
"rev": "1-4c114f3ae0073151e4082ff1d220c2a4",
},
},
Object {
"id": "gpx/4320000000000000/3trk/000000/000000",
"key": "gpx/4320000000000000/3trk/000000/000000",
"value": Object {
"rev": "1-68d7de0569de570229ea9f9e1a0b13cb",
},
},
],
"total_rows": 5,
}
`);
});
test('returns four rows after a gpx and a track point have been inserted.', async () => {
await putNewTrkpt();
const allDocs: any = await getFamily('gpx/');
expect(allDocs).toMatchInlineSnapshot(`
Object {
"offset": 0,
"rows": Array [
Object {
"id": "gpx/4320000000000000",
"key": "gpx/4320000000000000",
"value": Object {
"rev": "1-49baa096ec0c89962f2cafd3ff50b80b",
},
},
Object {
"id": "gpx/4320000000000000/3trk/000000",
"key": "gpx/4320000000000000/3trk/000000",
"value": Object {
"rev": "1-4c114f3ae0073151e4082ff1d220c2a4",
},
},
Object {
"id": "gpx/4320000000000000/3trk/000000/000000",
"key": "gpx/4320000000000000/3trk/000000/000000",
"value": Object {
"rev": "1-68d7de0569de570229ea9f9e1a0b13cb",
},
},
Object {
"id": "gpx/4320000000000000/3trk/000000/000000/000000",
"key": "gpx/4320000000000000/3trk/000000/000000/000000",
"value": Object {
"rev": "1-7d917d1f3505fe0e3092161694904b53",
},
},
],
"total_rows": 6,
}
`);
});
test('returns two rows after a gpx and a waypoint have been inserted.', async () => {
await putNewWpt();
const allDocs: any = await getFamily('gpx/');
expect(allDocs).toMatchInlineSnapshot(`
Object {
"offset": 0,
"rows": Array [
Object {
"id": "gpx/4320000000000000",
"key": "gpx/4320000000000000",
"value": Object {
"rev": "1-49baa096ec0c89962f2cafd3ff50b80b",
},
},
Object {
"id": "gpx/4320000000000000/1wpt/000000",
"key": "gpx/4320000000000000/1wpt/000000",
"value": Object {
"rev": "1-c6793365fd0dd56236ab8734a41c7ae7",
},
},
],
"total_rows": 4,
}
`);
});
test('returns two rows after a gpx and a route have been inserted.', async () => {
await putNewRte();
const allDocs: any = await getFamily('gpx/');
expect(allDocs).toMatchInlineSnapshot(`
Object {
"offset": 0,
"rows": Array [
Object {
"id": "gpx/4320000000000000",
"key": "gpx/4320000000000000",
"value": Object {
"rev": "1-49baa096ec0c89962f2cafd3ff50b80b",
},
},
Object {
"id": "gpx/4320000000000000/2rte/000000",
"key": "gpx/4320000000000000/2rte/000000",
"value": Object {
"rev": "1-2ca14f512a9c83f5a239389e580befce",
},
},
],
"total_rows": 4,
}
`);
});
test('returns three rows after a gpx and a route point have been inserted.', async () => {
await putNewRtept();
const allDocs: any = await getFamily('gpx/');
expect(allDocs).toMatchInlineSnapshot(`
Object {
"offset": 0,
"rows": Array [
Object {
"id": "gpx/4320000000000000",
"key": "gpx/4320000000000000",
"value": Object {
"rev": "1-49baa096ec0c89962f2cafd3ff50b80b",
},
},
Object {
"id": "gpx/4320000000000000/2rte/000000",
"key": "gpx/4320000000000000/2rte/000000",
"value": Object {
"rev": "1-2ca14f512a9c83f5a239389e580befce",
},
},
Object {
"id": "gpx/4320000000000000/2rte/000000/000000",
"key": "gpx/4320000000000000/2rte/000000/000000",
"value": Object {
"rev": "1-0f4064d20f6bfac3888a7758851fbac5",
},
},
],
"total_rows": 5,
}
`);
});
});
describe('getDocsByType', () => {
beforeEach(async () => {
await initDb({});
globalThis.Date.now = () => 0;
});
afterEach(async () => {
await db.destroy();
db = undefined;
globalThis.Date.now = originalDateNow;
});
test('gets the rte amongst other docs', async () => {
await putNewRtept();
const rtes = await getDocsByType('rte');
expect(rtes).toMatchInlineSnapshot(`
Array [
Object {
"_id": "gpx/4320000000000000/2rte/000000",
"_rev": "1-2ca14f512a9c83f5a239389e580befce",
"doc": Object {
"number": 0,
},
"type": "rte",
},
]
`);
});
});

52
src/db/lib.ts Normal file
View File

@ -0,0 +1,52 @@
import { cloneDeep } from 'lodash';
declare global {
var db: any;
}
export const put = async (
_id: string,
type: string,
update: (doc: any) => any,
defaultDoc: any
) => {
var current;
try {
current = await db.get(_id);
} catch {
current = { _rev: undefined, doc: cloneDeep(defaultDoc) };
}
try {
db.put({ _id, _rev: current._rev, type, doc: update(current.doc) });
} catch (error: any) {
if (error.name === 'conflict') {
await put(_id, type, update, defaultDoc);
} else {
console.error(
`put(${_id}, ${JSON.stringify(
update(current.doc)
)}), error: ${JSON.stringify(error)}`
);
}
}
};
export const getFamily = async (key: string, options: any = {}) => {
return await db.allDocs({
startkey: key,
endkey: key + '\ufff0',
...options,
});
};
export const get = async (id: string) => {
await db.get(id);
};
export const putAll = async (docs: any[]) => {
return await db.bulkDocs(docs);
};
export const getDocsByType = async (type: string) => {
return (await db.find({ selector: { type: type } })).docs;
};

47
src/db/rte.test.ts Normal file
View File

@ -0,0 +1,47 @@
import { putNewRte } from './rte';
declare global {
var db: any;
var dbReady: boolean;
}
const originalDb = globalThis.db;
const originalDateNow = globalThis.Date.now;
describe('The rte module', () => {
beforeEach(() => {
globalThis.db = { put: jest.fn() };
globalThis.Date.now = () => 0;
});
afterEach(() => {
globalThis.db = originalDb;
globalThis.Date.now = originalDateNow;
});
test('db.put() a new rte when required', async () => {
putNewRte({ gpx: 4320000000000000, rte: 25 });
await expect(globalThis.db.put).toBeCalledWith({
_id: 'gpx/4320000000000000/2rte/000025',
_rev: undefined,
doc: {
cmt: undefined,
desc: undefined,
extensions: undefined,
link: undefined,
name: undefined,
number: 0,
rtept: undefined,
src: undefined,
type: undefined,
},
type: 'rte',
});
});
test('db.put() generates an id for the trk if needed', async () => {
const id = await putNewRte({ gpx: 0 });
expect(id).toEqual({ gpx: 0, rte: 0 });
});
test('db.put() generates ids for both gpx and trk if needed', async () => {
const id = await putNewRte();
expect(id).toEqual({ gpx: 4320000000000000, rte: 0 });
});
});

33
src/db/rte.ts Normal file
View File

@ -0,0 +1,33 @@
import getUri from '../lib/ids';
import { putNewGpx } from './gpx';
import { put } from './lib';
export const emptyRte: Rte = {
name: undefined,
cmt: undefined,
desc: undefined,
src: undefined,
link: undefined,
number: 0,
type: undefined,
extensions: undefined,
rtept: undefined,
};
export const putNewRte = async (id?: IdRte | IdGpx) => {
let finalId = { ...id };
if (!('rte' in finalId)) {
const gpxId = await putNewGpx(id);
finalId = { ...gpxId, rte: 0 };
}
const uri = getUri('rte', finalId);
await put(
uri,
'rte',
(rte) => {
return rte;
},
emptyRte
);
return finalId as IdRte;
};

58
src/db/rtept.test.ts Normal file
View File

@ -0,0 +1,58 @@
import { putNewRtept } from './rtept';
declare global {
var db: any;
var dbReady: boolean;
}
const originalDb = globalThis.db;
const originalDateNow = globalThis.Date.now;
describe('The rtept module', () => {
beforeEach(() => {
globalThis.db = { put: jest.fn() };
globalThis.Date.now = () => 0;
});
afterEach(() => {
globalThis.db = originalDb;
globalThis.Date.now = originalDateNow;
});
test('db.put() a new rtept when required', async () => {
putNewRtept({ gpx: 0, rte: 0, rtept: 0 });
await expect(globalThis.db.put).toBeCalledWith({
_id: 'gpx/0000000000000000/2rte/000000/000000',
_rev: undefined,
doc: {
$: { lat: 0, lon: 0 },
ageofdgpsdata: undefined,
cmt: undefined,
desc: undefined,
dgpsid: undefined,
ele: undefined,
extensions: undefined,
fix: undefined,
geoidheight: undefined,
hdop: undefined,
link: undefined,
magvar: undefined,
name: undefined,
pdop: undefined,
sat: undefined,
src: undefined,
sym: undefined,
time: undefined,
type: undefined,
vdop: undefined,
},
type: 'rtept',
});
});
test('db.put() generates an id for the rtept if needed', async () => {
const id = await putNewRtept({ gpx: 0 });
expect(id).toEqual({ gpx: 0, rte: 0, rtept: 0 });
});
test('db.put() generates ids for both gpx and rte if needed', async () => {
const id = await putNewRtept();
expect(id).toEqual({ gpx: 4320000000000000, rte: 0, rtept: 0 });
});
});

44
src/db/rtept.ts Normal file
View File

@ -0,0 +1,44 @@
import getUri from '../lib/ids';
import { put } from './lib';
import { putNewRte } from './rte';
export const emptyRtept: Wpt = {
$: { lat: 0, lon: 0 },
ele: undefined,
time: undefined,
magvar: undefined,
geoidheight: undefined,
name: undefined,
cmt: undefined,
desc: undefined,
src: undefined,
link: undefined,
sym: undefined,
type: undefined,
fix: undefined,
sat: undefined,
hdop: undefined,
vdop: undefined,
pdop: undefined,
ageofdgpsdata: undefined,
dgpsid: undefined,
extensions: undefined,
};
export const putNewRtept = async (id?: IdGpx | IdRte | IdRtept) => {
let finalId = { ...id };
if (!('rtept' in finalId)) {
const rteId = await putNewRte(id);
finalId = { ...rteId, rtept: 0 };
}
const uri = getUri('rtept', finalId);
await put(
uri,
'rtept',
(rtept) => {
return rtept;
},
emptyRtept
);
return finalId as IdRtept;
};

47
src/db/trk.test.ts Normal file
View File

@ -0,0 +1,47 @@
import { putNewTrk } from './trk';
declare global {
var db: any;
var dbReady: boolean;
}
const originalDb = globalThis.db;
const originalDateNow = globalThis.Date.now;
describe('The trk module', () => {
beforeEach(() => {
globalThis.db = { put: jest.fn() };
globalThis.Date.now = () => 0;
});
afterEach(() => {
globalThis.db = originalDb;
globalThis.Date.now = originalDateNow;
});
test('db.put() a new trk when required', async () => {
putNewTrk({ gpx: 1, trk: 2 });
await expect(globalThis.db.put).toBeCalledWith({
_id: 'gpx/0000000000000001/3trk/000002',
_rev: undefined,
doc: {
cmt: undefined,
desc: undefined,
extensions: undefined,
link: undefined,
name: undefined,
number: 0,
src: undefined,
trkseg: undefined,
type: undefined,
},
type: 'trk',
});
});
test('db.put() generates an id for the trk if needed', async () => {
const id = await putNewTrk({ gpx: 2 });
expect(id).toEqual({ gpx: 2, trk: 0});
});
test('db.put() generates ids for both gpx and trk if needed', async () => {
const id = await putNewTrk();
expect(id).toEqual({ gpx: 4320000000000000, trk: 0});
});
});

42
src/db/trk.ts Normal file
View File

@ -0,0 +1,42 @@
import getUri from '../lib/ids';
import { putNewGpx } from './gpx';
import { getFamily, put } from './lib';
export const emptyTrk: Trk = {
name: undefined,
cmt: undefined,
desc: undefined,
src: undefined,
link: undefined,
number: 0,
type: undefined,
extensions: undefined,
trkseg: undefined,
};
export const putNewTrk = async (id?: IdTrk | IdGpx) => {
let finalId = { ...id };
if (!('trk' in finalId)) {
const gpxId = await putNewGpx(id);
finalId = { ...gpxId, trk: 0 };
}
const uri = getUri('trk', finalId);
await put(
uri,
'trk',
(trk) => {
return trk;
},
emptyTrk
);
return finalId as IdTrk;
};
export const getTrk = async (params: any) => {
const { id } = params;
const docs = await getFamily(id, { include_docs: true });
console.log(`getTrk, uri: ${id} docs: ${JSON.stringify(docs)}`);
return docs.rows.filter(
(row: any) => row.doc.type === 'trk' || row.doc.type === 'trkseg'
);
};

79
src/db/trkpt.test.ts Normal file
View File

@ -0,0 +1,79 @@
import { putNewTrkpt } from './trkpt';
declare global {
var db: any;
var dbReady: boolean;
}
const originalDb = globalThis.db;
const originalDateNow = globalThis.Date.now;
describe('The trkpt module', () => {
beforeEach(() => {
globalThis.db = { put: jest.fn() };
globalThis.Date.now = () => 0;
});
afterEach(() => {
globalThis.db = originalDb;
globalThis.Date.now = originalDateNow;
});
test('db.put() a new trkpt when required', async () => {
putNewTrkpt({
gpx: 1,
trk: 2,
trkseg: 3,
trkpt: 4,
});
await expect(globalThis.db.put).toBeCalledWith({
_id: 'gpx/0000000000000001/3trk/000002/000003/000004',
_rev: undefined,
doc: {
$: { lat: 0, lon: 0 },
ageofdgpsdata: undefined,
cmt: undefined,
desc: undefined,
dgpsid: undefined,
ele: undefined,
extensions: {
'dyo:accuracy': undefined,
'dyo:batterylevel': undefined,
'dyo:course': undefined,
'dyo:speed': undefined,
'dyo:useragent': undefined,
},
fix: undefined,
geoidheight: undefined,
hdop: undefined,
link: undefined,
magvar: undefined,
name: undefined,
pdop: undefined,
sat: undefined,
src: undefined,
sym: undefined,
time: undefined,
type: undefined,
vdop: undefined,
},
type: 'trkpt',
});
});
test('db.put() generates an id for the trk if needed', async () => {
const id = await putNewTrkpt({ gpx: 5 });
expect(id).toEqual({
gpx: 5,
trk: 0,
trkseg: 0,
trkpt: 0,
});
});
test('db.put() generates ids for both gpx and trk if needed', async () => {
const id = await putNewTrkpt();
expect(id).toEqual({
gpx: 4320000000000000,
trk: 0,
trkseg: 0,
trkpt: 0,
});
});
});

50
src/db/trkpt.ts Normal file
View File

@ -0,0 +1,50 @@
import getUri from '../lib/ids';
import { put } from './lib';
import { putNewTrkseg } from './trkseg';
const emptyTrkpt: Wpt = {
$: { lat: 0, lon: 0 },
ele: undefined,
time: undefined,
magvar: undefined,
geoidheight: undefined,
name: undefined,
cmt: undefined,
desc: undefined,
src: undefined,
link: undefined,
sym: undefined,
type: undefined,
fix: undefined,
sat: undefined,
hdop: undefined,
vdop: undefined,
pdop: undefined,
ageofdgpsdata: undefined,
dgpsid: undefined,
extensions: {
'dyo:speed': undefined,
'dyo:course': undefined,
'dyo:accuracy': undefined,
'dyo:batterylevel': undefined,
'dyo:useragent': undefined,
},
};
export const putNewTrkpt = async (id?: IdTrk | IdGpx | IdTrkseg | IdTrkpt) => {
let finalId = { ...id };
if (!('trkpt' in finalId)) {
const trksegId = await putNewTrkseg(id);
finalId = { ...trksegId, trkpt: 0 };
}
const uri = getUri('trkpt', finalId);
await put(
uri,
'trkpt',
(trkpt) => {
return trkpt;
},
emptyTrkpt
);
return finalId;
};

40
src/db/trkseg.test.ts Normal file
View File

@ -0,0 +1,40 @@
import { putNewTrkseg } from './trkseg';
declare global {
var db: any;
var dbReady: boolean;
}
const originalDb = globalThis.db;
const originalDateNow = globalThis.Date.now;
describe('The trkseg module', () => {
beforeEach(() => {
globalThis.db = { put: jest.fn() };
globalThis.Date.now = () => 0;
});
afterEach(() => {
globalThis.db = originalDb;
globalThis.Date.now = originalDateNow;
});
test('db.put() a new trk when required', async () => {
putNewTrkseg({ gpx: 1234567890123456, trk: 123456, trkseg: 5 });
await expect(globalThis.db.put).toBeCalledWith({
_id: 'gpx/1234567890123456/3trk/123456/000005',
_rev: undefined,
doc: {
trkpt: undefined,
extensions: undefined,
},
type: 'trkseg',
});
});
test('db.put() generates an id for the trk if needed', async () => {
const id = await putNewTrkseg({ gpx: 1 });
expect(id).toEqual({ gpx: 1, trk: 0, trkseg: 0 });
});
test('db.put() generates ids for both gpx and trk if needed', async () => {
const id = await putNewTrkseg();
expect(id).toEqual({ gpx: 4320000000000000, trk: 0, trkseg: 0 });
});
});

33
src/db/trkseg.ts Normal file
View File

@ -0,0 +1,33 @@
import getUri from '../lib/ids';
import { getFamily, put } from './lib';
import { putNewTrk } from './trk';
const emptyTrkseg: Trkseg = {
trkpt: undefined,
extensions: undefined,
};
export const putNewTrkseg = async (id?: IdTrk | IdGpx | IdTrkseg) => {
let finalId = { ...id };
if (!('trkseg' in finalId)) {
const trkId = await putNewTrk(id);
finalId = { ...trkId, trkseg: 0 };
}
const uri = getUri('trkseg', finalId);
await put(
uri,
'trkseg',
(trkseg) => {
return trkseg;
},
emptyTrkseg
);
return finalId as IdTrkseg;
};
export const getTrkseg = async (params: any) => {
const { id } = params;
const docs = await getFamily(id, { include_docs: true });
console.log(`getTrkseg, uri: ${id} docs: ${JSON.stringify(docs)}`);
return docs.rows;
};

119
src/db/types.d.ts vendored Normal file
View File

@ -0,0 +1,119 @@
interface Gpx {
$: Gpx_;
metadata?: Metadata;
wpt?: Wpt[];
rte?: Rte[];
trk?: Trk[];
extensions?: Extensions;
}
interface Gpx_ {
version: '1.1';
creator: string;
xmlns: 'http://www.topografix.com/GPX/1/1';
'xmlns:xsi'?: 'http://www.w3.org/2001/XMLSchema-instance';
'xsi:schemaLocation'?: string;
'xmlns:gpxx'?: string;
'xmlns:wptx1'?: string;
'xmlns:gpxtpx'?: string;
'xmlns:dyo'?: 'http://xmlns.dyomedea.com/';
}
interface Metadata {
name?: string;
desc?: string;
author?: string;
copyright?: string;
link?: Link[];
time?: string;
keywords?: string;
bounds?: Bounds;
extensions?: Extensions;
}
interface Bounds {
$: Bounds_;
}
interface Bounds_ {
minlat: number;
minlon: number;
maxlat: number;
maxlon: number;
}
interface Extensions {
'dyo:speed'?: number;
'dyo:course'?: number;
'dyo:accuracy'?: number;
'dyo:batterylevel'?: number;
'dyo:useragent'?: string;
}
interface Trk {
name?: string;
cmt?: string;
desc?: string;
src?: string;
link?: Link[];
number?: number;
type?: string;
extensions?: Extensions;
trkseg?: Trkseg[];
}
interface Link {
$: Link_;
text?: string;
type?: string;
}
interface Link_ {
href: string;
}
interface Trkseg {
trkpt?: Wpt[];
extensions?: Extensions;
}
interface Wpt {
$: Wpt_;
ele?: number;
time?: string;
magvar?: number;
geoidheight?: number;
name?: string;
cmt?: string;
desc?: string;
src?: string;
link?: Link;
sym?: string;
type?: string;
fix?: string;
sat?: number;
hdop?: number;
vdop?: number;
pdop?: number;
ageofdgpsdata?: number;
dgpsid?: number;
extensions?: Extensions;
}
interface Wpt_ {
lat: number;
lon: number;
}
interface Rte {
name?: string;
cmt?: string;
desc?: string;
src?: string;
link?: Link[];
number?: number;
type?: string;
extensions?: Extensions;
rtept?: Wpt[];
}

58
src/db/wpt.test.ts Normal file
View File

@ -0,0 +1,58 @@
import { putNewWpt } from './wpt';
declare global {
var db: any;
var dbReady: boolean;
}
const originalDb = globalThis.db;
const originalDateNow = globalThis.Date.now;
describe('The wpt module', () => {
beforeEach(() => {
globalThis.db = { put: jest.fn() };
globalThis.Date.now = () => 0;
});
afterEach(() => {
globalThis.db = originalDb;
globalThis.Date.now = originalDateNow;
});
test('db.put() a new wpt when required', async () => {
putNewWpt({ gpx: 1, wpt: 2 });
await expect(globalThis.db.put).toBeCalledWith({
_id: 'gpx/0000000000000001/1wpt/000002',
_rev: undefined,
doc: {
$: { lat: 0, lon: 0 },
ageofdgpsdata: undefined,
cmt: undefined,
desc: undefined,
dgpsid: undefined,
ele: undefined,
extensions: undefined,
fix: undefined,
geoidheight: undefined,
hdop: undefined,
link: undefined,
magvar: undefined,
name: undefined,
pdop: undefined,
sat: undefined,
src: undefined,
sym: undefined,
time: undefined,
type: undefined,
vdop: undefined,
},
type: 'wpt',
});
});
test('db.put() generates an id for the wpt if needed', async () => {
const id = await putNewWpt({ gpx: 1 });
expect(id).toEqual({ gpx: 1, wpt: 0 });
});
test('db.put() generates ids for both gpx and trk if needed', async () => {
const id = await putNewWpt();
expect(id).toEqual({ gpx: 4320000000000000, wpt: 0 });
});
});

44
src/db/wpt.ts Normal file
View File

@ -0,0 +1,44 @@
import getUri from '../lib/ids';
import { putNewGpx } from './gpx';
import { put } from './lib';
export const emptyWpt: Wpt = {
$: { lat: 0, lon: 0 },
ele: undefined,
time: undefined,
magvar: undefined,
geoidheight: undefined,
name: undefined,
cmt: undefined,
desc: undefined,
src: undefined,
link: undefined,
sym: undefined,
type: undefined,
fix: undefined,
sat: undefined,
hdop: undefined,
vdop: undefined,
pdop: undefined,
ageofdgpsdata: undefined,
dgpsid: undefined,
extensions: undefined,
};
export const putNewWpt = async (id?: IdGpx | IdWpt) => {
let finalId = { ...id };
if (!('wpt' in finalId)) {
const gpxId = await putNewGpx(id);
finalId = { ...gpxId, wpt: 0 };
}
const uri = getUri('wpt', finalId);
await put(
uri,
'wpt',
(wpt) => {
return wpt;
},
emptyWpt
);
return finalId as IdWpt;
};

View File

@ -2,7 +2,39 @@
import { render } from 'solid-js/web';
import { Router, hashIntegration } from '@solidjs/router';
import App from './App';
//source={hashIntegration()}
import dispatch from './workers/dispatcher-main';
/* // See https://stackoverflow.com/questions/71538643/property-wakelock-does-not-exist-on-type-navigator
const requestWakeLock = async () => {
const anyNav: any = navigator;
if ('wakeLock' in navigator) {
try {
const wakeLock = await anyNav['wakeLock'].request('screen');
} catch (err: any) {
// The wake lock request fails - usually system-related, such as low battery.
console.log(`Wake lock request failed: ${err.name}, ${err.message}`);
}
} else {
console.log('No wake lock support here...');
}
};
const handleVisibilityChange = () => {
if (document.hidden) {
console.log('Application hidden');
} else {
console.log('Application visible');
requestWakeLock();
}
};
// See https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API
document.addEventListener('visibilitychange', handleVisibilityChange, false);
requestWakeLock(); */
// Init the database
dispatch({ action: 'initDb' });
render(
() => (
<Router>

61
src/lib/cache.ts Normal file
View File

@ -0,0 +1,61 @@
/**
* @hidden
*/
export const thisIsAModule = true;
/**
*
*/
declare global {
var _allCaches: any;
}
globalThis._allCaches = new Map();
const cache = {
set: (params: any) => {
const { cacheId, key, value } = params;
if (!_allCaches.has(cacheId)) {
_allCaches.set(cacheId, new Map());
}
const k = _allCaches.get(cacheId);
k.set(key, value);
},
get: (params: any) => {
const { cacheId, key } = params;
if (!_allCaches.has(cacheId)) {
return null;
}
const k = _allCaches.get(cacheId);
if (!k.has(key)) {
return null;
}
const value = k.get(key);
k.delete(key);
k.set(key, value);
return value;
},
delete: (params: any) => {
const { cacheId, key } = params;
if (!_allCaches.has(cacheId)) {
return null;
}
const k = _allCaches.get(cacheId);
if (!k.has(key)) {
return null;
}
const value = k.get(key);
k.delete(key);
return value;
},
map: (params: any) => {
const { cacheId } = params;
if (!_allCaches.has(cacheId)) {
return null;
}
return _allCaches.get(cacheId);
},
};
export default cache;

133
src/lib/docuri/index.js Normal file
View File

@ -0,0 +1,133 @@
/*
* DocURI: Rich document ids for CouchDB.
*
* Copyright (c) 2014 null2 GmbH Berlin
* Licensed under the MIT license.
*/
// type/id/subtype/index/version
// var docuri = (module.exports = exports = {});
// Cached regular expressions for matching named param parts and splatted parts
// of route strings.
// http://backbonejs.org/docs/backbone.html#section-158
var optionalParam = /\((.*?)\)/g;
var namedParam = /(\(\?)?:\w+/g;
var splatParam = /\*\w+/g;
var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g;
var paramKeys = /[*:]\w+/g;
// Convert a route string into a regular expression,
// with named regular expressions for named arguments.
// http://backbonejs.org/docs/backbone.html#section-165
function routeToRegExp(src) {
var keys = [],
match;
while ((match = paramKeys.exec(src)) !== null) {
keys.push(match[0]);
}
var route = src
.replace(escapeRegExp, '\\$&')
.replace(optionalParam, '(?:$1)?')
.replace(namedParam, function (match, optional) {
return optional ? match : '([^/?]+)';
})
.replace(splatParam, '([^?]*?)');
keys = keys.reduce(function (memo, key) {
var value = '\\' + key;
memo[key] = new RegExp(value + '(\\/|\\)|\\(|$)');
return memo;
}, {});
return {
src: src,
exp: new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$'),
keys: keys,
};
}
// Given a route and a DocURI return an object of extracted parameters.
// Unmatched DocURIs will be treated as false.
// http://backbonejs.org/docs/backbone.html#section-166
function extractParameters(route, fragment, coding) {
var params = route.exp.exec(fragment);
if (!params) {
return false;
}
params = params.slice(1);
return Object.keys(route.keys).reduce(function (memo, key, i) {
var param = params[i];
if (param) {
var k = key.substr(1);
var decoder = (coding[k] ?? { decoder: decodeURIComponent }).decoder;
if (key[0] === '*') {
param = param.split('/').map(decoder);
} else {
param = decoder(param);
}
memo[key.substr(1)] = param;
}
return memo;
}, {});
}
// Insert named parameters from object.
function insertParameters(route, obj, coding) {
var str = route.src;
Object.keys(route.keys).forEach(function (key) {
var k = key.substr(1);
var value = obj[k] !== undefined ? obj[k] : '';
var encoder = (coding[k] ?? { encoder: encodeURIComponent }).encoder;
if (Array.isArray(value)) {
value = value.map(encoder).join('/');
} else {
value = encoder(value);
}
str = str.replace(route.keys[key], value + '$1');
});
// massage optional parameter
return str.replace(/\(\/\)/g, '').replace(/[)(]/g, '');
}
export const route = (route, coding = {}) => {
route = routeToRegExp(route);
return function (source, target) {
source = source || {};
if (target) {
source = extractParameters(route, source, coding);
Object.keys(target).forEach(function (key) {
source[key] = target[key];
});
}
if (typeof source === 'object') {
return insertParameters(route, source, coding);
}
if (typeof source === 'string') {
return extractParameters(route, source, coding);
}
};
};
export default route;

35
src/lib/geo.ts Normal file
View File

@ -0,0 +1,35 @@
import { Rectangle } from '../components/map/types';
// cf https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#ECMAScript_(JavaScript/ActionScript,_etc.)
export const lon2tile = (lon: number, zoom: number) => {
return ((Number(lon) + 180) / 360) * Math.pow(2, zoom);
};
export const lat2tile = (lat: number, zoom: number) => {
return (
((1 -
Math.log(
Math.tan((Number(lat) * Math.PI) / 180) +
1 / Math.cos((Number(lat) * Math.PI) / 180)
) /
Math.PI) /
2) *
Math.pow(2, zoom)
);
};
export function tile2long(x: number, z: number) {
return (x / Math.pow(2, z)) * 360 - 180;
}
export function tile2lat(y: number, z: number) {
var n = Math.PI - (2 * Math.PI * y) / Math.pow(2, z);
return (180 / Math.PI) * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n)));
}
export const rectanglesIntersect = (r1: Rectangle, r2: Rectangle) =>
!(
r2.topLeft.x > r1.bottomRight.x ||
r2.bottomRight.x < r1.topLeft.x ||
r2.topLeft.y > r1.bottomRight.y ||
r2.bottomRight.y < r1.topLeft.y
);

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Zheng-Xiang Ke
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,91 @@
# gpx-parser-builder
A simple gpx parser and builder between GPX string and JavaScript object. It is dependent on [isomorphic-xml2js](https://github.com/RikkiGibson/isomorphic-xml2js).
[![npm](https://img.shields.io/npm/dt/gpx-parser-builder.svg)](https://www.npmjs.com/package/gpx-parser-builder)
[![GitHub stars](https://img.shields.io/github/stars/kf99916/gpx-parser-builder.svg)](https://github.com/kf99916/gpx-parser-builder/stargazers)
[![GitHub forks](https://img.shields.io/github/forks/kf99916/gpx-parser-builder.svg)](https://github.com/kf99916/gpx-parser-builder/network)
[![npm](https://img.shields.io/npm/v/gpx-parser-builder.svg)](https://www.npmjs.com/package/gpx-parser-builder)
[![GitHub license](https://img.shields.io/github/license/kf99916/gpx-parser-builder.svg)](https://github.com/kf99916/gpx-parser-builder/blob/master/LICENSE)
## Requirements
gpx-parser-builder is written with ECMAScript 6. You can leverage [Babel](https://babeljs.io/) and [Webpack](https://webpack.js.org/) to make all browsers available.
## Installation
```bash
npm install gpx-parser-builder --save
```
## Version
v1.0.0+ is a breaking change for v0.2.2-. v1.0.0+ fully supports gpx files including waypoints, routes, and tracks. Every gpx type is 1-1 corresponding to a JavaScript class.
## Usage
```javascript
import GPX from 'gpx-parser-builder';
// Parse gpx
const gpx = GPX.parse('GPX_STRING');
window.console.dir(gpx.metadata);
window.console.dir(gpx.wpt);
window.console.dir(gpx.trk);
// Build gpx
window.console.log(gpx.toString());
```
Get more details about usage with the unit tests.
### GPX
The GPX JavaScript object.
`constructor(object)`
```javascript
const gpx = new Gpx({$:{...}, metadat: {...}, wpt:[{...},{...}]}, trk: {...}, rte: {...})
```
#### Member Variables
`$` the attributes for the gpx element. Default value:
```javascript
{
'version': '1.1',
'creator': 'gpx-parser-builder',
'xmlns': 'http://www.topografix.com/GPX/1/1',
'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
'xsi:schemaLocation': 'http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd'
}
```
`metadata` the metadata for the gpx.
`wpt` array of waypoints. It is corresponded to `<wpt>`. The type of all elements in `wpt` is `Waypoint`;
`rte` array of routes. It is corresponded to `<rte>`. The type of all elements in `rte` is `Route`;
`trk` array of tracks. It is corresponded to `<trk>`. The type of all elements in `trk` is `Track`;
#### Static Methods
`parse(gpxString)` parse gpx string to Gpx object. return `null` if parsing failed.
#### Member Methods
`toString(options)` GPX object to gpx string. The options is for [isomorphic-xml2js](https://github.com/RikkiGibson/isomorphic-xml2js).
## Save as GPX file in the frontend
You can leverage [StreamSaver.js](https://github.com/jimmywarting/StreamSaver.js) or [FileSaver.js](https://github.com/eligrey/FileSaver.js) to save as GPX file. ⚠Not all borwsers support the above file techniques. ⚠️️️
## Author
Zheng-Xiang Ke, kf99916@gmail.com
## License
gpx-parser-builder is available under the MIT license. See the LICENSE file for more info.

View File

@ -0,0 +1,36 @@
{
"name": "gpx-parser-builder",
"version": "1.0.2",
"description": "A simple gpx parser and builder between GPX string and JavaScript object",
"main": "./src/gpx.js",
"scripts": {
"test": "mocha --require @babel/register test/**/*.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/kf99916/gpx-parser-builder.git"
},
"keywords": [
"gpx",
"parser",
"builder"
],
"author": "Zheng-Xiang Ke",
"license": "MIT",
"bugs": {
"url": "https://github.com/kf99916/gpx-parser-builder/issues"
},
"homepage": "https://github.com/kf99916/gpx-parser-builder",
"files": [
"src"
],
"devDependencies": {
"@babel/core": "~7.7",
"@babel/preset-env": "~7.7",
"@babel/register": "~7.7",
"mocha": "~6.2"
},
"dependencies": {
"isomorphic-xml2js": "~0.1"
}
}

View File

@ -0,0 +1,8 @@
export default class Bounds {
constructor(object) {
this.minlat = object.minlat;
this.minlon = object.minlon;
this.maxlat = object.maxlat;
this.maxlon = object.maxlon;
}
}

View File

@ -0,0 +1,7 @@
export default class Copyright {
constructor(object) {
this.author = object.author;
this.year = object.year;
this.license = object.license;
}
}

View File

@ -0,0 +1,13 @@
declare module 'gpx-parser-builder' {
class GPX {
static parse(gpxString: any): any;
constructor(object: any);
$: any;
extensions: any;
metadata: any;
wpt: any;
rte: any;
trk: any;
toString(options: any): string;
}
}

View File

@ -0,0 +1,79 @@
import * as xml2js from 'isomorphic-xml2js';
import Metadata from './metadata';
import Waypoint from './waypoint';
import Route from './route';
import Track from './track';
import {removeEmpty, allDatesToISOString} from './utils';
const defaultAttributes = {
version: '1.1',
creator: 'gpx-parser-builder',
xmlns: 'http://www.topografix.com/GPX/1/1',
'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
'xsi:schemaLocation':
'http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd'
}
export default class GPX {
constructor(object) {
this.$ = Object.assign({}, defaultAttributes, object.$ || object.attributes || {});
this.extensions = object.extensions;
if (object.metadata) {
this.metadata = new Metadata(object.metadata);
}
if (object.wpt) {
if (!Array.isArray(object.wpt)) {
object.wpt = [object.wpt];
}
this.wpt = object.wpt.map(wpt => new Waypoint(wpt));
}
if (object.rte) {
if (!Array.isArray(object.rte)) {
object.rte = [object.rte];
}
this.rte = object.rte.map(rte => new Route(rte));
}
if (object.trk) {
if (!Array.isArray(object.trk)) {
object.trk = [object.trk];
}
this.trk = object.trk.map(trk => new Track(trk));
}
removeEmpty(this);
}
static parse(gpxString) {
let gpx;
xml2js.parseString(gpxString, {
explicitArray: false
}, (err, xml) => {
if (err) {
return;
}
if (!xml.gpx) {
return;
}
gpx = new GPX({
attributes: xml.gpx.$,
metadata: xml.gpx.metadata,
wpt: xml.gpx.wpt,
rte: xml.gpx.rte,
trk: xml.gpx.trk
});
});
return gpx;
}
toString(options) {
options = options || {};
options.rootName = 'gpx';
const builder = new xml2js.Builder(options), gpx = new GPX(this);
allDatesToISOString(gpx);
return builder.buildObject(gpx);
}
}

View File

@ -0,0 +1,8 @@
export default class Link {
constructor(object) {
this.$ = {};
this.$.href = object.$.href || object.href;
this.text = object.text;
this.type = object.type;
}
}

View File

@ -0,0 +1,29 @@
import Copyright from './copyright';
import Link from './link';
import Person from './person';
import Bounds from './bounds';
export default class Metadata {
constructor(object) {
this.name = object.name;
this.desc = object.desc;
this.time = object.time ? new Date(object.time) : new Date();
this.keywords = object.keywords;
this.extensions = object.extensions;
if (object.author) {
this.author = new Person(object.author);
}
if (object.link) {
if (!Array.isArray(object.link)) {
object.link = [object.link];
}
this.link = object.link.map(l => new Link(l));
}
if (object.bounds) {
this.bounds = new Bounds(object.bounds);
}
if (object.copyright) {
this.copyright = new Copyright(object.copyright);
}
}
}

View File

@ -0,0 +1,11 @@
import Link from './link';
export default class Person {
constructor(object) {
this.name = object.name;
this.email = object.email;
if (object.link) {
this.link = new Link(object.link);
}
}
}

View File

@ -0,0 +1,27 @@
import Waypoint from './waypoint';
import Link from './link';
export default class Route {
constructor(object) {
this.name = object.name;
this.cmt = object.cmt;
this.desc = object.desc;
this.src = object.src;
this.number = object.number;
this.type = object.type;
this.extensions = object.extensions;
if (object.link) {
if (!Array.isArray(object.link)) {
this.link = [object.link];
}
this.link = object.link.map(l => new Link(l));
}
if (object.rtept) {
if (!Array.isArray(object.rtept)) {
this.rtept = [object.rtept];
}
this.rtept = object.rtept.map(pt => new Waypoint(pt));
}
}
}

View File

@ -0,0 +1,13 @@
import Waypoint from './waypoint';
export default class TrackSegment {
constructor(object) {
if (object.trkpt) {
if (!Array.isArray(object.trkpt)) {
object.trkpt = [object.trkpt];
}
this.trkpt = object.trkpt.map(pt => new Waypoint(pt));
}
this.extensions = object.extensions;
}
}

View File

@ -0,0 +1,26 @@
import TrackSegment from './track-segment';
import Link from './link';
export default class Track {
constructor(object) {
this.name = object.name;
this.cmt = object.cmt;
this.desc = object.desc;
this.src = object.src;
this.number = object.number;
this.type = object.type;
this.extensions = object.extensions;
if (object.link) {
if (!Array.isArray(object.link)) {
object.link = [object.link];
}
this.link = object.link.map(l => new Link(l));
}
if (object.trkseg) {
if (!Array.isArray(object.trkseg)) {
object.trkseg = [object.trkseg];
}
this.trkseg = object.trkseg.map(seg => new TrackSegment(seg));
}
}
}

View File

@ -0,0 +1,23 @@
function removeEmpty(obj) {
Object.entries(obj).forEach(([key, val]) => {
if (val && val instanceof Object) {
removeEmpty(val);
} else if (val == null) {
delete obj[key];
}
});
}
function allDatesToISOString(obj) {
Object.entries(obj).forEach(([key, val]) => {
if (val) {
if (val instanceof Date) {
obj[key] = val.toISOString().split('.')[0] + 'Z';
} else if (val instanceof Object) {
allDatesToISOString(val);
}
}
});
}
export { removeEmpty, allDatesToISOString };

View File

@ -0,0 +1,32 @@
import Link from './link';
export default class Waypoint {
constructor(object) {
this.$ = {};
this.$.lat = object.$.lat === 0 || object.lat === 0 ? 0 : object.$.lat || object.lat || -1;
this.$.lon = object.$.lon === 0 || object.lon === 0 ? 0 : object.$.lon || object.lon || -1;
this.ele = object.ele;
this.time = object.time ? new Date(object.time) : new Date();
this.magvar = object.magvar;
this.geoidheight = object.geoidheight;
this.name = object.name;
this.cmt = object.cmt;
this.desc = object.desc;
this.src = object.src;
this.sym = object.sym;
this.type = object.type;
this.sat = object.sat;
this.hdop = object.hdop;
this.vdop = object.vdop;
this.pdop = object.pdop;
this.ageofdgpsdata = object.ageofdgpsdata;
this.dgpsid = object.dgpsid;
this.extensions = object.extensions;
if (object.link) {
if (!Array.isArray(object.link)) {
object.link = [object.link];
}
this.link = object.link.map(l => new Link(l));
}
}
}

33
src/lib/gpx.test.ts Normal file
View File

@ -0,0 +1,33 @@
import { assert } from 'console';
import { findStartTime } from './gpx';
describe('findStartTime', () => {
test('to be undefined for a string', () => {
const start = findStartTime('');
expect(start).toBeUndefined();
});
test('to be undefined for an object without time key', () => {
const start = findStartTime({ foo: 'foo', bar: 'bar' });
expect(start).toBeUndefined();
});
test('to be the time value for an object with a time key', () => {
const start = findStartTime({ foo: 'foo', time: 'bar' });
expect(start).toEqual('bar');
});
test('to be the lowest time value for an object with several time keys', () => {
const start = findStartTime({
foo: { time: 'foo' },
time: 'bar',
bar: { time: 'a' },
});
expect(start).toEqual('a');
});
test('to be the lowest time value for an array with several objects with time keys', () => {
const start = findStartTime({
foos: [{ time: 'foo' }, { time: '0' }],
time: 'bar',
bar: { time: 'a' },
});
expect(start).toEqual('0');
});
});

19
src/lib/gpx.ts Normal file
View File

@ -0,0 +1,19 @@
const min = (s1?: string, s2?: string) => {
return s1! < s2! ? s1 : s2;
};
export const findStartTime = (x: any, startTime?: string) => {
if (typeof x === 'object') {
let newStartTime = startTime;
for (const key in x) {
if (key === 'time') {
newStartTime = min(newStartTime, x[key]);
} else {
newStartTime = findStartTime(x[key], newStartTime);
}
}
return newStartTime;
}
else return startTime;
};

120
src/lib/ids.test.ts Normal file
View File

@ -0,0 +1,120 @@
import { route } from './docuri';
import uri from './ids';
describe('Checking some DocURI features', () => {
test(', basic route', () => {
const gpx = route('gpx/:id');
expect(gpx({ id: 10 })).toBe('gpx/10');
});
test(', basic route (vice-versa', () => {
const gpx = route('gpx/:id');
expect(gpx('gpx/10')).toMatchObject({ id: '10' });
});
});
describe('Checking a multilevel route', () => {
test(', using the two levels', () => {
const gpx = route('gpx/:gpx/3trk/:trk');
expect(gpx({ gpx: 10, trk: 0 })).toBe('gpx/10/3trk/0');
});
test(', using the two levels (vive-versa)', () => {
const gpx = route('gpx/:gpx/3trk/:trk');
expect(gpx('gpx/10/3trk/0')).toMatchObject({ gpx: '10', trk: '0' });
});
});
describe('Checking a multilevel route with optional part', () => {
test(', using the two levels', () => {
const gpx = route('gpx/:gpx(/3trk/:trk)');
expect(gpx({ gpx: 10, trk: 0 })).toBe('gpx/10/3trk/0');
});
test(', using the two levels (vive-versa)', () => {
const gpx = route('gpx/:gpx(/3trk/:trk)');
expect(gpx('gpx/10/3trk/0')).toMatchObject({ gpx: '10', trk: '0' });
});
test(', using only one level', () => {
const gpx = route('gpx/:gpx(/3trk/:trk)');
expect(gpx({ gpx: 10 })).toBe('gpx/10/3trk/'); //Unfortunately !
});
test(', using only one level (vive-versa)', () => {
const gpx = route('gpx/:gpx(/3trk/:trk)');
expect(gpx('gpx/10')).toMatchObject({ gpx: '10' });
});
});
describe('Checking gpx ids', () => {
const id = {
gpx: 1234567890123456,
};
const key = 'gpx/1234567890123456';
test(', vice', () => {
const gpx = uri('gpx', id);
expect(gpx).toBe(key);
});
test(', and versa', () => {
const gpx = uri('gpx', key);
expect(gpx).toMatchObject(id);
});
});
describe('Checking trk ids', () => {
const id = {
gpx: 1234567890123456,
trk: 123456,
};
const key = 'gpx/1234567890123456/3trk/123456';
test(', vice', () => {
const rte = uri('trk', id);
expect(rte).toBe(key);
});
test(', and versa', () => {
const rte = uri('trk', key);
expect(rte).toMatchObject(id);
});
});
describe('Checking trkseg ids', () => {
const id = {
gpx: 111,
trk: 0,
trkseg: 3,
};
const key = 'gpx/0000000000000111/3trk/000000/000003';
test(', vice', () => {
const rte = uri('trkseg', id);
expect(rte).toBe(key);
});
test(', and versa', () => {
const rte = uri('trkseg', key);
expect(rte).toMatchObject(id);
});
});
describe('Checking trkpt ids', () => {
const id = {
gpx: 25,
trk: 8,
trkseg: 0,
trkpt: 155,
};
const key = 'gpx/0000000000000025/3trk/000008/000000/000155';
test(', vice', () => {
const rte = uri('trkpt', id);
expect(rte).toBe(key);
});
test(', and versa', () => {
const rte = uri('trkpt', key);
expect(rte).toMatchObject(id);
});
});
describe('Checking settings id', () => {
test(', vice', () => {
const rte = uri('settings', {});
expect(rte).toBe('settings');
});
test(', and versa', () => {
const rte = uri('settings', 'settings');
expect(rte).toMatchObject({});
});
});

51
src/lib/ids.ts Normal file
View File

@ -0,0 +1,51 @@
import route from './docuri';
const integerType = (n: number) => {
return {
encoder: (v: number) => v.toString().padStart(n, '0'),
decoder: parseInt,
};
};
const bigIntType = (n: number) => {
return {
encoder: (v: number) => v.toString().padStart(n, '0'),
decoder: BigInt,
};
};
const coding = {
gpx: integerType(16),
wpt: integerType(6),
rte: integerType(6),
rtept: integerType(6),
trk: integerType(6),
trkseg: integerType(6),
trkpt: integerType(6),
};
const routes = {
dbdef: route('dbdef', coding),
settings: route('settings', coding),
gpx: route('gpx/:gpx', coding),
wpt: route('gpx/:gpx/1wpt/:wpt', coding),
rte: route('gpx/:gpx/2rte/:rte', coding),
rtept: route('gpx/:gpx/2rte/:rte/:rtept', coding),
trk: route('gpx/:gpx/3trk/:trk', coding),
trkseg: route('gpx/:gpx/3trk/:trk/:trkseg', coding),
trkpt: route('gpx/:gpx/3trk/:trk/:trkseg/:trkpt', coding),
extensions: route('gpx/:gpx/4extensions', coding),
};
type RouteKey = keyof typeof routes;
const uri = (type: RouteKey, param: any) => {
return routes[type](param);
};
export default uri;
const minDate = -8640000000000000;
const halfMinDate = minDate / 2;
export const intToGpxId = (i: number) => Math.round(i / 2) - halfMinDate;

34
src/lib/types.d.ts vendored Normal file
View File

@ -0,0 +1,34 @@
interface IdGpx {
gpx: number;
}
interface IdTrk {
gpx: number;
trk: number;
}
interface IdTrkseg {
gpx: number;
trk: number;
trkseg: number;
}
interface IdTrkpt {
gpx: number;
trk: number;
trkseg: number;
trkpt: number;
}
interface IdWpt {
gpx: number;
wpt: number;
}
interface IdRte {
gpx: number;
rte: number;
}
interface IdRtept {
gpx: number;
rte: number;
rtept: number;
}

1
src/missing-typedefs/docuri.d.ts vendored Normal file
View File

@ -0,0 +1 @@
declare module 'docuri';

View File

@ -0,0 +1 @@
declare module 'gpx-parser-builder';

View File

@ -0,0 +1,37 @@
import dispatch, { init, worker } from './dispatcher-main';
jest.mock('./get-worker', () => ({
getWorker: () => ({
port: {
postMessage: jest.fn(),
},
}),
}));
describe('The dispatcher-main', () => {
beforeEach(() => {
init();
});
test('should create a new shared web worker', () => {
expect(worker).toBeDefined();
});
test('should create a onmessage function', () => {
expect(worker.port.onmessage).toBeDefined();
});
test('should return a promise if no callback is provided', () => {
expect(dispatch('ping')).toBeInstanceOf(Promise);
});
test('should forward the message', () => {
dispatch('ping');
expect(worker.port.postMessage).toBeCalledWith({ id: 0, payload: 'ping' });
});
test('should return the response', () => {
var response;
const callback = (error, success) => {
response = success;
};
dispatch('ping', callback);
worker.port.onmessage({ data: { id: 0, payload: 'pong' } });
expect(response).toEqual('pong');
});
});

View File

@ -0,0 +1,53 @@
import { getWorker } from './get-worker';
declare global {
var dispatcherQueue: { index: number; queue: Map<number, any> };
}
export var worker: any;
export const init = () => {
console.log({ caller: 'dispatcher-main / init' });
globalThis.dispatcherQueue = { index: 0, queue: new Map() };
console.log({ caller: 'dispatcher-main / init', globalThis });
worker = getWorker();
console.log({ caller: 'dispatcher-main / init', worker });
worker.onmessage = (event: any) => {
const { id, payload } = event.data;
dispatcherQueue.queue.get(id)(null, payload);
dispatcherQueue.queue.delete(id);
};
};
const dispatch = (
payload: any,
callBack?: (error: any, result: any) => void
) => {
if (worker === undefined) {
init();
}
if (callBack === undefined) {
/** If a callback function is not provided, return a promise */
return new Promise((resolve, reject) => {
dispatch(payload, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
/** Otherwise, use the callback function */
dispatcherQueue.queue.set(dispatcherQueue.index, callBack);
const message = {
id: dispatcherQueue.index++,
payload: payload,
};
worker.postMessage(message);
console.log({ caller: 'dispatcher-main / dispatch', message });
};
export default dispatch;

View File

@ -0,0 +1,33 @@
import worker from './dispatcher-worker';
jest.mock('../db', () => ({
initDb: () => 'called initDb',
}));
describe('The dispatcher-worker ', () => {
let port;
beforeEach(() => {
port = {
postMessage: jest.fn(),
};
worker.onconnect({ ports: [port] });
});
test('creates a onmessage function', () => {
expect(port.onmessage).toBeDefined();
expect(port.postMessage).not.toBeCalled();
});
test('receives a ping and sends back an unknownAction', async () => {
await port.onmessage({ data: { id: 5, payload: { action: 'ping' } } });
expect(port.postMessage).toBeCalledWith({
id: 5,
payload: 'unknownAction',
});
});
test('calls initDb when required', async () => {
await port.onmessage({ data: { id: 5, payload: { action: 'initDb' } } });
expect(port.postMessage).toBeCalledWith({
id: 5,
payload: 'called initDb',
});
});
});

View File

@ -0,0 +1,42 @@
/// <reference lib="webworker" />
import { initDb } from '../db';
import {
putNewGpx,
existsGpx,
pruneAndSaveImportedGpx,
getGpxesForViewport,
getGpx,
} from '../db/gpx';
import { getTrk, putNewTrk } from '../db/trk';
import { getTrkseg } from '../db/trkseg';
const self = globalThis as unknown as WorkerGlobalScope;
const actions = {
initDb,
putNewGpx,
putNewTrk,
existsGpx,
pruneAndSaveImportedGpx,
getGpxesForViewport,
getGpx,
getTrk,
getTrkseg,
};
console.log({ caller: 'dispatcher-worker', actions });
onmessage = async function (e) {
console.log({ caller: 'dispatcher-worker / onmessage', e });
const { id, payload } = e.data;
console.log(`payload.action in actions: ${payload.action in actions}`);
var returnValue: any = 'unknownAction';
if (payload.action in actions) {
returnValue = await actions[<keyof typeof actions>payload.action](
payload.params
);
}
postMessage({ id: id, payload: returnValue });
};
//export default self;

View File

@ -0,0 +1,8 @@
export const getWorker = () => {
console.log({ caller: 'getWorker' });
return new Worker(new URL('./dispatcher-worker', import.meta.url), {
type: 'module',
});
};
export default getWorker;

View File

@ -4,7 +4,14 @@
import { defineConfig } from 'vite';
import solidPlugin from 'vite-plugin-solid';
export default defineConfig({
// yarn add --dev @esbuild-plugins/node-globals-polyfill
import { NodeGlobalsPolyfillPlugin } from '@esbuild-plugins/node-globals-polyfill';
// yarn add --dev @esbuild-plugins/node-modules-polyfill
import { NodeModulesPolyfillPlugin } from '@esbuild-plugins/node-modules-polyfill';
// You don't need to add this to deps, it's included by @esbuild-plugins/node-modules-polyfill
import rollupNodePolyFill from 'rollup-plugin-node-polyfills';
export default defineConfig(({ command, mode, ssrBuild }) => ({
plugins: [solidPlugin()],
test: {
environment: 'jsdom',
@ -29,5 +36,49 @@ export default defineConfig({
},
resolve: {
conditions: ['development', 'browser'],
alias: {
// This Rollup aliases are extracted from @esbuild-plugins/node-modules-polyfill,
// see https://github.com/remorses/esbuild-plugins/blob/master/node-modules-polyfill/src/polyfills.ts
// process and buffer are excluded because already managed
// by node-globals-polyfill
util: 'rollup-plugin-node-polyfills/polyfills/util',
sys: 'util',
events: 'rollup-plugin-node-polyfills/polyfills/events',
stream: 'rollup-plugin-node-polyfills/polyfills/stream',
path: 'rollup-plugin-node-polyfills/polyfills/path',
querystring: 'rollup-plugin-node-polyfills/polyfills/qs',
punycode: 'rollup-plugin-node-polyfills/polyfills/punycode',
url: 'rollup-plugin-node-polyfills/polyfills/url',
string_decoder: 'rollup-plugin-node-polyfills/polyfills/string-decoder',
http: 'rollup-plugin-node-polyfills/polyfills/http',
https: 'rollup-plugin-node-polyfills/polyfills/http',
os: 'rollup-plugin-node-polyfills/polyfills/os',
assert: 'rollup-plugin-node-polyfills/polyfills/assert',
constants: 'rollup-plugin-node-polyfills/polyfills/constants',
_stream_duplex:
'rollup-plugin-node-polyfills/polyfills/readable-stream/duplex',
_stream_passthrough:
'rollup-plugin-node-polyfills/polyfills/readable-stream/passthrough',
_stream_readable:
'rollup-plugin-node-polyfills/polyfills/readable-stream/readable',
_stream_writable:
'rollup-plugin-node-polyfills/polyfills/readable-stream/writable',
_stream_transform:
'rollup-plugin-node-polyfills/polyfills/readable-stream/transform',
timers: 'rollup-plugin-node-polyfills/polyfills/timers',
console: 'rollup-plugin-node-polyfills/polyfills/console',
vm: 'rollup-plugin-node-polyfills/polyfills/vm',
zlib: 'rollup-plugin-node-polyfills/polyfills/zlib',
tty: 'rollup-plugin-node-polyfills/polyfills/tty',
domain: 'rollup-plugin-node-polyfills/polyfills/domain',
},
});
},
define:
command === 'serve'
? {
// By default, Vite doesn't include shims for NodeJS/
// necessary for segment analytics lib to work
global: {},
}
: {},
}));