@workingdevshero/olympia-ait-adapter 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +208 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/reader/parse-day-sheet.d.ts +10 -0
- package/dist/reader/parse-day-sheet.d.ts.map +1 -0
- package/dist/reader/parse-day-sheet.js +199 -0
- package/dist/reader/parse-day-sheet.js.map +1 -0
- package/dist/reader/parse-workbook.d.ts +3 -0
- package/dist/reader/parse-workbook.d.ts.map +1 -0
- package/dist/reader/parse-workbook.js +55 -0
- package/dist/reader/parse-workbook.js.map +1 -0
- package/dist/schema/program.d.ts +692 -0
- package/dist/schema/program.d.ts.map +1 -0
- package/dist/schema/program.js +125 -0
- package/dist/schema/program.js.map +1 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +58 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/json-to-xlsx.d.ts +3 -0
- package/dist/tools/json-to-xlsx.d.ts.map +1 -0
- package/dist/tools/json-to-xlsx.js +5 -0
- package/dist/tools/json-to-xlsx.js.map +1 -0
- package/dist/tools/xlsx-to-json.d.ts +3 -0
- package/dist/tools/xlsx-to-json.d.ts.map +1 -0
- package/dist/tools/xlsx-to-json.js +5 -0
- package/dist/tools/xlsx-to-json.js.map +1 -0
- package/dist/writer/build-workbook.d.ts +3 -0
- package/dist/writer/build-workbook.d.ts.map +1 -0
- package/dist/writer/build-workbook.js +166 -0
- package/dist/writer/build-workbook.js.map +1 -0
- package/dist/writer/layout.d.ts +21 -0
- package/dist/writer/layout.d.ts.map +1 -0
- package/dist/writer/layout.js +40 -0
- package/dist/writer/layout.js.map +1 -0
- package/dist/writer/stamp-exercise.d.ts +31 -0
- package/dist/writer/stamp-exercise.d.ts.map +1 -0
- package/dist/writer/stamp-exercise.js +80 -0
- package/dist/writer/stamp-exercise.js.map +1 -0
- package/dist/writer/styles.d.ts +59 -0
- package/dist/writer/styles.d.ts.map +1 -0
- package/dist/writer/styles.js +266 -0
- package/dist/writer/styles.js.map +1 -0
- package/dist/writer/supplemental-template.d.ts +23 -0
- package/dist/writer/supplemental-template.d.ts.map +1 -0
- package/dist/writer/supplemental-template.js +112 -0
- package/dist/writer/supplemental-template.js.map +1 -0
- package/package.json +42 -0
- package/src/assets/olympia-logo.png +0 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const WEEK_COUNT = 6;
|
|
3
|
+
/**
|
|
4
|
+
* A single prescription cell for one set in one week.
|
|
5
|
+
*
|
|
6
|
+
* Olympia does not prescribe weights — the unit is reps, time, or breaths,
|
|
7
|
+
* encoded by notation in the cell text:
|
|
8
|
+
*
|
|
9
|
+
* "12" → 12 reps
|
|
10
|
+
* "10E" → 10 reps each side
|
|
11
|
+
* ":20" → 20-second hold
|
|
12
|
+
* ":20E" → 20-second hold each side
|
|
13
|
+
* ":60" → 60 seconds
|
|
14
|
+
* "5 breaths" → 5 breaths
|
|
15
|
+
* null → empty cell
|
|
16
|
+
*
|
|
17
|
+
* Free-form strings like "x3 Rest :60" are also valid (Giulianna's file
|
|
18
|
+
* has these for circuit instructions), so the grammar is intentionally
|
|
19
|
+
* not validated at the schema level.
|
|
20
|
+
*/
|
|
21
|
+
export const Cell = z
|
|
22
|
+
.string()
|
|
23
|
+
.nullable()
|
|
24
|
+
.describe('A single prescription. Common formats: "12" (12 reps), "10E" (10 reps each side), ":20" (20-sec hold), ":20E" (20-sec hold each side), ":60" (60 seconds), "5 breaths" (5 breaths). Free-form circuit instructions like "x3 Rest :60" are allowed. Use null for empty cells. Weights are NOT prescribed — units are reps, time, or breaths only.');
|
|
25
|
+
export const SetRow = z
|
|
26
|
+
.array(Cell)
|
|
27
|
+
.length(WEEK_COUNT)
|
|
28
|
+
.describe("One set's prescription across all 6 weeks of the program, in week order.");
|
|
29
|
+
export const Exercise = z
|
|
30
|
+
.object({
|
|
31
|
+
name: z
|
|
32
|
+
.string()
|
|
33
|
+
.min(1)
|
|
34
|
+
.describe('Exercise name, e.g. "2DB Alternating Bench Press".'),
|
|
35
|
+
groupLabel: z
|
|
36
|
+
.string()
|
|
37
|
+
.regex(/^\d+[A-Z]$/, "Group label must be a digit-letter pair like 1A, 1B, 2A")
|
|
38
|
+
.optional()
|
|
39
|
+
.describe('Superset/circuit grouping. Exercises sharing the same number form a group (e.g. 1A/1B run back-to-back). Omit for ungrouped exercises.'),
|
|
40
|
+
notes: z
|
|
41
|
+
.string()
|
|
42
|
+
.optional()
|
|
43
|
+
.describe('Coaching cue / instruction shown in the notes column.'),
|
|
44
|
+
sets: z
|
|
45
|
+
.array(SetRow)
|
|
46
|
+
.min(1)
|
|
47
|
+
.describe("2-D matrix of prescriptions: sets[setIdx][weekIdx]. Outer length = number of sets for this exercise (typically 3); each inner array is length 6 (weeks 1-6). Set positions are coherent across weeks — sets[0] is set 1's progression across the 6 weeks."),
|
|
48
|
+
})
|
|
49
|
+
.describe("One exercise in a training day, with its 6-week set-by-set prescription.");
|
|
50
|
+
export const SectionKind = z
|
|
51
|
+
.enum(["warmup", "main", "sportSpecific", "supplemental", "conditioning"])
|
|
52
|
+
.describe("Section type within a day. Olympia day-sheets are organized into ordered sections delimited by column-C labels.");
|
|
53
|
+
export const Section = z
|
|
54
|
+
.object({
|
|
55
|
+
kind: SectionKind,
|
|
56
|
+
exercises: z.array(Exercise),
|
|
57
|
+
})
|
|
58
|
+
.describe("An ordered group of exercises within a day.");
|
|
59
|
+
export const SupplementalNote = z
|
|
60
|
+
.object({
|
|
61
|
+
row: z
|
|
62
|
+
.number()
|
|
63
|
+
.int()
|
|
64
|
+
.nonnegative()
|
|
65
|
+
.describe('Row offset from the top of the supplemental area (0 = the "Sport Specific/" label row).'),
|
|
66
|
+
col: z
|
|
67
|
+
.string()
|
|
68
|
+
.regex(/^[A-Z]+$/)
|
|
69
|
+
.describe("Column letter where the cell sits. Typical values: C (label column), D/F/H/J/L/N (one per week, master cells of the single-row merges)."),
|
|
70
|
+
value: z.string().describe("The raw cell text the trainer wrote."),
|
|
71
|
+
})
|
|
72
|
+
.describe("One freeform cell captured from the trainer-managed Sport Specific / Supplemental area at the bottom of a day sheet.");
|
|
73
|
+
export const Day = z
|
|
74
|
+
.object({
|
|
75
|
+
name: z
|
|
76
|
+
.string()
|
|
77
|
+
.min(1)
|
|
78
|
+
.describe('Day name (becomes the sheet name), e.g. "Day 1".'),
|
|
79
|
+
sections: z.array(Section).min(1),
|
|
80
|
+
supplementalNotes: z
|
|
81
|
+
.array(SupplementalNote)
|
|
82
|
+
.optional()
|
|
83
|
+
.describe("Freeform notes the trainer wrote in the Sport Specific / Supplemental area at the bottom of the day. Sparse — only populated cells appear. The schema is intentionally loose because trainers use this area for athlete-specific instructions that don't fit the structured exercise table (e.g. conditioning circuits, sport-specific drills, recovery breath work)."),
|
|
84
|
+
})
|
|
85
|
+
.describe("One training day = one sheet in the output workbook.");
|
|
86
|
+
export const Program = z
|
|
87
|
+
.object({
|
|
88
|
+
client: z.string().min(1).describe('Client name, e.g. "Bobby Galli".'),
|
|
89
|
+
programDate: z
|
|
90
|
+
.string()
|
|
91
|
+
.describe('Program identifier date as it appears in the filename, e.g. "4.26" for April 2026. Free-form to match Olympia conventions.'),
|
|
92
|
+
weekDates: z
|
|
93
|
+
.array(z.string().nullable())
|
|
94
|
+
.length(WEEK_COUNT)
|
|
95
|
+
.optional()
|
|
96
|
+
.describe('Per-week date labels (the "Date:" header row in each sheet). 6 entries, one per week, or null where empty.'),
|
|
97
|
+
days: z.array(Day).min(1),
|
|
98
|
+
})
|
|
99
|
+
.describe("A complete Olympia training program. Maps 1:1 to one client XLSX workbook.");
|
|
100
|
+
// --- Tool input/output schemas ---
|
|
101
|
+
export const XlsxToJsonInput = z.object({
|
|
102
|
+
xlsxPath: z
|
|
103
|
+
.string()
|
|
104
|
+
.min(1)
|
|
105
|
+
.describe("Absolute path to an Olympia .xlsx file on the local filesystem."),
|
|
106
|
+
});
|
|
107
|
+
export const XlsxToJsonOutput = z.object({
|
|
108
|
+
program: Program,
|
|
109
|
+
warnings: z
|
|
110
|
+
.array(z.string())
|
|
111
|
+
.describe("Structural surprises encountered while parsing (e.g. unexpected section labels, exercises without group labels where one was expected). Empty if the file parsed cleanly."),
|
|
112
|
+
});
|
|
113
|
+
export const JsonToXlsxInput = z.object({
|
|
114
|
+
program: Program,
|
|
115
|
+
outputPath: z
|
|
116
|
+
.string()
|
|
117
|
+
.min(1)
|
|
118
|
+
.describe("Absolute path where the generated .xlsx file should be written. Parent directory must exist."),
|
|
119
|
+
});
|
|
120
|
+
export const JsonToXlsxOutput = z.object({
|
|
121
|
+
outputPath: z.string(),
|
|
122
|
+
bytesWritten: z.number().int().nonnegative(),
|
|
123
|
+
});
|
|
124
|
+
export const WEEK_COUNT_CONST = WEEK_COUNT;
|
|
125
|
+
//# sourceMappingURL=program.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"program.js","sourceRoot":"","sources":["../../src/schema/program.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAWpE,wBAAgB,YAAY,IAAI,SAAS,CAiDxC"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { JsonToXlsxInput, JsonToXlsxOutput, XlsxToJsonInput, XlsxToJsonOutput, } from "./schema/program.js";
|
|
5
|
+
import { xlsxToJson } from "./tools/xlsx-to-json.js";
|
|
6
|
+
import { jsonToXlsx } from "./tools/json-to-xlsx.js";
|
|
7
|
+
export function createServer() {
|
|
8
|
+
const server = new McpServer({
|
|
9
|
+
name: "olympia-ait-adapter",
|
|
10
|
+
version: "0.1.0",
|
|
11
|
+
});
|
|
12
|
+
server.registerTool("olympia_xlsx_to_json", {
|
|
13
|
+
title: "Olympia XLSX → JSON",
|
|
14
|
+
description: "Parse an Olympia Fitness program XLSX workbook into a strict JSON document. The output `program` is a structured representation of all training days, sections, exercises, and per-set/per-week prescriptions. `warnings` lists any structural surprises the parser encountered.",
|
|
15
|
+
inputSchema: XlsxToJsonInput.shape,
|
|
16
|
+
outputSchema: XlsxToJsonOutput.shape,
|
|
17
|
+
}, async (raw) => {
|
|
18
|
+
const input = XlsxToJsonInput.parse(raw);
|
|
19
|
+
const result = await xlsxToJson(input);
|
|
20
|
+
return {
|
|
21
|
+
content: [
|
|
22
|
+
{ type: "text", text: JSON.stringify(result, null, 2) },
|
|
23
|
+
],
|
|
24
|
+
structuredContent: result,
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
server.registerTool("olympia_json_to_xlsx", {
|
|
28
|
+
title: "Olympia JSON → XLSX",
|
|
29
|
+
description: "Render an Olympia program JSON document to an .xlsx workbook on disk. The input `program` MUST conform to the strict Olympia schema (one sheet per day; 6 weeks per set; group labels match `<digit><uppercase letter>`; units are reps/time/breaths only — weights are never prescribed). Returns the output path and bytes written.",
|
|
30
|
+
inputSchema: JsonToXlsxInput.shape,
|
|
31
|
+
outputSchema: JsonToXlsxOutput.shape,
|
|
32
|
+
}, async (raw) => {
|
|
33
|
+
const input = JsonToXlsxInput.parse(raw);
|
|
34
|
+
const result = await jsonToXlsx(input);
|
|
35
|
+
return {
|
|
36
|
+
content: [
|
|
37
|
+
{ type: "text", text: JSON.stringify(result, null, 2) },
|
|
38
|
+
],
|
|
39
|
+
structuredContent: result,
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
return server;
|
|
43
|
+
}
|
|
44
|
+
async function main() {
|
|
45
|
+
const server = createServer();
|
|
46
|
+
const transport = new StdioServerTransport();
|
|
47
|
+
await server.connect(transport);
|
|
48
|
+
}
|
|
49
|
+
const isMain = process.argv[1] !== undefined &&
|
|
50
|
+
(import.meta.url === `file://${process.argv[1]}` ||
|
|
51
|
+
import.meta.url.endsWith(process.argv[1]));
|
|
52
|
+
if (isMain) {
|
|
53
|
+
main().catch((err) => {
|
|
54
|
+
console.error(err);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,eAAe,EACf,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAErD,MAAM,UAAU,YAAY;IAC1B,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,qBAAqB;QAC3B,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;IAEH,MAAM,CAAC,YAAY,CACjB,sBAAsB,EACtB;QACE,KAAK,EAAE,qBAAqB;QAC5B,WAAW,EACT,kRAAkR;QACpR,WAAW,EAAE,eAAe,CAAC,KAAK;QAClC,YAAY,EAAE,gBAAgB,CAAC,KAAK;KACrC,EACD,KAAK,EAAE,GAAG,EAAE,EAAE;QACZ,MAAM,KAAK,GAAG,eAAe,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACzC,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,CAAC;QACvC,OAAO;YACL,OAAO,EAAE;gBACP,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;aACxD;YACD,iBAAiB,EAAE,MAAM;SAC1B,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,sBAAsB,EACtB;QACE,KAAK,EAAE,qBAAqB;QAC5B,WAAW,EACT,uUAAuU;QACzU,WAAW,EAAE,eAAe,CAAC,KAAK;QAClC,YAAY,EAAE,gBAAgB,CAAC,KAAK;KACrC,EACD,KAAK,EAAE,GAAG,EAAE,EAAE;QACZ,MAAM,KAAK,GAAG,eAAe,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACzC,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,CAAC;QACvC,OAAO;YACL,OAAO,EAAE;gBACP,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;aACxD;YACD,iBAAiB,EAAE,MAAM;SAC1B,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;IAC9B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,MAAM,GACV,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,SAAS;IAC7B,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,UAAU,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE;QAC9C,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAE/C,IAAI,MAAM,EAAE,CAAC;IACX,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QACnB,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-to-xlsx.d.ts","sourceRoot":"","sources":["../../src/tools/json-to-xlsx.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE9E,wBAAsB,UAAU,CAC9B,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,gBAAgB,CAAC,CAE3B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-to-xlsx.js","sourceRoot":"","sources":["../../src/tools/json-to-xlsx.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAG5D,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,KAAsB;IAEtB,OAAO,aAAa,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;AACxD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xlsx-to-json.d.ts","sourceRoot":"","sources":["../../src/tools/xlsx-to-json.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE9E,wBAAsB,UAAU,CAC9B,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,gBAAgB,CAAC,CAE3B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xlsx-to-json.js","sourceRoot":"","sources":["../../src/tools/xlsx-to-json.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAG5D,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,KAAsB;IAEtB,OAAO,aAAa,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;AACvC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-workbook.d.ts","sourceRoot":"","sources":["../../src/writer/build-workbook.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,gBAAgB,EAChB,OAAO,EAER,MAAM,sBAAsB,CAAC;AA+B9B,wBAAsB,aAAa,CACjC,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,gBAAgB,CAAC,CA4J3B"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { promises as fs } from "node:fs";
|
|
4
|
+
import ExcelJS from "exceljs";
|
|
5
|
+
import { COLUMN_WIDTHS, DATE_ROW, FIRST_EXERCISE_ROW, HEADER_ROW, LOGO_ANCHOR_TL, LOGO_PIXEL_SIZE, NOTES_COL, WARMUP_ROW, WEEK_REPS_COLS, WEEK_WEIGHT_COLS, } from "./layout.js";
|
|
6
|
+
import { DATE_LABEL, EXERCISE_HEADER, headerPadAboveStyle, headerPadBelowStyle, NOTES_HEADER_CELL, REPS_HEADER, SECTION_LABEL, WARMUP_DAY, WARMUP_DAY_LAST, WEIGHT_HEADER, } from "./styles.js";
|
|
7
|
+
import { groupNumberOf, stampExercise } from "./stamp-exercise.js";
|
|
8
|
+
import { stampSupplementalTemplate } from "./supplemental-template.js";
|
|
9
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const LOGO_PATH = path.resolve(here, "../../src/assets/olympia-logo.png");
|
|
11
|
+
export async function buildWorkbook(program, outputPath) {
|
|
12
|
+
const workbook = new ExcelJS.Workbook();
|
|
13
|
+
workbook.creator = "olympia-ait-adapter";
|
|
14
|
+
workbook.created = new Date(0);
|
|
15
|
+
workbook.modified = new Date(0);
|
|
16
|
+
workbook.title = `${program.client} Programs ${program.programDate}`;
|
|
17
|
+
workbook.subject = program.client;
|
|
18
|
+
workbook.keywords = program.programDate;
|
|
19
|
+
let logoBytes;
|
|
20
|
+
try {
|
|
21
|
+
const raw = await fs.readFile(LOGO_PATH);
|
|
22
|
+
logoBytes = new Uint8Array(raw);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
/* logo optional in dev */
|
|
26
|
+
}
|
|
27
|
+
const imageId = logoBytes !== undefined
|
|
28
|
+
? workbook.addImage({
|
|
29
|
+
buffer: logoBytes,
|
|
30
|
+
extension: "png",
|
|
31
|
+
})
|
|
32
|
+
: undefined;
|
|
33
|
+
for (const day of program.days) {
|
|
34
|
+
const ws = workbook.addWorksheet(day.name);
|
|
35
|
+
// Column widths
|
|
36
|
+
for (const { col, width } of COLUMN_WIDTHS) {
|
|
37
|
+
ws.getColumn(col).width = width;
|
|
38
|
+
}
|
|
39
|
+
// --- Row 2: "Date:" cells + "Notes" header ---
|
|
40
|
+
for (const col of WEEK_REPS_COLS) {
|
|
41
|
+
const c = ws.getCell(`${col}${DATE_ROW}`);
|
|
42
|
+
c.value = "Date:";
|
|
43
|
+
Object.assign(c, DATE_LABEL);
|
|
44
|
+
}
|
|
45
|
+
for (const col of WEEK_WEIGHT_COLS) {
|
|
46
|
+
const c = ws.getCell(`${col}${DATE_ROW}`);
|
|
47
|
+
c.value = "Date:";
|
|
48
|
+
Object.assign(c, DATE_LABEL);
|
|
49
|
+
}
|
|
50
|
+
{
|
|
51
|
+
const cell = ws.getCell(`${NOTES_COL}${DATE_ROW}`);
|
|
52
|
+
cell.value = "Notes";
|
|
53
|
+
Object.assign(cell, NOTES_HEADER_CELL);
|
|
54
|
+
}
|
|
55
|
+
for (let i = 0; i < 6; i++) {
|
|
56
|
+
ws.mergeCells(`${WEEK_REPS_COLS[i]}${DATE_ROW}:${WEEK_WEIGHT_COLS[i]}${DATE_ROW}`);
|
|
57
|
+
}
|
|
58
|
+
// --- Row 3: "Warmup" + "Warm Up #1" labels ---
|
|
59
|
+
{
|
|
60
|
+
const c = ws.getCell(`C${WARMUP_ROW}`);
|
|
61
|
+
c.value = "Warmup";
|
|
62
|
+
Object.assign(c, SECTION_LABEL);
|
|
63
|
+
}
|
|
64
|
+
for (let i = 0; i < 6; i++) {
|
|
65
|
+
const a = WEEK_REPS_COLS[i];
|
|
66
|
+
const b = WEEK_WEIGHT_COLS[i];
|
|
67
|
+
ws.mergeCells(`${a}${WARMUP_ROW}:${b}${WARMUP_ROW}`);
|
|
68
|
+
const c = ws.getCell(`${a}${WARMUP_ROW}`);
|
|
69
|
+
c.value = "Warm Up #1";
|
|
70
|
+
Object.assign(c, i === 5 ? WARMUP_DAY_LAST : WARMUP_DAY);
|
|
71
|
+
}
|
|
72
|
+
// --- Row 4: gray padding above the "Exercise" header ---
|
|
73
|
+
{
|
|
74
|
+
Object.assign(ws.getCell(`C4`), headerPadAboveStyle("C"));
|
|
75
|
+
for (let i = 0; i < 6; i++) {
|
|
76
|
+
Object.assign(ws.getCell(`${WEEK_REPS_COLS[i]}4`), headerPadAboveStyle("reps"));
|
|
77
|
+
Object.assign(ws.getCell(`${WEEK_WEIGHT_COLS[i]}4`), headerPadAboveStyle("weight"));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// --- Row 5: column headers ---
|
|
81
|
+
{
|
|
82
|
+
const c = ws.getCell(`C${HEADER_ROW}`);
|
|
83
|
+
c.value = "Exercise";
|
|
84
|
+
Object.assign(c, EXERCISE_HEADER);
|
|
85
|
+
}
|
|
86
|
+
for (let i = 0; i < 6; i++) {
|
|
87
|
+
const reps = ws.getCell(`${WEEK_REPS_COLS[i]}${HEADER_ROW}`);
|
|
88
|
+
reps.value = "Reps";
|
|
89
|
+
Object.assign(reps, REPS_HEADER);
|
|
90
|
+
const weight = ws.getCell(`${WEEK_WEIGHT_COLS[i]}${HEADER_ROW}`);
|
|
91
|
+
weight.value = "Weight";
|
|
92
|
+
Object.assign(weight, WEIGHT_HEADER);
|
|
93
|
+
}
|
|
94
|
+
// --- Row 6: gray padding below the "Exercise" header ---
|
|
95
|
+
{
|
|
96
|
+
Object.assign(ws.getCell(`C6`), headerPadBelowStyle("C"));
|
|
97
|
+
for (let i = 0; i < 6; i++) {
|
|
98
|
+
Object.assign(ws.getCell(`${WEEK_REPS_COLS[i]}6`), headerPadBelowStyle("reps"));
|
|
99
|
+
Object.assign(ws.getCell(`${WEEK_WEIGHT_COLS[i]}6`), headerPadBelowStyle("weight"));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Week dates (row 2) — override "Date:" labels if program has them.
|
|
103
|
+
if (program.weekDates) {
|
|
104
|
+
for (let i = 0; i < 6; i++) {
|
|
105
|
+
const v = program.weekDates[i];
|
|
106
|
+
if (v) {
|
|
107
|
+
const c = ws.getCell(`${WEEK_REPS_COLS[i]}${DATE_ROW}`);
|
|
108
|
+
c.value = v;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// --- Body: walk sections, stamp exercises with neighbor-aware borders. ---
|
|
113
|
+
let row = FIRST_EXERCISE_ROW;
|
|
114
|
+
for (const section of day.sections) {
|
|
115
|
+
if (section.kind === "sportSpecific" || section.kind === "supplemental") {
|
|
116
|
+
// These section kinds are visualized by the fixed bottom template
|
|
117
|
+
// appended later; skip any user-supplied content (the agent should
|
|
118
|
+
// not be filling these in for now).
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
row = stampSection(ws, row, section);
|
|
122
|
+
}
|
|
123
|
+
// --- Fixed Sport Specific / Supplemental template at the bottom of
|
|
124
|
+
// the sheet, with any freeform notes the trainer/agent supplied. ---
|
|
125
|
+
row = stampSupplementalTemplate(ws, row, day.supplementalNotes);
|
|
126
|
+
// Olympia logo: one-cell anchor with explicit pixel dimensions so the
|
|
127
|
+
// image keeps its native aspect ratio (a two-cell anchor stretches the
|
|
128
|
+
// image to fit the cell range, which distorts the logo vertically).
|
|
129
|
+
if (imageId !== undefined) {
|
|
130
|
+
ws.addImage(imageId, {
|
|
131
|
+
tl: { col: LOGO_ANCHOR_TL.col, row: LOGO_ANCHOR_TL.row },
|
|
132
|
+
ext: { width: LOGO_PIXEL_SIZE.width, height: LOGO_PIXEL_SIZE.height },
|
|
133
|
+
editAs: "oneCell",
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
await workbook.xlsx.writeFile(outputPath);
|
|
138
|
+
const stat = await fs.stat(outputPath);
|
|
139
|
+
return { outputPath, bytesWritten: stat.size };
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Walk a section's exercises, computing each block's top/bottom border
|
|
143
|
+
* based on neighbor group numbers:
|
|
144
|
+
* - first block in section: top MEDIUM
|
|
145
|
+
* - block whose previous neighbor shares the group number: top HAIR
|
|
146
|
+
* - block whose previous neighbor has a different group number: top MEDIUM
|
|
147
|
+
* - last block in section: group-label bottom MEDIUM
|
|
148
|
+
* - non-last block: group-label bottom NONE (the next block's top provides
|
|
149
|
+
* the visual separator)
|
|
150
|
+
*/
|
|
151
|
+
function stampSection(ws, startRow, section) {
|
|
152
|
+
let row = startRow;
|
|
153
|
+
for (let i = 0; i < section.exercises.length; i++) {
|
|
154
|
+
const ex = section.exercises[i];
|
|
155
|
+
const prev = i > 0 ? section.exercises[i - 1] : undefined;
|
|
156
|
+
const prevGroup = groupNumberOf(prev?.groupLabel);
|
|
157
|
+
const myGroup = groupNumberOf(ex.groupLabel);
|
|
158
|
+
const topBorder = prev !== undefined && prevGroup !== undefined && prevGroup === myGroup
|
|
159
|
+
? "hair"
|
|
160
|
+
: "medium";
|
|
161
|
+
const bottomBorder = i === section.exercises.length - 1 ? "medium" : "none";
|
|
162
|
+
row = stampExercise(ws, row, ex, { topBorder, bottomBorder });
|
|
163
|
+
}
|
|
164
|
+
return row;
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=build-workbook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-workbook.js","sourceRoot":"","sources":["../../src/writer/build-workbook.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare const COLUMN_WIDTHS: {
|
|
2
|
+
col: string;
|
|
3
|
+
width: number;
|
|
4
|
+
}[];
|
|
5
|
+
export declare const DATE_ROW = 2;
|
|
6
|
+
export declare const WARMUP_ROW = 3;
|
|
7
|
+
export declare const HEADER_ROW = 5;
|
|
8
|
+
export declare const FIRST_EXERCISE_ROW = 7;
|
|
9
|
+
export declare const WEEK_REPS_COLS: readonly ["D", "F", "H", "J", "L", "N"];
|
|
10
|
+
export declare const WEEK_WEIGHT_COLS: readonly ["E", "G", "I", "K", "M", "O"];
|
|
11
|
+
export declare const NOTES_COL = "P";
|
|
12
|
+
export declare const EXERCISE_NAME_COL = "C";
|
|
13
|
+
export declare const LOGO_ANCHOR_TL: {
|
|
14
|
+
readonly col: 5;
|
|
15
|
+
readonly row: 32;
|
|
16
|
+
};
|
|
17
|
+
export declare const LOGO_PIXEL_SIZE: {
|
|
18
|
+
readonly width: 301;
|
|
19
|
+
readonly height: 70;
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=layout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../src/writer/layout.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,aAAa,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAoBzD,CAAC;AAEF,eAAO,MAAM,QAAQ,IAAI,CAAC;AAC1B,eAAO,MAAM,UAAU,IAAI,CAAC;AAC5B,eAAO,MAAM,UAAU,IAAI,CAAC;AAC5B,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAGpC,eAAO,MAAM,cAAc,yCAA0C,CAAC;AACtE,eAAO,MAAM,gBAAgB,yCAA0C,CAAC;AACxE,eAAO,MAAM,SAAS,MAAM,CAAC;AAC7B,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAOrC,eAAO,MAAM,cAAc;;;CAA+B,CAAC;AAC3D,eAAO,MAAM,eAAe;;;CAAsC,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Column widths and row positions extracted from the sample Olympia workbooks.
|
|
2
|
+
// These are constants so output is byte-stable.
|
|
3
|
+
export const COLUMN_WIDTHS = [
|
|
4
|
+
{ col: "A", width: 0.66 },
|
|
5
|
+
{ col: "B", width: 0.66 },
|
|
6
|
+
{ col: "C", width: 25.83 },
|
|
7
|
+
{ col: "D", width: 7.66 },
|
|
8
|
+
{ col: "E", width: 8.83 },
|
|
9
|
+
{ col: "F", width: 7.66 },
|
|
10
|
+
{ col: "G", width: 7.83 },
|
|
11
|
+
{ col: "H", width: 7.66 },
|
|
12
|
+
{ col: "I", width: 8.0 },
|
|
13
|
+
{ col: "J", width: 7.66 },
|
|
14
|
+
{ col: "K", width: 8.66 },
|
|
15
|
+
{ col: "L", width: 7.66 },
|
|
16
|
+
{ col: "M", width: 9.33 },
|
|
17
|
+
{ col: "N", width: 7.66 },
|
|
18
|
+
{ col: "O", width: 8.33 },
|
|
19
|
+
// Notes column — widened from the source's 10.83 so coaching cues fit
|
|
20
|
+
// without wrapping aggressively.
|
|
21
|
+
{ col: "P", width: 18 },
|
|
22
|
+
{ col: "Q", width: 10.5 },
|
|
23
|
+
];
|
|
24
|
+
export const DATE_ROW = 2;
|
|
25
|
+
export const WARMUP_ROW = 3;
|
|
26
|
+
export const HEADER_ROW = 5;
|
|
27
|
+
export const FIRST_EXERCISE_ROW = 7;
|
|
28
|
+
// Per-week column pairs: D/E = week 1 reps/weight, F/G = week 2, etc.
|
|
29
|
+
export const WEEK_REPS_COLS = ["D", "F", "H", "J", "L", "N"];
|
|
30
|
+
export const WEEK_WEIGHT_COLS = ["E", "G", "I", "K", "M", "O"];
|
|
31
|
+
export const NOTES_COL = "P";
|
|
32
|
+
export const EXERCISE_NAME_COL = "C";
|
|
33
|
+
// Olympia logo anchor. We use a one-cell anchor with explicit pixel
|
|
34
|
+
// dimensions (rather than a two-cell anchor) so the image keeps its native
|
|
35
|
+
// 693:160 aspect ratio regardless of column/row sizes. The native PNG is
|
|
36
|
+
// 693 × 160 (4.33:1); we render at a smaller pixel size so the logo fits
|
|
37
|
+
// under the supplemental template without overlapping it.
|
|
38
|
+
export const LOGO_ANCHOR_TL = { col: 5, row: 32 };
|
|
39
|
+
export const LOGO_PIXEL_SIZE = { width: 301, height: 70 };
|
|
40
|
+
//# sourceMappingURL=layout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layout.js","sourceRoot":"","sources":["../../src/writer/layout.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,gDAAgD;AAEhD,MAAM,CAAC,MAAM,aAAa,GAAqC;IAC7D,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE;IACzB,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE;IACzB,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE;IAC1B,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE;IACzB,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE;IACzB,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE;IACzB,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE;IACzB,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE;IACzB,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE;IACxB,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE;IACzB,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE;IACzB,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE;IACzB,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE;IACzB,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE;IACzB,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE;IACzB,sEAAsE;IACtE,iCAAiC;IACjC,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE;IACvB,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE;CAC1B,CAAC;AAEF,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,CAAC;AAC1B,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC;AAC5B,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC;AAC5B,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC;AAEpC,sEAAsE;AACtE,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAU,CAAC;AACtE,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAU,CAAC;AACxE,MAAM,CAAC,MAAM,SAAS,GAAG,GAAG,CAAC;AAC7B,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAErC,oEAAoE;AACpE,2EAA2E;AAC3E,yEAAyE;AACzE,yEAAyE;AACzE,0DAA0D;AAC1D,MAAM,CAAC,MAAM,cAAc,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAW,CAAC;AAC3D,MAAM,CAAC,MAAM,eAAe,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAW,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type ExcelJS from "exceljs";
|
|
2
|
+
import type { Exercise } from "../schema/program.js";
|
|
3
|
+
import { type CTopBorder, type CBottomBorder } from "./styles.js";
|
|
4
|
+
export interface StampOptions {
|
|
5
|
+
/** Border style for the top of the exercise-name (column C) cell. */
|
|
6
|
+
topBorder: CTopBorder;
|
|
7
|
+
/**
|
|
8
|
+
* Border style for the bottom of the group-label (column C) cell. Use
|
|
9
|
+
* "medium" only on the last exercise of a section; otherwise "none".
|
|
10
|
+
*/
|
|
11
|
+
bottomBorder: CBottomBorder;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Stamps one exercise block onto the worksheet starting at `startRow`.
|
|
15
|
+
*
|
|
16
|
+
* Layout rules (N = exercise.sets.length):
|
|
17
|
+
* - With groupLabel: name occupies the first N−1 rows of col C (merged
|
|
18
|
+
* when N > 2); group label sits in col C of the last row.
|
|
19
|
+
* - Without groupLabel: name occupies all N rows of col C (merged when
|
|
20
|
+
* N > 1); no group-label row.
|
|
21
|
+
*
|
|
22
|
+
* Borders on column C (the gray bar) are driven by `topBorder` and
|
|
23
|
+
* `bottomBorder` so the caller can express "this block is the first/last
|
|
24
|
+
* of its section" and "this block starts a new group vs. continues one".
|
|
25
|
+
*
|
|
26
|
+
* @returns the next free row after the block.
|
|
27
|
+
*/
|
|
28
|
+
export declare function stampExercise(ws: ExcelJS.Worksheet, startRow: number, exercise: Exercise, opts: StampOptions): number;
|
|
29
|
+
/** Extracts the leading numeric portion of a group label ("1A" → "1"). */
|
|
30
|
+
export declare function groupNumberOf(label: string | undefined): string | undefined;
|
|
31
|
+
//# sourceMappingURL=stamp-exercise.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stamp-exercise.d.ts","sourceRoot":"","sources":["../../src/writer/stamp-exercise.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAOrD,OAAO,EAML,KAAK,UAAU,EACf,KAAK,aAAa,EACnB,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,SAAS,EAAE,UAAU,CAAC;IACtB;;;OAGG;IACH,YAAY,EAAE,aAAa,CAAC;CAC7B;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAC3B,EAAE,EAAE,OAAO,CAAC,SAAS,EACrB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,YAAY,GACjB,MAAM,CAiER;AAED,0EAA0E;AAC1E,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAI3E"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { EXERCISE_NAME_COL, NOTES_COL, WEEK_REPS_COLS, WEEK_WEIGHT_COLS, } from "./layout.js";
|
|
2
|
+
import { exerciseNameStyle, groupLabelStyle, NOTES_CELL, weekRepsCellStyle, weekWeightCellStyle, } from "./styles.js";
|
|
3
|
+
/**
|
|
4
|
+
* Stamps one exercise block onto the worksheet starting at `startRow`.
|
|
5
|
+
*
|
|
6
|
+
* Layout rules (N = exercise.sets.length):
|
|
7
|
+
* - With groupLabel: name occupies the first N−1 rows of col C (merged
|
|
8
|
+
* when N > 2); group label sits in col C of the last row.
|
|
9
|
+
* - Without groupLabel: name occupies all N rows of col C (merged when
|
|
10
|
+
* N > 1); no group-label row.
|
|
11
|
+
*
|
|
12
|
+
* Borders on column C (the gray bar) are driven by `topBorder` and
|
|
13
|
+
* `bottomBorder` so the caller can express "this block is the first/last
|
|
14
|
+
* of its section" and "this block starts a new group vs. continues one".
|
|
15
|
+
*
|
|
16
|
+
* @returns the next free row after the block.
|
|
17
|
+
*/
|
|
18
|
+
export function stampExercise(ws, startRow, exercise, opts) {
|
|
19
|
+
const setCount = exercise.sets.length;
|
|
20
|
+
const hasGroupLabel = exercise.groupLabel !== undefined;
|
|
21
|
+
const lastBlockRow = startRow + setCount - 1;
|
|
22
|
+
// Where the exercise-name region ends.
|
|
23
|
+
const nameLastRow = hasGroupLabel && setCount > 1 ? lastBlockRow - 1 : lastBlockRow;
|
|
24
|
+
if (nameLastRow > startRow) {
|
|
25
|
+
ws.mergeCells(`${EXERCISE_NAME_COL}${startRow}:${EXERCISE_NAME_COL}${nameLastRow}`);
|
|
26
|
+
}
|
|
27
|
+
const nameCell = ws.getCell(`${EXERCISE_NAME_COL}${startRow}`);
|
|
28
|
+
nameCell.value = exercise.name;
|
|
29
|
+
Object.assign(nameCell, exerciseNameStyle(opts.topBorder));
|
|
30
|
+
if (hasGroupLabel && setCount > 1) {
|
|
31
|
+
const groupCell = ws.getCell(`${EXERCISE_NAME_COL}${lastBlockRow}`);
|
|
32
|
+
groupCell.value = exercise.groupLabel;
|
|
33
|
+
Object.assign(groupCell, groupLabelStyle(opts.bottomBorder));
|
|
34
|
+
}
|
|
35
|
+
else if (!hasGroupLabel && opts.bottomBorder === "medium") {
|
|
36
|
+
// No group label but we still need a bottom border on the merged name
|
|
37
|
+
// cell. Apply both the top and bottom variants by composing.
|
|
38
|
+
const composed = exerciseNameStyle(opts.topBorder);
|
|
39
|
+
// The merged name cell occupies all N rows; its border 'bottom' is what
|
|
40
|
+
// closes the section. Re-apply with bottom merged in.
|
|
41
|
+
composed.border = {
|
|
42
|
+
...composed.border,
|
|
43
|
+
bottom: { style: "medium", color: { indexed: 64 } },
|
|
44
|
+
};
|
|
45
|
+
Object.assign(nameCell, composed);
|
|
46
|
+
}
|
|
47
|
+
// Notes column P: merge across the whole block when there's more than one row.
|
|
48
|
+
if (setCount > 1) {
|
|
49
|
+
ws.mergeCells(`${NOTES_COL}${startRow}:${NOTES_COL}${lastBlockRow}`);
|
|
50
|
+
}
|
|
51
|
+
const noteCell = ws.getCell(`${NOTES_COL}${startRow}`);
|
|
52
|
+
noteCell.value = exercise.notes ?? "";
|
|
53
|
+
Object.assign(noteCell, NOTES_CELL);
|
|
54
|
+
// Per-set per-week cells
|
|
55
|
+
for (let setIdx = 0; setIdx < setCount; setIdx++) {
|
|
56
|
+
const r = startRow + setIdx;
|
|
57
|
+
const isTop = setIdx === 0;
|
|
58
|
+
const isBottom = setIdx === setCount - 1;
|
|
59
|
+
const setRow = exercise.sets[setIdx];
|
|
60
|
+
for (let weekIdx = 0; weekIdx < 6; weekIdx++) {
|
|
61
|
+
const repsCol = WEEK_REPS_COLS[weekIdx];
|
|
62
|
+
const weightCol = WEEK_WEIGHT_COLS[weekIdx];
|
|
63
|
+
const repsCell = ws.getCell(`${repsCol}${r}`);
|
|
64
|
+
repsCell.value = setRow[weekIdx] ?? null;
|
|
65
|
+
Object.assign(repsCell, weekRepsCellStyle({ isTop, isBottom }));
|
|
66
|
+
const weightCell = ws.getCell(`${weightCol}${r}`);
|
|
67
|
+
weightCell.value = null;
|
|
68
|
+
Object.assign(weightCell, weekWeightCellStyle({ isTop, isBottom }));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return lastBlockRow + 1;
|
|
72
|
+
}
|
|
73
|
+
/** Extracts the leading numeric portion of a group label ("1A" → "1"). */
|
|
74
|
+
export function groupNumberOf(label) {
|
|
75
|
+
if (!label)
|
|
76
|
+
return undefined;
|
|
77
|
+
const m = label.match(/^(\d+)/);
|
|
78
|
+
return m ? m[1] : undefined;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=stamp-exercise.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stamp-exercise.js","sourceRoot":"","sources":["../../src/writer/stamp-exercise.ts"],"names":[],"mappings":"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"}
|