POWR/types/templates.ts

291 lines
6.6 KiB
TypeScript
Raw Normal View History

2025-02-24 22:27:01 -05:00
// types/templates.ts
import { BaseExercise, Equipment, ExerciseCategory, SetType } from './exercise';
2025-02-19 21:39:47 -05:00
import { StorageSource, SyncableContent } from './shared';
import { generateId } from '@/utils/ids';
/**
* Template Classifications
2025-02-24 22:27:01 -05:00
* Aligned with NIP-33402
2025-02-19 21:39:47 -05:00
*/
export type TemplateType = 'strength' | 'circuit' | 'emom' | 'amrap';
export type TemplateCategory =
| 'Full Body'
| 'Push/Pull/Legs'
| 'Upper/Lower'
| 'Custom'
| 'Cardio'
| 'CrossFit'
| 'Strength'
| 'Conditioning';
/**
* Exercise configurations within templates
*/
export interface TemplateExerciseDisplay {
title: string;
targetSets: number;
targetReps: number;
equipment?: string;
2025-02-19 21:39:47 -05:00
notes?: string;
}
export interface TemplateExerciseConfig {
id?: string; // Add this line
2025-02-19 21:39:47 -05:00
exercise: BaseExercise;
targetSets?: number;
targetReps?: number;
targetWeight?: number;
2025-02-19 21:39:47 -05:00
weight?: number;
rpe?: number;
2025-02-24 22:27:01 -05:00
setType?: SetType;
2025-02-19 21:39:47 -05:00
restSeconds?: number;
notes?: string;
2025-02-24 22:27:01 -05:00
// Format configuration from NIP-33401
2025-02-19 21:39:47 -05:00
format?: {
weight?: boolean;
reps?: boolean;
rpe?: boolean;
set_type?: boolean;
};
format_units?: {
weight?: 'kg' | 'lbs';
reps?: 'count';
rpe?: '0-10';
set_type?: 'warmup|normal|drop|failure';
};
2025-02-24 22:27:01 -05:00
// For timed workouts
duration?: number;
interval?: number;
// For circuit/EMOM
position?: number;
roundRest?: number;
2025-02-19 21:39:47 -05:00
}
export interface TemplateExerciseWithData {
id: string;
exercise: BaseExercise;
displayOrder: number;
targetSets: number | null;
targetReps: number | null;
targetWeight: number | null;
notes: string | null;
nostrReference?: string | null;
}
2025-02-19 21:39:47 -05:00
/**
* Template versioning and derivation tracking
*/
export interface TemplateSource {
id: string;
eventId?: string;
authorName?: string;
authorPubkey?: string;
version?: number;
}
/**
* Base template properties shared between UI and database
*/
export interface TemplateBase {
id: string;
title: string;
type: TemplateType;
category: TemplateCategory;
description?: string;
2025-02-24 22:27:01 -05:00
notes?: string;
2025-02-19 21:39:47 -05:00
tags: string[];
2025-02-24 22:27:01 -05:00
// Workout structure
rounds?: number;
duration?: number;
interval?: number;
restBetweenRounds?: number;
// Metadata
2025-02-19 21:39:47 -05:00
metadata?: {
lastUsed?: number;
useCount?: number;
averageDuration?: number;
2025-02-24 22:27:01 -05:00
completionRate?: number;
2025-02-19 21:39:47 -05:00
};
2025-02-24 22:27:01 -05:00
2025-02-19 21:39:47 -05:00
author?: {
name: string;
pubkey?: string;
};
}
/**
* UI Template - Used for display and interaction
*/
export interface Template extends TemplateBase {
exercises: TemplateExerciseDisplay[];
source: 'local' | 'powr' | 'nostr';
isFavorite: boolean; // Required for UI state
}
/**
* Full Template - Used for database storage and Nostr events
*/
export interface WorkoutTemplate extends TemplateBase, SyncableContent {
exercises: TemplateExerciseConfig[];
isPublic: boolean;
version: number;
lastUpdated?: number;
parentId?: string;
2025-02-19 21:39:47 -05:00
// Template configuration
format?: {
weight?: boolean;
reps?: boolean;
rpe?: boolean;
set_type?: boolean;
};
format_units?: {
weight: 'kg' | 'lbs';
reps: 'count';
rpe: '0-10';
set_type: 'warmup|normal|drop|failure';
};
// Template derivation
sourceTemplate?: TemplateSource;
derivatives?: {
count: number;
lastCreated: number;
};
// Nostr integration
nostrEventId?: string;
2025-02-24 22:27:01 -05:00
relayUrls?: string[];
authorPubkey?: string;
// Template management
isArchived?: boolean;
2025-02-19 21:39:47 -05:00
}
/**
* Helper Functions
*/
/**
* Gets a display string for the template source
*/
export function getSourceDisplay(template: WorkoutTemplate): string {
if (!template.sourceTemplate) {
return template.availability.source.includes('nostr')
? 'NOSTR'
: template.availability.source.includes('powr')
? 'POWR'
: 'Local Template';
}
const author = template.sourceTemplate.authorName || 'Unknown Author';
if (template.sourceTemplate.version) {
return `Modified from ${author} (v${template.sourceTemplate.version})`;
}
return `Original by ${author}`;
}
/**
* Converts a WorkoutTemplate to Template for UI display
*/
export function toTemplateDisplay(template: WorkoutTemplate): Template {
return {
id: template.id,
title: template.title,
type: template.type,
category: template.category,
description: template.description,
exercises: template.exercises.map(ex => ({
title: ex.exercise.title,
targetSets: ex.targetSets || 0, // Add default value
targetReps: ex.targetReps || 0, // Add default value
2025-02-19 21:39:47 -05:00
notes: ex.notes
})),
tags: template.tags,
source: template.availability.source.includes('nostr')
? 'nostr'
: template.availability.source.includes('powr')
? 'powr'
: 'local',
isFavorite: false,
metadata: template.metadata,
author: template.author
};
}
/**
* Converts a Template to WorkoutTemplate for storage/sync
*/
export function toWorkoutTemplate(template: Template): WorkoutTemplate {
return {
...template,
exercises: template.exercises.map(ex => ({
exercise: {
id: generateId(),
title: ex.title,
type: 'strength',
category: 'Push' as ExerciseCategory,
2025-02-24 22:27:01 -05:00
equipment: 'barbell' as Equipment,
2025-02-19 21:39:47 -05:00
tags: [],
2025-02-24 22:27:01 -05:00
format: {
weight: true,
reps: true,
rpe: true,
set_type: true
},
format_units: {
weight: 'kg',
reps: 'count',
rpe: '0-10',
set_type: 'warmup|normal|drop|failure'
},
2025-02-19 21:39:47 -05:00
availability: {
source: ['local']
},
created_at: Date.now()
},
targetSets: ex.targetSets,
targetReps: ex.targetReps,
notes: ex.notes
})),
isPublic: false,
version: 1,
created_at: Date.now(),
availability: {
source: ['local']
}
};
2025-02-24 22:27:01 -05:00
}
/**
* Creates a Nostr event from a template (NIP-33402)
*/
export function createNostrTemplateEvent(template: WorkoutTemplate) {
return {
kind: 33402,
content: template.description || '',
tags: [
['d', template.id],
['title', template.title],
['type', template.type],
...(template.rounds ? [['rounds', template.rounds.toString()]] : []),
...(template.duration ? [['duration', template.duration.toString()]] : []),
...(template.interval ? [['interval', template.interval.toString()]] : []),
...template.exercises.map(ex => [
'exercise',
`33401:${ex.exercise.id}`,
(ex.targetSets || 0).toString(),
(ex.targetReps || 0).toString(),
2025-02-24 22:27:01 -05:00
ex.setType || 'normal'
]),
...template.tags.map(tag => ['t', tag])
],
created_at: Math.floor(Date.now() / 1000)
};
2025-02-19 21:39:47 -05:00
}