full settings overhaul

This commit is contained in:
Christian Beutel
2024-07-05 18:14:31 +02:00
parent 6522b06e96
commit d0a408983b
47 changed files with 4355 additions and 551 deletions

266
web/src/lib/vendor/easy-fit/binary.ts vendored Normal file
View File

@@ -0,0 +1,266 @@
import { FIT } from "./fit";
import { getFitMessage, getFitMessageBaseType } from "./messages";
export { addEndian, readRecord, getArrayBuffer, calculateCRC };
function addEndian(littleEndian: boolean, bytes: any[]) {
var result = 0;
if (!littleEndian) bytes.reverse();
for (var i = 0; i < bytes.length; i++) {
result += bytes[i] << (i << 3) >>> 0;
}
return result;
}
function readData(blob: Uint8Array, fDef: { endianAbility: boolean; size: number; littleEndian: boolean; type: string; }, startIndex: number) {
if (fDef.endianAbility === true) {
var temp = [];
for (var i = 0; i < fDef.size; i++) {
temp.push(blob[startIndex + i]);
}
var uint32Rep = addEndian(fDef.littleEndian, temp);
if (fDef.type === 'sint32') {
return uint32Rep >> 0;
}
return uint32Rep;
}
return blob[startIndex];
}
function formatByType(data: number, type: string | number, scale: number, offset: number) {
switch (type) {
case 'date_time':
return new Date(data * 1000 + 631065600000);
case 'sint32':
case 'sint16':
return data * FIT.scConst;
case 'uint32':
case 'uint16':
return scale ? data / scale + offset : data;
default:
if (FIT.types[type]) {
return FIT.types[type][data];
}
return data;
}
}
function isInvalidValue(data: number, type: string) {
switch (type) {
case 'enum':
return data === 0xFF;
case 'sint8':
return data === 0x7F;
case 'uint8':
return data === 0xFF;
case 'sint16':
return data === 0x7FFF;
case 'unit16':
return data === 0xFFFF;
case 'sint32':
return data === 0x7FFFFFFF;
case 'uint32':
return data === 0xFFFFFFFF;
case 'string':
return data === 0x00;
case 'float32':
return data === 0xFFFFFFFF;
case 'float64':
return data === 0xFFFFFFFFFFFFFFFF;
case 'uint8z':
return data === 0x00;
case 'uint16z':
return data === 0x0000;
case 'uint32z':
return data === 0x000000;
case 'byte':
return data === 0xFF;
case 'sint64':
return data === 0x7FFFFFFFFFFFFFFF;
case 'uint64':
return data === 0xFFFFFFFFFFFFFFFF;
case 'uint64z':
return data === 0x0000000000000000;
default:
return false;
}
}
function convertTo(data: number, unitsList: string, speedUnit: string | number) {
var unitObj = FIT.options[unitsList][speedUnit];
return unitObj ? data * unitObj.multiplier + unitObj.offset : data;
}
function applyOptions(data: number, field: any, options: { speedUnit?: string | number; lengthUnit?: string | number; temperatureUnit?: string | number; }) {
switch (field) {
case 'speed':
case 'enhanced_speed':
case 'vertical_speed':
case 'avg_speed':
case 'max_speed':
case 'speed_1s':
case 'ball_speed':
case 'enhanced_avg_speed':
case 'enhanced_max_speed':
case 'avg_pos_vertical_speed':
case 'max_pos_vertical_speed':
case 'avg_neg_vertical_speed':
case 'max_neg_vertical_speed':
return convertTo(data, 'speedUnits', options.speedUnit ?? 0);
case 'distance':
case 'total_distance':
case 'enhanced_avg_altitude':
case 'enhanced_min_altitude':
case 'enhanced_max_altitude':
case 'enhanced_altitude':
case 'height':
case 'odometer':
case 'avg_stroke_distance':
case 'min_altitude':
case 'avg_altitude':
case 'max_altitude':
case 'total_ascent':
case 'total_descent':
case 'altitude':
case 'cycle_length':
case 'auto_wheelsize':
case 'custom_wheelsize':
case 'gps_accuracy':
return convertTo(data, 'lengthUnits', options.lengthUnit ?? 0);
case 'temperature':
case 'avg_temperature':
case 'max_temperature':
return convertTo(data, 'temperatureUnits', options.temperatureUnit ?? 0);
default:
return data;
}
}
function readRecord(blob: Uint8Array, messageTypes: Record<number, any>, startIndex: number, options: { elapsedRecordField?: any; speedUnit?: string | number; lengthUnit?: string | number; temperatureUnit?: string | number; }, startDate: number | undefined) {
var recordHeader = blob[startIndex];
var localMessageType = recordHeader & 15;
if ((recordHeader & 64) === 64) {
// is definition message
// startIndex + 1 is reserved
var lEnd = blob[startIndex + 2] === 0;
var mTypeDef: { littleEndian: boolean, globalMessageNumber: number, numberOfFields: number, fieldDefs: any[] } = {
littleEndian: lEnd,
globalMessageNumber: addEndian(lEnd, [blob[startIndex + 3], blob[startIndex + 4]]),
numberOfFields: blob[startIndex + 5],
fieldDefs: []
};
var _message = getFitMessage(mTypeDef.globalMessageNumber);
for (var i = 0; i < mTypeDef.numberOfFields; i++) {
var fDefIndex = startIndex + 6 + i * 3;
var baseType = blob[fDefIndex + 2];
var _message$getAttribute = _message.getAttributes(blob[fDefIndex]),
field = _message$getAttribute.field,
type = _message$getAttribute.type;
var fDef = {
type: type,
fDefNo: blob[fDefIndex],
size: blob[fDefIndex + 1],
endianAbility: (baseType & 128) === 128,
littleEndian: lEnd,
baseTypeNo: baseType & 15,
name: field,
dataType: getFitMessageBaseType(baseType & 15)
};
mTypeDef.fieldDefs.push(fDef);
}
messageTypes[localMessageType] = mTypeDef;
return {
messageType: 'fieldDescription',
nextIndex: startIndex + 6 + mTypeDef.numberOfFields * 3
};
}
var messageType: { littleEndian: boolean, globalMessageNumber: number, numberOfFields: number, fieldDefs: any[] } | undefined = void 0;
if (messageTypes[localMessageType]) {
messageType = messageTypes[localMessageType];
} else {
messageType = messageTypes[0];
}
// TODO: handle compressed header ((recordHeader & 128) == 128)
// uncompressed header
var messageSize = 0;
var readDataFromIndex = startIndex + 1;
var fields: any = {};
var message = getFitMessage(messageType?.globalMessageNumber);
for (var _i = 0; _i < (messageType?.fieldDefs.length ?? 0); _i++) {
var _fDef = messageType?.fieldDefs[_i];
var data = readData(blob, _fDef, readDataFromIndex);
if (!isInvalidValue(data, _fDef.type)) {
var _message$getAttribute2 = message.getAttributes(_fDef.fDefNo),
field = _message$getAttribute2.field,
type = _message$getAttribute2.type,
scale = _message$getAttribute2.scale,
offset = _message$getAttribute2.offset;
if (field !== 'unknown' && field !== '' && field !== undefined) {
fields[field] = applyOptions(formatByType(data, type, scale, offset), field, options);
}
if (message.name === 'record' && options.elapsedRecordField) {
fields.elapsed_time = (fields.timestamp - (startDate ?? 0)) / 1000;
}
}
readDataFromIndex += _fDef.size;
messageSize += _fDef.size;
}
var result = {
messageType: message.name,
nextIndex: startIndex + messageSize + 1,
message: fields
};
return result;
}
function getArrayBuffer(buffer: ArrayBuffer | number[]) {
if (buffer instanceof ArrayBuffer) {
return buffer;
}
var ab = new ArrayBuffer(buffer.length);
var view = new Uint8Array(ab);
for (var i = 0; i < buffer.length; ++i) {
view[i] = buffer[i];
}
return ab;
}
function calculateCRC(blob: Uint8Array, start: number, end: number) {
var crcTable = [0x0000, 0xCC01, 0xD801, 0x1400, 0xF001, 0x3C00, 0x2800, 0xE401, 0xA001, 0x6C00, 0x7800, 0xB401, 0x5000, 0x9C01, 0x8801, 0x4400];
var crc = 0;
for (var i = start; i < end; i++) {
var byte = blob[i];
var tmp = crcTable[crc & 0xF];
crc = crc >> 4 & 0x0FFF;
crc = crc ^ tmp ^ crcTable[byte & 0xF];
tmp = crcTable[crc & 0xF];
crc = crc >> 4 & 0x0FFF;
crc = crc ^ tmp ^ crcTable[byte >> 4 & 0xF];
}
return crc;
}

