Add goal tree toolbox module
All checks were successful
Deploy Sokko G / deploy (push) Successful in 15s
All checks were successful
Deploy Sokko G / deploy (push) Successful in 15s
This commit is contained in:
parent
e4cbe3e087
commit
59fdcb0927
63 changed files with 4062 additions and 252 deletions
|
|
@ -30,6 +30,7 @@ const SUPPORTED_TEXT_EXCHANGE_TYPES = new Set([
|
|||
"timer",
|
||||
"calendar",
|
||||
"taskPlanner",
|
||||
"goalTree",
|
||||
"equipmentPlanner",
|
||||
"images",
|
||||
"imageAnnotation"
|
||||
|
|
@ -61,6 +62,7 @@ export function hasTextExchangeContent(type, data) {
|
|||
if (type === "timer") return Boolean(data?.stopwatch?.laps?.length || data?.countdowns?.length || data?.stopwatch?.elapsedMs);
|
||||
if (type === "calendar") return Boolean(data?.events?.length);
|
||||
if (type === "taskPlanner") return Boolean(data?.tasks?.length);
|
||||
if (type === "goalTree") return Boolean(data?.nodes?.length);
|
||||
if (type === "equipmentPlanner") return Boolean(data?.equipments?.length);
|
||||
if (type === "images") return Boolean(data?.images?.length);
|
||||
if (type === "imageAnnotation") return Boolean(data?.image);
|
||||
|
|
@ -78,6 +80,7 @@ export function exportModuleText(type, data) {
|
|||
if (type === "timer") return exportTimerText(data);
|
||||
if (type === "calendar") return exportCalendarText(data);
|
||||
if (type === "taskPlanner") return exportTaskPlannerText(data);
|
||||
if (type === "goalTree") return exportGoalTreeText(data);
|
||||
if (type === "equipmentPlanner") return exportEquipmentPlannerText(data);
|
||||
if (type === "images") return exportImagesText(data);
|
||||
if (type === "imageAnnotation") return exportImageAnnotationText(data);
|
||||
|
|
@ -99,6 +102,7 @@ export function importModuleText(type, text, context) {
|
|||
else if (type === "timer") data = importTimerText(value, context);
|
||||
else if (type === "calendar") data = importCalendarText(value, context);
|
||||
else if (type === "taskPlanner") data = importTaskPlannerText(value, context);
|
||||
else if (type === "goalTree") data = importGoalTreeText(value, context);
|
||||
else if (type === "equipmentPlanner") data = importEquipmentPlannerText(value, context);
|
||||
else if (type === "images") data = importImagesText(value, context);
|
||||
else if (type === "imageAnnotation") data = importImageAnnotationText(value, context);
|
||||
|
|
@ -586,6 +590,90 @@ function importTaskPlannerText(text, context) {
|
|||
return context.normalizeTaskPlannerData(data);
|
||||
}
|
||||
|
||||
function exportGoalTreeText(data) {
|
||||
const lines = [];
|
||||
if (data.lastResetAt) lines.push(`@lastResetAt: ${data.lastResetAt}`);
|
||||
function visit(parentId = "", depth = 0) {
|
||||
(data.nodes || []).filter((node) => (node.parentId || "") === parentId).forEach((node) => {
|
||||
const prefix = " ".repeat(depth);
|
||||
const formula = String(node.formula || "").trim();
|
||||
const qty = Number.isFinite(node.computedQtyTarget) ? node.computedQtyTarget : node.qtyTarget;
|
||||
const suffix = node.kind === "step" ? (formula ? ` = ${formula}` : ` x${qty}`) : "";
|
||||
const meta = formatMeta({
|
||||
kind: node.kind !== "step" ? node.kind : "",
|
||||
icon: node.kind === "step" && node.icon && node.icon !== "cube" ? node.icon : "",
|
||||
color: node.kind === "step" && node.color && node.color !== "gold" ? node.color : "",
|
||||
items: node.kind !== "step" && node.items?.length ? encodeURIComponent(JSON.stringify(node.items)) : "",
|
||||
current: node.qtyCurrent || "",
|
||||
reset: node.resetType !== "none" ? node.resetType : "",
|
||||
resetTime: node.resetTime && node.resetTime !== "00:00" ? node.resetTime : "",
|
||||
weeklyResetDay: node.weeklyResetDay !== 1 ? node.weeklyResetDay : "",
|
||||
collapsed: node.collapsed
|
||||
});
|
||||
lines.push(`${prefix}- ${node.title}${suffix}${meta ? ` | ${meta}` : ""}`);
|
||||
visit(node.id, depth + 1);
|
||||
});
|
||||
}
|
||||
visit();
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
function importGoalTreeText(text, context) {
|
||||
const nodes = [];
|
||||
const stack = [];
|
||||
const data = { lastResetAt: 0, nodes };
|
||||
splitLines(text).forEach((line, index) => {
|
||||
if (!line.trim()) return;
|
||||
const cleanLine = line.trim();
|
||||
const rootMeta = parseMetaLine(cleanLine);
|
||||
if (rootMeta && !stack.length) {
|
||||
if (rootMeta.key === "lastResetAt") data.lastResetAt = parseInteger(rootMeta.value, 0);
|
||||
return;
|
||||
}
|
||||
const depth = Math.floor((line.match(/^ */)?.[0].length || 0) / 2);
|
||||
const match = cleanLine.match(/^-\s+(.+)$/);
|
||||
if (!match) throw lineError(index, `ligne arbre d'objectifs invalide: ${cleanLine}`);
|
||||
const { title: rawTitle, meta } = parsePipeParts(match[1]);
|
||||
const formulaIndex = rawTitle.lastIndexOf("=");
|
||||
const quantityMatch = rawTitle.match(/\s+x(\d+(?:[,.]\d+)?)\s*$/i);
|
||||
const hasFormula = formulaIndex >= 0;
|
||||
const title = (hasFormula ? rawTitle.slice(0, formulaIndex) : rawTitle.replace(/\s+x(\d+(?:[,.]\d+)?)\s*$/i, "")).trim();
|
||||
if (!title) throw lineError(index, "titre manquant.");
|
||||
const parent = stack[depth - 1];
|
||||
const node = {
|
||||
id: context.uid("goal"),
|
||||
parentId: parent?.id || "",
|
||||
title,
|
||||
kind: meta.kind || "step",
|
||||
icon: meta.icon || "cube",
|
||||
color: meta.color || "gold",
|
||||
qtyTarget: hasFormula ? 1 : Math.max(0, parseNumber(quantityMatch?.[1], 1)) || 1,
|
||||
qtyCurrent: 0,
|
||||
formula: hasFormula ? rawTitle.slice(formulaIndex + 1).trim() : "",
|
||||
items: parseGoalTreeItemsMeta(meta.items),
|
||||
resetType: meta.reset || "none",
|
||||
resetTime: meta.resetTime || "00:00",
|
||||
weeklyResetDay: parseInteger(meta.weeklyResetDay, 1),
|
||||
checkedAt: 0,
|
||||
collapsed: parseBoolean(meta.collapsed)
|
||||
};
|
||||
nodes.push(node);
|
||||
stack[depth] = node;
|
||||
stack.length = depth + 1;
|
||||
});
|
||||
return context.normalizeGoalTreeData(data);
|
||||
}
|
||||
|
||||
function parseGoalTreeItemsMeta(value) {
|
||||
if (!value) return [];
|
||||
try {
|
||||
const parsed = JSON.parse(decodeURIComponent(value));
|
||||
return Array.isArray(parsed) ? parsed : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function exportTimerText(data) {
|
||||
const lines = [];
|
||||
if (data.activeTab && data.activeTab !== "stopwatch") lines.push(`@activeTab: ${data.activeTab}`);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue