merge: combine all features from both codebases - mobile dashboard + layer panel + notifications + shares + all fixes

This commit is contained in:
Leopoldadmin
2026-07-04 16:43:55 +02:00
parent 0606dbb501
commit be62470b53
79 changed files with 9562 additions and 3132 deletions
+115
View File
@@ -0,0 +1,115 @@
import { describe, it, expect } from 'vitest';
import { formatDistance, parseDistance, formatCoordinate, formatInputValue } from '../format';
describe('formatDistance', () => {
it('formats mm correctly with default scaleFactor', () => {
expect(formatDistance(100, 'mm')).toBe('100 mm');
expect(formatDistance(0, 'mm')).toBe('0 mm');
expect(formatDistance(1234.5, 'mm')).toBe('1235 mm');
});
it('formats cm correctly with default scaleFactor', () => {
expect(formatDistance(100, 'cm')).toBe('10.0 cm');
expect(formatDistance(0, 'cm')).toBe('0.0 cm');
expect(formatDistance(55, 'cm')).toBe('5.5 cm');
});
it('formats m correctly with default scaleFactor', () => {
expect(formatDistance(1000, 'm')).toBe('1.00 m');
expect(formatDistance(0, 'm')).toBe('0.00 m');
expect(formatDistance(1500, 'm')).toBe('1.50 m');
});
it('applies scaleFactor correctly', () => {
expect(formatDistance(100, 'mm', 10)).toBe('1000 mm');
expect(formatDistance(100, 'cm', 10)).toBe('100.0 cm');
expect(formatDistance(100, 'm', 10)).toBe('1.00 m');
});
});
describe('parseDistance', () => {
it('parses mm input correctly', () => {
expect(parseDistance('500', 'mm')).toBe(500);
expect(parseDistance('0', 'mm')).toBe(0);
});
it('parses cm input correctly', () => {
expect(parseDistance('10', 'cm')).toBe(100);
expect(parseDistance('5.5', 'cm')).toBe(55);
});
it('parses m input correctly', () => {
expect(parseDistance('1', 'm')).toBe(1000);
expect(parseDistance('0.5', 'm')).toBe(500);
});
it('applies scaleFactor correctly', () => {
expect(parseDistance('500', 'mm', 10)).toBe(50);
expect(parseDistance('10', 'cm', 10)).toBe(10);
expect(parseDistance('1', 'm', 10)).toBe(100);
});
it('returns 0 for invalid input', () => {
expect(parseDistance('abc', 'mm')).toBe(0);
expect(parseDistance('', 'cm')).toBe(0);
expect(parseDistance('NaN', 'm')).toBe(0);
});
});
describe('formatCoordinate', () => {
it('formats mm coordinate', () => {
expect(formatCoordinate(123.7, 'mm')).toBe('124');
});
it('formats cm coordinate', () => {
expect(formatCoordinate(123.7, 'cm')).toBe('12.4');
});
it('formats m coordinate with 3 decimal places', () => {
expect(formatCoordinate(1234.5, 'm')).toBe('1.235');
});
});
describe('formatInputValue', () => {
it('formats mm input value without unit suffix', () => {
expect(formatInputValue(100, 'mm')).toBe('100');
});
it('formats cm input value', () => {
expect(formatInputValue(100, 'cm')).toBe('10.0');
});
it('formats m input value', () => {
expect(formatInputValue(1500, 'm')).toBe('1.500');
});
});
describe('round-trip: parseDistance to formatDistance', () => {
it('round-trips mm values correctly', () => {
const input = '500';
const world = parseDistance(input, 'mm');
const formatted = formatDistance(world, 'mm');
expect(formatted).toBe('500 mm');
});
it('round-trips cm values correctly', () => {
const input = '10.5';
const world = parseDistance(input, 'cm');
const formatted = formatDistance(world, 'cm');
expect(formatted).toBe('10.5 cm');
});
it('round-trips m values correctly', () => {
const input = '2.50';
const world = parseDistance(input, 'm');
const formatted = formatDistance(world, 'm');
expect(formatted).toBe('2.50 m');
});
it('round-trips with scaleFactor', () => {
const input = '500';
const world = parseDistance(input, 'mm', 10);
const formatted = formatDistance(world, 'mm', 10);
expect(formatted).toBe('500 mm');
});
});
+94
View File
@@ -0,0 +1,94 @@
/**
* Unit formatting and parsing utilities for CAD distance values.
* All internal CAD coordinates are stored in world units.
* scaleFactor converts world units to millimeters (1 world unit = scaleFactor mm).
*/
export type UnitType = 'mm' | 'cm' | 'm';
/**
* Format a raw world-unit distance into a human-readable string with the given unit.
* @param raw - distance in world units
* @param unit - target display unit ('mm' | 'cm' | 'm')
* @param scaleFactor - millimeters per world unit (default 1)
* @returns formatted string like "500 mm", "5.0 cm", "0.05 m"
*/
export function formatDistance(
raw: number,
unit: UnitType,
scaleFactor: number = 1,
): string {
const mm = raw * scaleFactor;
switch (unit) {
case 'mm':
return `${mm.toFixed(0)} mm`;
case 'cm':
return `${(mm / 10).toFixed(1)} cm`;
case 'm':
return `${(mm / 1000).toFixed(2)} m`;
}
}
/**
* Parse a user-entered distance string back into world units.
* @param input - numeric string (e.g. "500", "5.5", "0.05")
* @param unit - the unit the user is entering values in
* @param scaleFactor - millimeters per world unit (default 1)
* @returns world-unit distance, or 0 if input is invalid
*/
export function parseDistance(
input: string,
unit: UnitType,
scaleFactor: number = 1,
): number {
const value = parseFloat(input);
if (isNaN(value)) return 0;
switch (unit) {
case 'mm':
return value / scaleFactor;
case 'cm':
return (value * 10) / scaleFactor;
case 'm':
return (value * 1000) / scaleFactor;
}
}
/**
* Format a raw world-unit coordinate value for compact display (e.g. status bar).
* Uses fewer decimal places than formatDistance.
*/
export function formatCoordinate(
raw: number,
unit: UnitType,
scaleFactor: number = 1,
): string {
const mm = raw * scaleFactor;
switch (unit) {
case 'mm':
return `${mm.toFixed(0)}`;
case 'cm':
return `${(mm / 10).toFixed(1)}`;
case 'm':
return `${(mm / 1000).toFixed(3)}`;
}
}
/**
* Format a raw world-unit value for an input field (without unit suffix).
* This is what the user sees in text inputs so they can edit the numeric value.
*/
export function formatInputValue(
raw: number,
unit: UnitType,
scaleFactor: number = 1,
): string {
const mm = raw * scaleFactor;
switch (unit) {
case 'mm':
return `${mm.toFixed(0)}`;
case 'cm':
return `${(mm / 10).toFixed(1)}`;
case 'm':
return `${(mm / 1000).toFixed(3)}`;
}
}