156
web/src/lib/vendor/easy-fit/easy-fit.ts vendored Normal file
View File

@@ -0,0 +1,156 @@
import { calculateCRC, getArrayBuffer, readRecord } from "./binary";
type Options = {
force: boolean,
speedUnit: 'm/s' | 'km/h' | 'mph',
lengthUnit: 'm' | 'km' | 'mi',
temperatureUnit: 'celsius' | 'fahrenheit',
elapsedRecordField: boolean,
mode: 'cascade' | 'list' | 'both'
}
class EasyFit {
options: Options;
constructor(
options?: Options
) {
this.options = options || {
force: true,
speedUnit: 'm/s',
lengthUnit: 'm',
temperatureUnit: 'celsius',
elapsedRecordField: false,
mode: 'list'
};
}
parse(content: ArrayBuffer, callback: { (error: string, data: any): void; (arg0: string | null, arg1: {}): void; }) {
var blob = new Uint8Array(getArrayBuffer(content));
if (blob.length < 12) {
callback('File to small to be a FIT file', {});
if (!this.options.force) {
return;
}
}
var headerLength = blob[0];
if (headerLength !== 14 && headerLength !== 12) {
callback('Incorrect header size', {});
if (!this.options.force) {
return;
}
}
var fileTypeString = '';
for (var i = 8; i < 12; i++) {
fileTypeString += String.fromCharCode(blob[i]);
}
if (fileTypeString !== '.FIT') {
callback('Missing \'.FIT\' in header', {});
if (!this.options.force) {
return;
}
}
if (headerLength === 14) {
var crcHeader = blob[12] + (blob[13] << 8);
var crcHeaderCalc = calculateCRC(blob, 0, 12);
if (crcHeader !== crcHeaderCalc) {
// callback('Header CRC mismatch', {});
// TODO: fix Header CRC check
if (!this.options.force) {
return;
}
}
}
var dataLength = blob[4] + (blob[5] << 8) + (blob[6] << 16) + (blob[7] << 24);
var crcStart = dataLength + headerLength;
var crcFile = blob[crcStart] + (blob[crcStart + 1] << 8);
var crcFileCalc = calculateCRC(blob, headerLength === 12 ? 0 : headerLength, crcStart);
if (crcFile !== crcFileCalc) {
// callback('File CRC mismatch', {});
// TODO: fix File CRC check
if (!this.options.force) {
return;
}
}
var fitObj = {};
var sessions = [];
var laps = [];
var records = [];
var events = [];
var tempLaps = [];
var tempRecords = [];
var loopIndex = headerLength;
var messageTypes: Record<number, any> = [];
var isModeCascade = this.options.mode === 'cascade';
var isCascadeNeeded = isModeCascade || this.options.mode === 'both';
var startDate = void 0;
while (loopIndex < crcStart) {
var _readRecord = readRecord(blob, messageTypes, loopIndex, this.options, startDate),
nextIndex = _readRecord.nextIndex,
messageType = _readRecord.messageType,
message = _readRecord.message;
loopIndex = nextIndex;
switch (messageType) {
case 'lap':
if (isCascadeNeeded) {
message.records = tempRecords;
tempRecords = [];
tempLaps.push(message);
}
laps.push(message);
break;
case 'session':
if (isCascadeNeeded) {
message.laps = tempLaps;
tempLaps = [];
}
sessions.push(message);
break;
case 'event':
events.push(message);
break;
case 'record':
if (!startDate) {
startDate = message.timestamp;
message.elapsed_time = 0;
}
records.push(message);
if (isCascadeNeeded) {
tempRecords.push(message);
}
break;
default:
if (messageType !== '') {
fitObj[messageType] = message;
}
break;
}
}
if (isCascadeNeeded) {
fitObj.activity.sessions = sessions;
fitObj.activity.events = events;
}
if (!isModeCascade) {
fitObj.sessions = sessions;
fitObj.laps = laps;
fitObj.records = records;
fitObj.events = events;
}
callback(null, fitObj);
}
}
export default EasyFit

