Commit 8100ce07 authored by jan.koester's avatar jan.koester
Browse files

fixed survey

parent 1d595954
Loading
Loading
Loading
Loading
+38 −0
Changes for editor/html/css/editor.css: 38 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -1239,6 +1239,40 @@ dialog label {
    white-space: nowrap;
}

.survey-select-field {
    display: flex;
    gap: 6px;
    align-items: center;
}

.survey-select-name {
    flex: 1;
    padding: 6px 8px;
    background: var(--bg-tertiary);
    color: var(--text-primary);
    border: 1px solid var(--border);
    border-radius: 4px;
    font-size: 13px;
    min-height: 32px;
    box-sizing: border-box;
}

.survey-select-btn {
    padding: 6px 10px;
    background: var(--accent);
    color: var(--bg-primary);
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 12px;
    font-weight: 600;
    white-space: nowrap;
}

.survey-select-btn:hover {
    background: var(--accent-hover);
}

/* Color Var / Custom Picker */
.color-mode-toggle {
    display: flex;
@@ -1469,11 +1503,15 @@ dialog label {
}

.sm-q-panel .set-field {
    flex-direction: column;
    align-items: stretch;
    gap: 0.2em;
    margin-bottom: 0.5em;
}

.sm-q-panel label {
    display: block;
    min-width: 0;
    font-size: 0.75rem;
    color: var(--text-secondary);
    margin-bottom: 0.2em;
+3 −3
Changes for editor/html/js/properties.js: 3 added lines, 3 removed lines.
Original line number Diff line number Diff line
@@ -634,16 +634,16 @@ var PropertiesPanel = (function() {
                    surveyHidden.value = value;

                    var surveyWrap = document.createElement('div');
                    surveyWrap.style.cssText = 'display:flex;gap:6px;align-items:center;';
                    surveyWrap.className = 'survey-select-field';

                    var surveyName = document.createElement('span');
                    surveyName.style.cssText = 'flex:1;padding:6px 8px;border:1px solid #ddd;border-radius:4px;font-size:.9em;color:#555;background:#f9f9f9;min-height:32px;box-sizing:border-box;';
                    surveyName.className = 'survey-select-name';
                    surveyName.textContent = value || '(keine Umfrage)';

                    var surveyBtn = document.createElement('button');
                    surveyBtn.type = 'button';
                    surveyBtn.className = 'survey-select-btn';
                    surveyBtn.textContent = 'Umfragen verwalten';
                    surveyBtn.style.cssText = 'padding:6px 10px;background:#3b82f6;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:.85em;white-space:nowrap;';

                    (function(hidden, nameEl) {
                        surveyBtn.addEventListener('click', function() {
+76 −42
Changes for editor/html/js/survey-manager.js: 76 added lines, 42 removed lines.
Original line number Diff line number Diff line
@@ -144,36 +144,48 @@ var SurveyManager = (function() {
        renderCanvas(_layout.map(function(l) { return l.q; }));
    }

    function buildTree(questions) {
        var byId = {};
        questions.forEach(function(q) { byId[q.id] = { q: q, children: [] }; });
        var roots = [];
        questions.forEach(function(q) {
            var pid = q.parent_id;
            if (pid && byId[pid]) {
                byId[pid].children.push(byId[q.id]);
            } else {
                roots.push(byId[q.id]);
            }
    // Questions are always presented to visitors in a single sequential order
    // (order_idx); the only real branching is the "excludes" skip-logic on
    // options, drawn separately as side edges in renderCanvas().
    function layoutSequential(questions) {
        _layout = [];
        var sorted = questions.slice().sort(function(a, b) {
            return (a.order_idx || 0) - (b.order_idx || 0);
        });
        sorted.forEach(function(q, i) {
            _layout.push({ q: q, x: 20, y: i * (NODE_H + V_GAP) + 20 });
        });
        return roots;
    }

    function layoutTree(roots) {
        _layout = [];
        var col = 0;

        function place(node, depth) {
            var x = col * (NODE_W + H_GAP);
            var y = depth * (NODE_H + V_GAP) + 20;
            _layout.push({ q: node.q, x: x, y: y });
            col++;
            node.children.forEach(function(child) {
                place(child, depth + 1);
    function collectExcludeEdges(byId) {
        var edges = [];
        _layout.forEach(function(n) {
            (n.q.options || []).forEach(function(opt) {
                if (!opt.excludes) return;
                opt.excludes.split(',').forEach(function(id) {
                    id = id.trim();
                    var target = byId[id];
                    if (!id || !target || target === n) return;
                    edges.push({ from: n, to: target, label: opt.label || opt.value });
                });
            });
        });
        return edges;
    }

        roots.forEach(function(r) { place(r, 0); });
    function drawArrowHead(ctx, x, y, angle, color) {
        var size = 6;
        ctx.save();
        ctx.translate(x, y);
        ctx.rotate(angle);
        ctx.beginPath();
        ctx.moveTo(0, 0);
        ctx.lineTo(-size, -size / 2);
        ctx.lineTo(-size, size / 2);
        ctx.closePath();
        ctx.fillStyle = color;
        ctx.fill();
        ctx.restore();
    }

    var QUESTION_TYPE_LABELS = {
@@ -186,16 +198,20 @@ var SurveyManager = (function() {
        'yesno':    'Ja/Nein'
    };

    var EDGE_BOW = 90; // horizontal room reserved for exclude-logic curves

    function renderCanvas(questions) {
        if (!_canvas || !_ctx) return;

        var roots = buildTree(questions);
        layoutTree(roots);
        layoutSequential(questions);
        var byId = {};
        _layout.forEach(function(n) { byId[n.q.id] = n; });
        var excludeEdges = collectExcludeEdges(byId);

        // Compute required canvas size
        var maxX = 0, maxY = 0;
        _layout.forEach(function(n) {
            maxX = Math.max(maxX, n.x + NODE_W + 20);
            maxX = Math.max(maxX, n.x + NODE_W + 20 + (excludeEdges.length ? EDGE_BOW : 0));
            maxY = Math.max(maxY, n.y + NODE_H + 20);
        });
        _canvas.width  = Math.max(maxX, 400);
@@ -209,27 +225,45 @@ var SurveyManager = (function() {
        var cBg       = style.getPropertyValue('--bg-secondary').trim()   || '#1e1e2e';
        var cBorder   = style.getPropertyValue('--border').trim()         || '#444';
        var cAccent   = style.getPropertyValue('--accent').trim()         || '#7aa2f7';
        var cOnAccent = style.getPropertyValue('--bg-primary').trim()     || '#1e1e2e';
        var cText     = style.getPropertyValue('--text-primary').trim()   || '#cdd6f4';
        var cSub      = style.getPropertyValue('--text-secondary').trim() || '#888';
        var cWarning  = style.getPropertyValue('--warning').trim()        || '#f9e2af';

        // Draw edges first
        _layout.forEach(function(n) {
            if (!n.q.parent_id) return;
            var parentNode = null;
            for (var i = 0; i < _layout.length; i++) {
                if (_layout[i].q.id === n.q.parent_id) { parentNode = _layout[i]; break; }
            }
            if (!parentNode) return;
        // Sequential flow: a plain line from each question to the next
        for (var i = 0; i < _layout.length - 1; i++) {
            var a = _layout[i], b = _layout[i + 1];
            ctx.beginPath();
            ctx.strokeStyle = cBorder;
            ctx.lineWidth = 1.5;
            var sx = parentNode.x + NODE_W / 2;
            var sy = parentNode.y + NODE_H;
            var ex = n.x + NODE_W / 2;
            var ey = n.y;
            ctx.moveTo(a.x + NODE_W / 2, a.y + NODE_H);
            ctx.lineTo(b.x + NODE_W / 2, b.y);
            ctx.stroke();
        }

        // Skip-logic ("excludes") edges: dashed curves bowing out to the right,
        // labeled with the answer option that triggers the skip.
        excludeEdges.forEach(function(edge) {
            var sx = edge.from.x + NODE_W, sy = edge.from.y + NODE_H / 2;
            var ex = edge.to.x + NODE_W, ey = edge.to.y + NODE_H / 2;
            var bow = EDGE_BOW * (0.6 + 0.4 * Math.min(3, Math.abs(edge.to.y - edge.from.y) / (NODE_H + V_GAP)));

            ctx.beginPath();
            ctx.strokeStyle = cWarning;
            ctx.lineWidth = 1.5;
            ctx.setLineDash([4, 3]);
            ctx.moveTo(sx, sy);
            ctx.bezierCurveTo(sx, sy + V_GAP * 0.5, ex, ey - V_GAP * 0.5, ex, ey);
            ctx.bezierCurveTo(sx + bow, sy, ex + bow, ey, ex, ey);
            ctx.stroke();
            ctx.setLineDash([]);
            drawArrowHead(ctx, ex, ey, Math.PI, cWarning);

            if (edge.label) {
                ctx.fillStyle = cWarning;
                ctx.font = '9px sans-serif';
                ctx.textAlign = 'left';
                ctx.fillText(truncate(edge.label, 14), sx + 4, sy - 4);
            }
        });

        // Draw nodes
@@ -255,14 +289,14 @@ var SurveyManager = (function() {
            ctx.lineWidth = sel ? 2 : 1;
            ctx.stroke();

            ctx.fillStyle = sel ? '#fff' : cText;
            ctx.fillStyle = sel ? cOnAccent : cText;
            ctx.font = 'bold 11px sans-serif';
            ctx.textAlign = 'center';
            ctx.fillText(
                truncate(n.q.label || '(leer)', 22),
                rx + rw / 2, ry + 20
            );
            ctx.fillStyle = sel ? 'rgba(255,255,255,0.8)' : cSub;
            ctx.fillStyle = sel ? cOnAccent : cSub;
            ctx.font = '10px sans-serif';
            ctx.fillText(
                QUESTION_TYPE_LABELS[n.q.question_type] || n.q.question_type,