managarten/packages/qr-export/src/builder.ts
Till-JS e5109da732 feat(wallpaper-generator): add device wallpaper generation package
Add new @manacore/wallpaper-generator package for creating device
wallpapers from QR codes and images.

Features:
- 30 device presets (phones, tablets, desktops)
- 3 layout types (center, corner, pattern)
- 5 gradient presets + solid color backgrounds
- Browser (Canvas) and Node.js (Sharp) renderers
- Svelte WallpaperModal UI component

Integrations:
- @manacore/qr-export: toWallpaper() function
- @manacore/spiral-db: toWallpaper() function
- QRExportModal: "Als Wallpaper" button

Also includes the full @manacore/qr-export package which was
previously untracked.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-17 12:57:43 +01:00

247 lines
4.6 KiB
TypeScript

/**
* ManaQR Export Builder
*
* Fluent API for building QR exports easily.
*/
import type {
ManaQRExport,
ManaQRUserContext,
ManaQRContact,
ManaQREvent,
ManaQRTodo,
EncodeResult,
ContactRelation,
TodoPriority,
} from './types';
import { MANA_QR_LIMITS } from './types';
import { encode, estimateSize } from './encoder';
import {
selectTopContacts,
selectUpcomingEvents,
selectPriorityTodos,
type ContactInput,
type EventInput,
type TodoInput,
} from './selectors';
/**
* Builder for creating ManaQR exports
*
* @example
* ```ts
* const result = createManaQRExport()
* .user({ n: 'Till', z: 'Europe/Berlin', l: 'de' })
* .addContact({ n: 'Mama', p: '+491701234567', r: 1 })
* .addContact({ n: 'Papa', p: '+491707654321', r: 1 })
* .addEvent({ t: 'Zahnarzt', s: Date.now() + 86400000, d: 60 })
* .addTodo({ t: 'Steuererklärung', p: 1, d: 14 })
* .encode();
* ```
*/
export class ManaQRExportBuilder {
private _user: ManaQRUserContext = { n: '' };
private _contacts: ManaQRContact[] = [];
private _events: ManaQREvent[] = [];
private _todos: ManaQRTodo[] = [];
private _timestamp: number = Math.floor(Date.now() / 1000);
/**
* Set user context
*/
user(context: ManaQRUserContext): this {
this._user = context;
return this;
}
/**
* Set user name (shorthand)
*/
userName(name: string): this {
this._user.n = name;
return this;
}
/**
* Set user timezone
*/
timezone(tz: string): this {
this._user.z = tz;
return this;
}
/**
* Set user language
*/
language(lang: string): this {
this._user.l = lang;
return this;
}
/**
* Set user location
*/
location(loc: string): this {
this._user.w = loc;
return this;
}
/**
* Add a single contact
*/
addContact(contact: ManaQRContact): this {
this._contacts.push(contact);
return this;
}
/**
* Add multiple contacts
*/
contacts(contacts: ManaQRContact[]): this {
this._contacts.push(...contacts);
return this;
}
/**
* Add contacts from input format (auto-selects top contacts)
*/
contactsFrom(inputs: ContactInput[], limit: number = MANA_QR_LIMITS.MAX_CONTACTS): this {
this._contacts = selectTopContacts(inputs, limit);
return this;
}
/**
* Add a single event
*/
addEvent(event: ManaQREvent): this {
this._events.push(event);
return this;
}
/**
* Add multiple events
*/
events(events: ManaQREvent[]): this {
this._events.push(...events);
return this;
}
/**
* Add events from input format (auto-selects upcoming)
*/
eventsFrom(inputs: EventInput[], limit: number = MANA_QR_LIMITS.MAX_EVENTS): this {
this._events = selectUpcomingEvents(inputs, limit);
return this;
}
/**
* Add a single todo
*/
addTodo(todo: ManaQRTodo): this {
this._todos.push(todo);
return this;
}
/**
* Add multiple todos
*/
todos(todos: ManaQRTodo[]): this {
this._todos.push(...todos);
return this;
}
/**
* Add todos from input format (auto-selects by priority)
*/
todosFrom(inputs: TodoInput[], limit: number = MANA_QR_LIMITS.MAX_TODOS): this {
this._todos = selectPriorityTodos(inputs, limit);
return this;
}
/**
* Set custom timestamp (Unix seconds)
*/
timestamp(ts: number): this {
this._timestamp = ts;
return this;
}
/**
* Build the export data (without encoding)
*/
build(): ManaQRExport {
return {
v: 1,
ts: this._timestamp,
u: this._user,
c: this._contacts,
e: this._events,
t: this._todos,
};
}
/**
* Estimate encoded size in bytes
*/
estimateSize(): number {
return estimateSize(this.build());
}
/**
* Check if current data will fit in a QR code
*/
willFit(): boolean {
return this.estimateSize() <= MANA_QR_LIMITS.MAX_QR_BYTES;
}
/**
* Encode to QR-ready string
*/
encode(): EncodeResult {
return encode(this.build());
}
}
/**
* Create a new ManaQR export builder
*/
export function createManaQRExport(): ManaQRExportBuilder {
return new ManaQRExportBuilder();
}
// --- Quick helpers for common formats ---
/**
* Create a contact in compact format
*/
export function contact(
name: string,
phone?: string,
relation: ContactRelation = 3
): ManaQRContact {
return { n: name, p: phone, r: relation };
}
/**
* Create an event in compact format
*/
export function event(
title: string,
startDate: Date,
durationMinutes = 60,
location?: string
): ManaQREvent {
return {
t: title,
s: Math.floor(startDate.getTime() / 1000),
d: durationMinutes,
l: location,
};
}
/**
* Create a todo in compact format
*/
export function todo(title: string, priority: TodoPriority = 2, dueDays?: number): ManaQRTodo {
return { t: title, p: priority, d: dueDays };
}