2437
web/src/lib/vendor/easy-fit/fit.ts vendored Normal file

File diff suppressed because it is too large Load Diff

16
web/src/lib/vendor/easy-fit/messages.ts vendored Normal file
View File

@@ -0,0 +1,16 @@
export { getFitMessage, getFitMessageBaseType }
import { getFieldObject, getMessageName } from "./fit"
function getFitMessage(messageNum: any) {
return {
name: getMessageName(messageNum),
getAttributes: function getAttributes(fieldNum: any) {
return getFieldObject(fieldNum, messageNum);
}
};
}
function getFitMessageBaseType(foo: any) {
return foo;
}

View File

@@ -1,39 +0,0 @@
import { Feature, FeatureCollection } from 'geojson';
declare interface Bounds {
minlat: string;
minlon: string;
maxlat: string;
maxlon: string;
}
declare interface Copyright {
author?: string;
year?: string;
license?: string;
}
declare interface Link {
href: string;
text?: string;
type?: string;
}
declare interface Person {
name?: string;
email?: string;
link?: Link;
}
declare interface MetaData {
name?: string;
desc?: string;
author?: Person;
copyright?: Copyright;
link?: Link;
time?: string;
keywords?: string;
bounds?: Bounds;
}
export declare interface Options {
creator?: string;
version?: string;
metadata?: MetaData;
}
export default function GeoJsonToGpx(geoJson: Feature | FeatureCollection, options?: Options, implementation?: DOMImplementation): XMLDocument;
export {};

