@tekkare/romulus 0.1.239 → 0.1.240
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/dist/module.json +1 -1
- package/dist/runtime/components/ChartCard.vue +5 -5
- package/dist/runtime/components/TagRow.vue +1 -1
- package/dist/runtime/composables/useRmCardActions.js +2 -2
- package/dist/runtime/composables/useRmExport.js +82 -8
- package/dist/runtime/utils/cardData.d.ts +7 -1
- package/dist/runtime/utils/cardData.js +9 -0
- package/dist/runtime/utils/export.d.ts +16 -0
- package/dist/runtime/utils/export.js +22 -0
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -125,11 +125,11 @@ const t = computed(() => WORDS[props.lang])
|
|
|
125
125
|
*/
|
|
126
126
|
const ISO_DATE = /^\d{4}-\d{2}-\d{2}/
|
|
127
127
|
|
|
128
|
-
/**
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
})
|
|
128
|
+
/**
|
|
129
|
+
* Nom des exports : le titre et sa phrase de contexte. Les etiquettes n'y
|
|
130
|
+
* entrent pas, les exports les redessinent sous le titre (cf. `rmCardTags`).
|
|
131
|
+
*/
|
|
132
|
+
const exportName = computed(() => (props.context ? `${props.title} (${props.context})` : props.title))
|
|
133
133
|
|
|
134
134
|
const verifiedText = computed(() => {
|
|
135
135
|
if (typeof props.verified === 'string' && !ISO_DATE.test(props.verified)) return props.verified
|
|
@@ -80,7 +80,7 @@ watch(() => props.tags, measure, { deep: true })
|
|
|
80
80
|
</script>
|
|
81
81
|
|
|
82
82
|
<template>
|
|
83
|
-
<div class="rm-tag-row">
|
|
83
|
+
<div class="rm-tag-row" :data-rm-tags="JSON.stringify(items)">
|
|
84
84
|
<div ref="rowRef" class="rm-tag-row__items">
|
|
85
85
|
<RmTag v-for="tag in shown" :key="tag.label" data-rm-tag :variant="tag.variant">{{ tag.label }}</RmTag>
|
|
86
86
|
</div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { rmCardCharts, rmCardSheets, rmCardTitle } from "../utils/cardData.js";
|
|
1
|
+
import { rmCardCharts, rmCardSheets, rmCardTags, rmCardTitle } from "../utils/cardData.js";
|
|
2
2
|
import { useRmExport } from "./useRmExport.js";
|
|
3
3
|
import { rmExportDefaults } from "./useRmExportDefaults.js";
|
|
4
4
|
const FILE_NAME_MAX = 60;
|
|
@@ -51,7 +51,7 @@ export function useRmCardActions() {
|
|
|
51
51
|
return run(configOf(format, fileNameOf(title)), {
|
|
52
52
|
...defaults,
|
|
53
53
|
title,
|
|
54
|
-
charts: rmCardCharts(card).map((el) => ({ title, el })),
|
|
54
|
+
charts: rmCardCharts(card).map((el) => ({ title, el, tags: rmCardTags(card) })),
|
|
55
55
|
// Le lien de la carte, sinon celui du cadre : le permalien de la page
|
|
56
56
|
// porte la vue et les filtres, la ou l'URL courante ne dit rien de ce
|
|
57
57
|
// qui est affiche.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RM_EXPORT_EXTENSIONS } from "../utils/export.js";
|
|
1
|
+
import { RM_EXPORT_EXTENSIONS, rmExportTagColor } from "../utils/export.js";
|
|
2
2
|
const BRAND_TITLE = "OIP Healthcare";
|
|
3
3
|
const BRAND_LINE = "By Tekkare \u{1F49C}";
|
|
4
4
|
const COVER_SHEET = "Tekkare";
|
|
@@ -92,7 +92,28 @@ function titleGap(title) {
|
|
|
92
92
|
return title ? 22 : 0;
|
|
93
93
|
}
|
|
94
94
|
function drawableCharts(payload) {
|
|
95
|
-
return (payload.charts ?? []).map((chart) => ({ title: chart.title, svg: svgOf(chart.el) })).filter((chart) => !!chart.svg);
|
|
95
|
+
return (payload.charts ?? []).map((chart) => ({ title: chart.title, tags: chart.tags, svg: svgOf(chart.el) })).filter((chart) => !!chart.svg);
|
|
96
|
+
}
|
|
97
|
+
const TAG_FONT = "500 12px Figtree, Helvetica, sans-serif";
|
|
98
|
+
const TAG_HEIGHT = 22;
|
|
99
|
+
const TAG_SPACE = 6;
|
|
100
|
+
function layoutTags(tags, width) {
|
|
101
|
+
if (!tags?.length) return { placed: [], height: 0 };
|
|
102
|
+
const measure = document.createElement("canvas").getContext("2d");
|
|
103
|
+
measure.font = TAG_FONT;
|
|
104
|
+
let x = 0;
|
|
105
|
+
let row = 0;
|
|
106
|
+
const placed = tags.map((tag) => {
|
|
107
|
+
const w = Math.ceil(measure.measureText(tag.label).width) + 16;
|
|
108
|
+
if (x > 0 && x + w > width) {
|
|
109
|
+
x = 0;
|
|
110
|
+
row++;
|
|
111
|
+
}
|
|
112
|
+
const at = { tag, x, row, w };
|
|
113
|
+
x += w + TAG_SPACE;
|
|
114
|
+
return at;
|
|
115
|
+
});
|
|
116
|
+
return { placed, height: (row + 1) * (TAG_HEIGHT + TAG_SPACE) + 4 };
|
|
96
117
|
}
|
|
97
118
|
function svgOf(el) {
|
|
98
119
|
return el instanceof SVGSVGElement ? el : el.querySelector("svg");
|
|
@@ -344,9 +365,12 @@ export function useRmExport() {
|
|
|
344
365
|
const pad = 24;
|
|
345
366
|
const sources = sourcesOf(config, payload);
|
|
346
367
|
const head = config.options.includes("logo") ? sources.length ? 88 : 72 : 0;
|
|
347
|
-
const parts = drawn.map((chart) =>
|
|
368
|
+
const parts = drawn.map((chart) => {
|
|
369
|
+
const part = serialize(chart.svg);
|
|
370
|
+
return { ...part, title: chart.title, tags: layoutTags(chart.tags, part.width) };
|
|
371
|
+
});
|
|
348
372
|
const width = Math.max(...parts.map((part) => part.width)) + pad * 2;
|
|
349
|
-
const height = parts.reduce((total, part) => total + part.height + pad + titleGap(part.title), pad) + head;
|
|
373
|
+
const height = parts.reduce((total, part) => total + part.height + pad + titleGap(part.title) + part.tags.height, pad) + head;
|
|
350
374
|
const canvas = document.createElement("canvas");
|
|
351
375
|
canvas.width = width * scale;
|
|
352
376
|
canvas.height = height * scale;
|
|
@@ -373,6 +397,18 @@ export function useRmExport() {
|
|
|
373
397
|
context.fillText(part.title, pad, y + 12);
|
|
374
398
|
y += titleGap(part.title);
|
|
375
399
|
}
|
|
400
|
+
context.font = TAG_FONT;
|
|
401
|
+
for (const { tag, x, row, w } of part.tags.placed) {
|
|
402
|
+
const { fill, text } = rmExportTagColor(tag.variant);
|
|
403
|
+
const top = y + row * (TAG_HEIGHT + TAG_SPACE);
|
|
404
|
+
context.fillStyle = `rgb(${fill.join(",")})`;
|
|
405
|
+
context.beginPath();
|
|
406
|
+
context.roundRect(pad + x, top, w, TAG_HEIGHT, 4);
|
|
407
|
+
context.fill();
|
|
408
|
+
context.fillStyle = text;
|
|
409
|
+
context.fillText(tag.label, pad + x + 8, top + 15);
|
|
410
|
+
}
|
|
411
|
+
y += part.tags.height;
|
|
376
412
|
const raw = await raster(part.markup, part.width, part.height, scale);
|
|
377
413
|
context.drawImage(raw, pad, y, part.width, part.height);
|
|
378
414
|
y += part.height + pad;
|
|
@@ -389,16 +425,25 @@ export function useRmExport() {
|
|
|
389
425
|
const pad = 24;
|
|
390
426
|
const sources = sourcesOf(config, payload);
|
|
391
427
|
const head = config.options.includes("logo") ? sources.length ? 88 : 72 : 0;
|
|
392
|
-
const parts = drawn.map((chart) =>
|
|
428
|
+
const parts = drawn.map((chart) => {
|
|
429
|
+
const part = serialize(chart.svg);
|
|
430
|
+
return { ...part, title: chart.title, tags: layoutTags(chart.tags, part.width) };
|
|
431
|
+
});
|
|
393
432
|
const width = Math.max(...parts.map((part) => part.width)) + pad * 2;
|
|
394
|
-
const height = parts.reduce((total, part) => total + part.height + pad + titleGap(part.title), pad) + head;
|
|
433
|
+
const height = parts.reduce((total, part) => total + part.height + pad + titleGap(part.title) + part.tags.height, pad) + head;
|
|
395
434
|
let y = head + pad;
|
|
396
435
|
const nested = parts.map((part) => {
|
|
397
436
|
const label = part.title ? `<text x="${pad}" y="${y + 12}" font-family="Figtree, Helvetica, sans-serif" font-size="14" font-weight="bold" fill="#1A1A2E">${escapeXml(part.title)}</text>` : "";
|
|
398
437
|
y += titleGap(part.title);
|
|
438
|
+
const tags = part.tags.placed.map(({ tag, x, row, w }) => {
|
|
439
|
+
const { fill, text } = rmExportTagColor(tag.variant);
|
|
440
|
+
const top = y + row * (TAG_HEIGHT + TAG_SPACE);
|
|
441
|
+
return `<rect x="${pad + x}" y="${top}" width="${w}" height="${TAG_HEIGHT}" rx="4" fill="rgb(${fill.join(",")})"/><text x="${pad + x + 8}" y="${top + 15}" font-family="Figtree, Helvetica, sans-serif" font-size="12" font-weight="500" fill="${text}">${escapeXml(tag.label)}</text>`;
|
|
442
|
+
}).join("");
|
|
443
|
+
y += part.tags.height;
|
|
399
444
|
const markup2 = part.markup.replace(/^<svg /, `<svg x="${pad}" y="${y}" `);
|
|
400
445
|
y += part.height + pad;
|
|
401
|
-
return label + markup2;
|
|
446
|
+
return label + tags + markup2;
|
|
402
447
|
}).join("\n");
|
|
403
448
|
const header = head ? `<text x="${pad}" y="32" font-family="Figtree, Helvetica, sans-serif" font-size="20" font-weight="bold" fill="#1A1A2E">${BRAND_TITLE}</text><text x="${pad}" y="52" font-family="Figtree, Helvetica, sans-serif" font-size="12" fill="#6C6C8A">${escapeXml(`${payload.title} \xB7 ${BRAND_LINE} \xB7 ${longDate(lang)}`)}</text>` + (sources.length ? `<text x="${pad}" y="68" font-family="Figtree, Helvetica, sans-serif" font-size="12" fill="#6C6C8A">${escapeXml(`Source${sources.length > 1 ? "s" : ""} : ${sources.map((source) => sourceLabel(source, lang)).join(" \xB7 ")}`)}</text>` : "") : "";
|
|
404
449
|
const markup = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}"><rect width="${width}" height="${height}" fill="#FFFFFF"/>${header}${nested}</svg>`;
|
|
@@ -566,7 +611,23 @@ export function useRmExport() {
|
|
|
566
611
|
drawWidth = part.width / part.height * drawHeight;
|
|
567
612
|
}
|
|
568
613
|
const titleHeight = chart.title ? 7 : 0;
|
|
569
|
-
|
|
614
|
+
doc.setFont("helvetica", "normal");
|
|
615
|
+
doc.setFontSize(8);
|
|
616
|
+
let tagX = 0;
|
|
617
|
+
let tagRow = 0;
|
|
618
|
+
const tagsPlaced = (chart.tags ?? []).map((tag) => {
|
|
619
|
+
const label = printable(tag.label);
|
|
620
|
+
const w = doc.getTextWidth(label) + 3.2;
|
|
621
|
+
if (tagX > 0 && tagX + w > inner - 10) {
|
|
622
|
+
tagX = 0;
|
|
623
|
+
tagRow++;
|
|
624
|
+
}
|
|
625
|
+
const at = { tag, label, x: tagX, row: tagRow, w };
|
|
626
|
+
tagX += w + 1.5;
|
|
627
|
+
return at;
|
|
628
|
+
});
|
|
629
|
+
const tagsHeight = tagsPlaced.length ? (tagRow + 1) * 6.5 + 1 : 0;
|
|
630
|
+
const cardHeight = titleHeight + tagsHeight + drawHeight + 8;
|
|
570
631
|
if (y + cardHeight > bottom) {
|
|
571
632
|
doc.addPage();
|
|
572
633
|
y = margin;
|
|
@@ -583,6 +644,19 @@ export function useRmExport() {
|
|
|
583
644
|
doc.text(printable(chart.title), margin + 5, chartY + 2);
|
|
584
645
|
chartY += titleHeight;
|
|
585
646
|
}
|
|
647
|
+
if (tagsPlaced.length) {
|
|
648
|
+
doc.setFont("helvetica", "normal");
|
|
649
|
+
doc.setFontSize(8);
|
|
650
|
+
for (const { tag, label, x, row, w } of tagsPlaced) {
|
|
651
|
+
const { fill, text } = rmExportTagColor(tag.variant);
|
|
652
|
+
const top = chartY - 1 + row * 6.5;
|
|
653
|
+
doc.setFillColor(...fill);
|
|
654
|
+
doc.roundedRect(margin + 5 + x, top, w, 5, 1, 1, "F");
|
|
655
|
+
doc.setTextColor(text);
|
|
656
|
+
doc.text(label, margin + 5 + x + 1.6, top + 3.5);
|
|
657
|
+
}
|
|
658
|
+
chartY += tagsHeight;
|
|
659
|
+
}
|
|
586
660
|
doc.addImage(
|
|
587
661
|
canvas.toDataURL("image/png"),
|
|
588
662
|
"PNG",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* - un graphe dessine dans une cellule (courbe de tendance d'une ligne) est
|
|
22
22
|
* une decoration du tableau : il ne fait pas un export a lui seul.
|
|
23
23
|
*/
|
|
24
|
-
import type { RmExportSheet } from './export.js';
|
|
24
|
+
import type { RmExportSheet, RmExportTag } from './export.js';
|
|
25
25
|
/** La carte qui contient un noeud, quel que soit le composant qui la rend. */
|
|
26
26
|
export declare function rmCardOf(el: Element | null): HTMLElement | null;
|
|
27
27
|
/**
|
|
@@ -30,6 +30,12 @@ export declare function rmCardOf(el: Element | null): HTMLElement | null;
|
|
|
30
30
|
* pareil ne se distinguent plus dans un dossier de telechargement.
|
|
31
31
|
*/
|
|
32
32
|
export declare function rmCardTitle(card: Element): string;
|
|
33
|
+
/**
|
|
34
|
+
* Etiquettes de perimetre de la carte (RmTagRow sous son titre), toutes, y
|
|
35
|
+
* compris celles que la rangee replie en « +N » faute de place : la rangee
|
|
36
|
+
* les porte en entier dans `data-rm-tags`.
|
|
37
|
+
*/
|
|
38
|
+
export declare function rmCardTags(card: Element): RmExportTag[];
|
|
33
39
|
/** Les graphes exportables d'une carte, vides tant que la donnee n'est pas rendue. */
|
|
34
40
|
export declare function rmCardCharts(card: Element): HTMLElement[];
|
|
35
41
|
/**
|
|
@@ -16,6 +16,15 @@ export function rmCardTitle(card) {
|
|
|
16
16
|
const context = cellText(card.querySelector(".chart-card-context"));
|
|
17
17
|
return context ? `${title} (${context})` : title;
|
|
18
18
|
}
|
|
19
|
+
export function rmCardTags(card) {
|
|
20
|
+
const row = card.querySelector(".chart-card-head [data-rm-tags], .rm-table-card__header [data-rm-tags]");
|
|
21
|
+
try {
|
|
22
|
+
const tags = JSON.parse(row?.getAttribute("data-rm-tags") ?? "[]");
|
|
23
|
+
return Array.isArray(tags) ? tags : [];
|
|
24
|
+
} catch {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
19
28
|
export function rmCardCharts(card) {
|
|
20
29
|
return [...card.querySelectorAll(CHART_SELECTOR)].filter(
|
|
21
30
|
(holder) => holder.querySelector("svg") && !holder.closest("table")
|
|
@@ -263,6 +263,7 @@ export interface RmExportPayload {
|
|
|
263
263
|
charts?: {
|
|
264
264
|
title?: string;
|
|
265
265
|
el: Element;
|
|
266
|
+
tags?: RmExportTag[];
|
|
266
267
|
}[];
|
|
267
268
|
/**
|
|
268
269
|
* Cartes de l'ecran, a capturer telles quelles pour le rapport mis en page.
|
|
@@ -329,3 +330,18 @@ export declare function rmExportDuration(kb: number, rows: number): string;
|
|
|
329
330
|
* dossier de telechargements.
|
|
330
331
|
*/
|
|
331
332
|
export declare function rmExportFileName(...parts: (string | undefined | null)[]): string;
|
|
333
|
+
/**
|
|
334
|
+
* Etiquette de perimetre d'un graphe (element, mesure, periode, lieu), posee
|
|
335
|
+
* sous son titre a l'ecran par RmTagRow. Les exports la redessinent en
|
|
336
|
+
* pastille de la meme couleur : ecrite en texte a la suite du titre, elle
|
|
337
|
+
* donnait un intitule de deux lignes ou plus rien ne se distinguait.
|
|
338
|
+
*/
|
|
339
|
+
export interface RmExportTag {
|
|
340
|
+
label: string;
|
|
341
|
+
/** Variante de RmTag ; `gray` a defaut. */
|
|
342
|
+
variant?: string;
|
|
343
|
+
}
|
|
344
|
+
export declare function rmExportTagColor(variant?: string): {
|
|
345
|
+
fill: [number, number, number];
|
|
346
|
+
text: string;
|
|
347
|
+
};
|
|
@@ -101,3 +101,25 @@ export function rmExportFileName(...parts) {
|
|
|
101
101
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
102
102
|
return `${slug || "export"}-${today}`;
|
|
103
103
|
}
|
|
104
|
+
const RM_EXPORT_TAG_COLORS = {
|
|
105
|
+
gray: { fill: [244, 244, 247], text: "#1A1A2E" },
|
|
106
|
+
rose: { fill: [254, 236, 239], text: "#9f1239" },
|
|
107
|
+
pink: { fill: [253, 237, 245], text: "#9d174d" },
|
|
108
|
+
fushia: { fill: [251, 236, 253], text: "#86198f" },
|
|
109
|
+
purple: { fill: [246, 238, 254], text: "#6b21a8" },
|
|
110
|
+
violet: { fill: [243, 239, 254], text: "#5b21b6" },
|
|
111
|
+
indigo: { fill: [239, 240, 254], text: "#3730a3" },
|
|
112
|
+
blue: { fill: [235, 242, 254], text: "#1e40af" },
|
|
113
|
+
sky: { fill: [231, 246, 253], text: "#075985" },
|
|
114
|
+
cyan: { fill: [230, 248, 251], text: "#155e75" },
|
|
115
|
+
teal: { fill: [232, 248, 246], text: "#115e59" },
|
|
116
|
+
emerald: { fill: [231, 248, 242], text: "#065f46" },
|
|
117
|
+
green: { fill: [233, 249, 239], text: "#166534" },
|
|
118
|
+
lime: { fill: [243, 250, 232], text: "#3f6212" },
|
|
119
|
+
yellow: { fill: [253, 247, 230], text: "#854d0e" },
|
|
120
|
+
amber: { fill: [254, 245, 231], text: "#92400e" },
|
|
121
|
+
orange: { fill: [254, 241, 232], text: "#9a3412" }
|
|
122
|
+
};
|
|
123
|
+
export function rmExportTagColor(variant) {
|
|
124
|
+
return RM_EXPORT_TAG_COLORS[variant ?? "gray"] ?? RM_EXPORT_TAG_COLORS.gray;
|
|
125
|
+
}
|