View File

@@ -1,155 +0,0 @@
export default function GeoJsonToGpx(geoJson, options, implementation) {
if (implementation === void 0) { implementation = document.implementation; }
var doc = implementation.createDocument('http://www.topografix.com/GPX/1/1', '');
var instruct = doc.createProcessingInstruction('xml', 'version="1.0" encoding="UTF-8"');
doc.appendChild(instruct);
var defaultPackageName = '@dwayneparton/geojson-to-gpx';
var creator = (options === null || options === void 0 ? void 0 : options.creator) || defaultPackageName;
var createElementWithNS = function (tagName) {
return doc.createElementNS('http://www.topografix.com/GPX/1/1', tagName);
};
var gpx = createElementWithNS('gpx');
gpx.setAttribute('version', '1.1');
gpx.setAttribute('creator', creator);
var wpts = [];
var trks = [];
function createTagInParentElement(parent, tagName, content) {
if (content === undefined) {
return;
}
var element = createElementWithNS(tagName);
var contentEl = doc.createTextNode(String(content));
element.appendChild(contentEl);
parent.appendChild(element);
}
function addSupportedPropertiesFromObject(el, supports, properties) {
if (properties && typeof properties === 'object') {
supports.forEach(function (key) {
var value = properties[key];
if (value && typeof value === 'string' && supports.includes(key)) {
createTagInParentElement(el, key, value);
}
});
}
}
function createLinkInParentElement(parent, props) {
var href = props.href;
if (!href) {
return;
}
var el = createElementWithNS('link');
el.setAttribute('href', href);
addSupportedPropertiesFromObject(el, ['text', 'type'], props);
parent.appendChild(el);
}
function createTrk(properties) {
var el = createElementWithNS('trk');
var supports = ['name', 'desc', 'src', 'type'];
addSupportedPropertiesFromObject(el, supports, properties);
return el;
}
function createPt(type, position, properties) {
var lon = position[0], lat = position[1], ele = position[2], time = position[3];
var el = createElementWithNS(type);
el.setAttribute('lat', String(lat));
el.setAttribute('lon', String(lon));
createTagInParentElement(el, 'ele', ele);
createTagInParentElement(el, 'time', time);
var supports = ['name', 'desc', 'src', 'type'];
addSupportedPropertiesFromObject(el, supports, properties);
return el;
}
function createTrkSeg(coordinates) {
var el = createElementWithNS('trkseg');
coordinates.forEach(function (point) {
el.appendChild(createPt('trkpt', point));
});
return el;
}
function interpretFeature(feature) {
var geometry = feature.geometry, properties = feature.properties;
var type = geometry.type;
switch (type) {
case 'Polygon':
break;
case 'Point': {
wpts.push(createPt('wpt', geometry.coordinates, properties));
break;
}
case 'MultiPoint': {
geometry.coordinates.forEach(function (coord) {
wpts.push(createPt('wpt', coord, properties));
});
break;
}
case 'LineString': {
var lineTrk = createTrk(properties);
var trkseg = createTrkSeg(geometry.coordinates);
lineTrk.appendChild(trkseg);
trks.push(lineTrk);
break;
}
case 'MultiLineString': {
var trk_1 = createTrk(properties);
geometry.coordinates.forEach(function (pos) {
var trkseg = createTrkSeg(pos);
trk_1.appendChild(trkseg);
});
trks.push(trk_1);
break;
}
default:
break;
}
}
if (options && typeof options.metadata === 'object') {
var meta = options.metadata;
var metadata = createElementWithNS('metadata');
createTagInParentElement(metadata, 'name', meta.name);
createTagInParentElement(metadata, 'desc', meta.desc);
if (meta.author && typeof meta.author === 'object') {
var author = createElementWithNS('author');
createTagInParentElement(author, 'name', meta.author.name);
createTagInParentElement(author, 'email', meta.author.email);
if (meta.author.link && typeof meta.author.link === 'object') {
createLinkInParentElement(author, meta.author.link);
}
metadata.appendChild(author);
}
if (typeof meta.copyright === 'object') {
var copyright = createElementWithNS('copyright');
if (meta.copyright.author) {
copyright.setAttribute('author', meta.copyright.author);
}
createTagInParentElement(copyright, 'year', meta.copyright.year);
createTagInParentElement(copyright, 'license', meta.copyright.license);
metadata.appendChild(copyright);
}
if (typeof meta.link === 'object') {
createLinkInParentElement(metadata, meta.link);
}
createTagInParentElement(metadata, 'time', meta.time);
createTagInParentElement(metadata, 'keywords', meta.keywords);
gpx.appendChild(metadata);
}
var type = geoJson.type;
switch (type) {
case 'Feature': {
interpretFeature(geoJson);
break;
}
case 'FeatureCollection': {
var features = geoJson.features;
features.forEach(function (feature) {
interpretFeature(feature);
});
break;
}
default:
break;
}
wpts.forEach(function (wpt) { return gpx.appendChild(wpt); });
trks.forEach(function (trk) { return gpx.appendChild(trk); });
doc.appendChild(gpx);
return doc;
}

View File

@@ -134,7 +134,9 @@ export const createForm = (config) => {
return clearErrorsAndSubmit(values);
} else {
errors.set(error);
onError(error)
if(onError) {
onError(error)
}
isSubmitting.set(false);
}
})
@@ -156,7 +158,9 @@ export const createForm = (config) => {
yupErrors.inner.map((error) =>
util.set(updatedErrors, error.path, error.message),
);
onError(updatedErrors)
if(onError) {
onError(updatedErrors)
}
errors.set(updatedErrors);
}
isSubmitting.set(false);