MediaWiki:Gadget-DiagnosticTree.js: Difference between revisions
Jump to navigation
Jump to search
No edit summary |
No edit summary |
||
| Line 1: | Line 1: | ||
/** | /** | ||
* DiagnosticTree.js — v6 | * DiagnosticTree.js — v6 PATCH: choice node support | ||
* | * | ||
* | * THREE changes required. Search for each FIND block and replace | ||
* | * with the corresponding REPLACE block. Nothing else changes. | ||
* | * | ||
* | * ───────────────────────────────────────────────────────────────── | ||
* | * CHANGE 1 of 3 — Add 'choice' to NODE_META | ||
* ───────────────────────────────────────────────────────────────── | |||
* | * | ||
* | * FIND (the last entry in NODE_META, around line 13): | ||
* { | * | ||
* | * neuro_referral: { color:'#b91c1c', icon:'', label:'Neurological Referral' } | ||
* | * | ||
* | * REPLACE WITH: | ||
* | * | ||
* | * neuro_referral: { color:'#b91c1c', icon:'', label:'Neurological Referral' }, | ||
* | * choice: { color:'#7c3aed', icon:'', label:'Select Best Match' } | ||
* | * | ||
* | |||
* ───────────────────────────────────────────────────────────────── | |||
* CHANGE 2 of 3 — Route choice nodes to _choice() in render() | |||
* ───────────────────────────────────────────────────────────────── | |||
* | |||
* FIND (inside DiagnosticTree.prototype.render, around line 120): | |||
* | |||
* if ( node.type === 'result' ) cardHtml = this._result( node ); | |||
* else if ( node.type === 'overlap' ) cardHtml = this._overlap( node ); | |||
* else if ( node.type === 'neuro_referral') cardHtml = this._neuroRef( node ); | |||
* else cardHtml = this._question( node, meta ); | |||
* | |||
* REPLACE WITH: | |||
* | |||
* if ( node.type === 'result' ) cardHtml = this._result( node ); | |||
* else if ( node.type === 'overlap' ) cardHtml = this._overlap( node ); | |||
* else if ( node.type === 'neuro_referral') cardHtml = this._neuroRef( node ); | |||
* else if ( node.type === 'choice' ) cardHtml = this._choice( node, meta ); | |||
* else cardHtml = this._question( node, meta ); | |||
* | |||
* | |||
* ───────────────────────────────────────────────────────────────── | |||
* CHANGE 3 of 3 — Wire option button clicks in render() | |||
* AND add the _choice() method | |||
* ───────────────────────────────────────────────────────────────── | |||
* | |||
* FIND (inside DiagnosticTree.prototype.render, the yes/no wiring | |||
* block near the bottom, around line 140): | |||
* | |||
* b( '#dt-back', function () { s.state.back(); s.render(); } ); | |||
* b( '#dt-reset', function () { s.state.reset(); s.render(); } ); | |||
* b( '#dt-yes', function () { var n = s.state.node(); if ( n.yes ) { s.state.go( n.yes ); s.render(); } } ); | |||
* b( '#dt-no', function () { var n = s.state.node(); if ( n.no ) { s.state.go( n.no ); s.render(); } } ); | |||
* | |||
* REPLACE WITH: | |||
* | |||
* b( '#dt-back', function () { s.state.back(); s.render(); } ); | |||
* b( '#dt-reset', function () { s.state.reset(); s.render(); } ); | |||
* b( '#dt-yes', function () { var n = s.state.node(); if ( n.yes ) { s.state.go( n.yes ); s.render(); } } ); | |||
* b( '#dt-no', function () { var n = s.state.node(); if ( n.no ) { s.state.go( n.no ); s.render(); } } ); | |||
* | |||
* this.el.querySelectorAll( '.dt-option-btn' ).forEach( function ( btn ) { | |||
* btn.addEventListener( 'click', function () { | |||
* var next = btn.getAttribute( 'data-next' ); | |||
* if ( next ) { s.state.go( next ); s.render(); } | |||
* } ); | |||
* } ); | |||
* | |||
* | |||
* ───────────────────────────────────────────────────────────────── | |||
* Then add _choice() as a new method. | |||
* | |||
* FIND (the opening line of the existing _question method): | |||
* | |||
* DiagnosticTree.prototype._question = function ( node, meta ) { | |||
* | |||
* INSERT the entire _choice() method ABOVE that line: | |||
* ───────────────────────────────────────────────────────────────── | |||
*/ | */ | ||
// ─── INSERT THIS ENTIRE BLOCK above _question ─────────────────── | |||
DiagnosticTree.prototype._choice = function ( node, meta ) { | |||
var rat = node.clinical_rationale || ''; | |||
var opts = node.options.map( function ( opt, i ) { | |||
return '<button class="dt-option-btn" data-opt-index="' + i + '" data-next="' + opt.next + '">' + | |||
'<div class="dt-option-content">' + | |||
'<span class="dt-option-label">' + opt.label + '</span>' + | |||
( opt.sublabel | |||
? '<span class="dt-option-sublabel">' + opt.sublabel + '</span>' | |||
: '' ) + | |||
return | |||
'<div class="dt- | |||
'< | |||
'</div>' + | '</div>' + | ||
'</button>'; | |||
} ).join( '' ); | |||
return '<div class="dt-card" style="border-color:' + meta.color + '22">' + | |||
'<div class="dt-type-badge" style="background:' + meta.color + '">' + | |||
meta.icon + ' ' + meta.label + | |||
'</div>' + | |||
'<div class="dt-question">' + node.question + '</div>' + | |||
( rat ? '<div class="dt-rationale">' + rat + '</div>' : '' ) + | |||
'<div class="dt-options">' + opts + '</div>' + | |||
'</div>'; | |||
}; | |||
// ─── END INSERT ────────────────────────────────────────────────── | |||
/** | |||
* ───────────────────────────────────────────────────────────────── | |||
* CSS ADDITIONS — append to MediaWiki:Gadget-DiagnosticTree.css | |||
* ───────────────────────────────────────────────────────────────── | |||
* | |||
* Add the following rules at the end of the existing CSS file. | |||
* They use only existing CSS variables — no new variables needed. | |||
*/ | |||
/* | |||
.dt-options { | |||
display: flex; | |||
flex-direction: column; | |||
gap: 0.5rem; | |||
margin-top: 1rem; | |||
} | |||
.dt-option-btn { | |||
display: flex; | |||
align-items: flex-start; | |||
gap: 0.85rem; | |||
width: 100%; | |||
padding: 0.75rem 0.9rem; | |||
background: var(--paper-2); | |||
border: 1.5px solid var(--rule); | |||
border-radius: 8px; | |||
cursor: pointer; | |||
text-align: left; | |||
font-family: 'DM Sans', sans-serif; | |||
position: relative; | |||
overflow: hidden; | |||
transition: border-color 0.15s, background 0.15s, transform 0.1s, box-shadow 0.15s; | |||
} | |||
.dt-option-btn::before { | |||
content: ''; | |||
position: absolute; | |||
left: 0; top: 0; bottom: 0; | |||
width: 4px; | |||
background: var(--opt-color, var(--blue)); | |||
border-radius: 8px 0 0 8px; | |||
transition: width 0.15s; | |||
} | |||
.dt-option-btn:hover { | |||
border-color: var(--opt-color, var(--blue)); | |||
background: #fff; | |||
transform: translateY(-1px); | |||
box-shadow: 0 3px 10px rgba(0,0,0,0.08); | |||
} | |||
.dt-option-btn:hover::before { width: 6px; } | |||
.dt-option-btn:active { transform: translateY(0); box-shadow: none; } | |||
.dt-option-btn[data-opt-index="0"] { --opt-color: var(--blue); } | |||
.dt-option-btn[data-opt-index="1"] { --opt-color: var(--teal); } | |||
.dt-option-btn[data-opt-index="2"] { --opt-color: var(--slate); } | |||
.dt-option-btn[data-opt-index="3"] { --opt-color: var(--urgent); } | |||
.dt-option-btn[data-opt-index="0"]:hover { background: #eff6ff; border-color: var(--blue); } | |||
.dt-option-btn[data-opt-index="1"]:hover { background: #f0fdfa; border-color: var(--teal); } | |||
.dt-option-btn[data-opt-index="2"]:hover { background: #f8fafc; border-color: var(--slate); } | |||
.dt-option-btn[data-opt-index="3"]:hover { background: #fffbeb; border-color: var(--urgent); } | |||
.dt-option-content { | |||
display: flex; | |||
flex-direction: column; | |||
gap: 0.15rem; | |||
padding-left: 0.25rem; | |||
} | |||
.dt-option-label { | |||
font-size: 0.9rem; | |||
font-weight: 600; | |||
color: var(--ink); | |||
line-height: 1.35; | |||
} | |||
.dt-option-sublabel { | |||
font-size: 0.75rem; | |||
color: var(--ink-soft); | |||
line-height: 1.4; | |||
font-weight: 400; | |||
} | |||
*/ | |||
/** | |||
* ───────────────────────────────────────────────────────────────── | |||
* SUMMARY OF ALL CHANGES | |||
* ───────────────────────────────────────────────────────────────── | |||
* | |||
* DiagnosticTree.js — 3 edits: | |||
* 1. Add 'choice' entry to NODE_META (~1 line) | |||
* 2. Add choice branch in render() dispatcher (~1 line) | |||
* 3. Add option button click wiring in render() (~5 lines) | |||
* 4. Add _choice() method above _question() (~20 lines) | |||
* | |||
* DiagnosticTree.css — 1 edit: | |||
* Append ~50 lines of new rules at the end of the file. | |||
* All rules use only existing CSS variables. | |||
* No existing rules are modified. | |||
* | |||
* Backward compatibility: | |||
* All existing binary (yes/no) nodes are completely unchanged. | |||
* The new code only activates when node.type === 'choice'. | |||
* Existing EarTMJ and FrontOfChest trees work identically. | |||
* | |||
* JSON schema for a choice node: | |||
* { | |||
* "type": "choice", | |||
* "question": "...", | |||
* "clinical_rationale": "...", <- optional | |||
* "options": [ | |||
* { "label": "...", "sublabel": "...", "next": "node-id" }, | |||
* { "label": "...", "sublabel": "...", "next": "node-id" }, | |||
* { "label": "...", "sublabel": "...", "next": "node-id" } | |||
* ] | |||
* } | |||
* sublabel is optional. Maximum 4 options (index 0–3 are styled). | |||
* ───────────────────────────────────────────────────────────────── | |||
*/ | |||
} | |||
Revision as of 12:21, 9 May 2026
/**
* DiagnosticTree.js — v6 PATCH: choice node support
*
* THREE changes required. Search for each FIND block and replace
* with the corresponding REPLACE block. Nothing else changes.
*
* ─────────────────────────────────────────────────────────────────
* CHANGE 1 of 3 — Add 'choice' to NODE_META
* ─────────────────────────────────────────────────────────────────
*
* FIND (the last entry in NODE_META, around line 13):
*
* neuro_referral: { color:'#b91c1c', icon:'', label:'Neurological Referral' }
*
* REPLACE WITH:
*
* neuro_referral: { color:'#b91c1c', icon:'', label:'Neurological Referral' },
* choice: { color:'#7c3aed', icon:'', label:'Select Best Match' }
*
*
* ─────────────────────────────────────────────────────────────────
* CHANGE 2 of 3 — Route choice nodes to _choice() in render()
* ─────────────────────────────────────────────────────────────────
*
* FIND (inside DiagnosticTree.prototype.render, around line 120):
*
* if ( node.type === 'result' ) cardHtml = this._result( node );
* else if ( node.type === 'overlap' ) cardHtml = this._overlap( node );
* else if ( node.type === 'neuro_referral') cardHtml = this._neuroRef( node );
* else cardHtml = this._question( node, meta );
*
* REPLACE WITH:
*
* if ( node.type === 'result' ) cardHtml = this._result( node );
* else if ( node.type === 'overlap' ) cardHtml = this._overlap( node );
* else if ( node.type === 'neuro_referral') cardHtml = this._neuroRef( node );
* else if ( node.type === 'choice' ) cardHtml = this._choice( node, meta );
* else cardHtml = this._question( node, meta );
*
*
* ─────────────────────────────────────────────────────────────────
* CHANGE 3 of 3 — Wire option button clicks in render()
* AND add the _choice() method
* ─────────────────────────────────────────────────────────────────
*
* FIND (inside DiagnosticTree.prototype.render, the yes/no wiring
* block near the bottom, around line 140):
*
* b( '#dt-back', function () { s.state.back(); s.render(); } );
* b( '#dt-reset', function () { s.state.reset(); s.render(); } );
* b( '#dt-yes', function () { var n = s.state.node(); if ( n.yes ) { s.state.go( n.yes ); s.render(); } } );
* b( '#dt-no', function () { var n = s.state.node(); if ( n.no ) { s.state.go( n.no ); s.render(); } } );
*
* REPLACE WITH:
*
* b( '#dt-back', function () { s.state.back(); s.render(); } );
* b( '#dt-reset', function () { s.state.reset(); s.render(); } );
* b( '#dt-yes', function () { var n = s.state.node(); if ( n.yes ) { s.state.go( n.yes ); s.render(); } } );
* b( '#dt-no', function () { var n = s.state.node(); if ( n.no ) { s.state.go( n.no ); s.render(); } } );
*
* this.el.querySelectorAll( '.dt-option-btn' ).forEach( function ( btn ) {
* btn.addEventListener( 'click', function () {
* var next = btn.getAttribute( 'data-next' );
* if ( next ) { s.state.go( next ); s.render(); }
* } );
* } );
*
*
* ─────────────────────────────────────────────────────────────────
* Then add _choice() as a new method.
*
* FIND (the opening line of the existing _question method):
*
* DiagnosticTree.prototype._question = function ( node, meta ) {
*
* INSERT the entire _choice() method ABOVE that line:
* ─────────────────────────────────────────────────────────────────
*/
// ─── INSERT THIS ENTIRE BLOCK above _question ───────────────────
DiagnosticTree.prototype._choice = function ( node, meta ) {
var rat = node.clinical_rationale || '';
var opts = node.options.map( function ( opt, i ) {
return '<button class="dt-option-btn" data-opt-index="' + i + '" data-next="' + opt.next + '">' +
'<div class="dt-option-content">' +
'<span class="dt-option-label">' + opt.label + '</span>' +
( opt.sublabel
? '<span class="dt-option-sublabel">' + opt.sublabel + '</span>'
: '' ) +
'</div>' +
'</button>';
} ).join( '' );
return '<div class="dt-card" style="border-color:' + meta.color + '22">' +
'<div class="dt-type-badge" style="background:' + meta.color + '">' +
meta.icon + ' ' + meta.label +
'</div>' +
'<div class="dt-question">' + node.question + '</div>' +
( rat ? '<div class="dt-rationale">' + rat + '</div>' : '' ) +
'<div class="dt-options">' + opts + '</div>' +
'</div>';
};
// ─── END INSERT ──────────────────────────────────────────────────
/**
* ─────────────────────────────────────────────────────────────────
* CSS ADDITIONS — append to MediaWiki:Gadget-DiagnosticTree.css
* ─────────────────────────────────────────────────────────────────
*
* Add the following rules at the end of the existing CSS file.
* They use only existing CSS variables — no new variables needed.
*/
/*
.dt-options {
display: flex;
flex-direction: column;
gap: 0.5rem;
margin-top: 1rem;
}
.dt-option-btn {
display: flex;
align-items: flex-start;
gap: 0.85rem;
width: 100%;
padding: 0.75rem 0.9rem;
background: var(--paper-2);
border: 1.5px solid var(--rule);
border-radius: 8px;
cursor: pointer;
text-align: left;
font-family: 'DM Sans', sans-serif;
position: relative;
overflow: hidden;
transition: border-color 0.15s, background 0.15s, transform 0.1s, box-shadow 0.15s;
}
.dt-option-btn::before {
content: '';
position: absolute;
left: 0; top: 0; bottom: 0;
width: 4px;
background: var(--opt-color, var(--blue));
border-radius: 8px 0 0 8px;
transition: width 0.15s;
}
.dt-option-btn:hover {
border-color: var(--opt-color, var(--blue));
background: #fff;
transform: translateY(-1px);
box-shadow: 0 3px 10px rgba(0,0,0,0.08);
}
.dt-option-btn:hover::before { width: 6px; }
.dt-option-btn:active { transform: translateY(0); box-shadow: none; }
.dt-option-btn[data-opt-index="0"] { --opt-color: var(--blue); }
.dt-option-btn[data-opt-index="1"] { --opt-color: var(--teal); }
.dt-option-btn[data-opt-index="2"] { --opt-color: var(--slate); }
.dt-option-btn[data-opt-index="3"] { --opt-color: var(--urgent); }
.dt-option-btn[data-opt-index="0"]:hover { background: #eff6ff; border-color: var(--blue); }
.dt-option-btn[data-opt-index="1"]:hover { background: #f0fdfa; border-color: var(--teal); }
.dt-option-btn[data-opt-index="2"]:hover { background: #f8fafc; border-color: var(--slate); }
.dt-option-btn[data-opt-index="3"]:hover { background: #fffbeb; border-color: var(--urgent); }
.dt-option-content {
display: flex;
flex-direction: column;
gap: 0.15rem;
padding-left: 0.25rem;
}
.dt-option-label {
font-size: 0.9rem;
font-weight: 600;
color: var(--ink);
line-height: 1.35;
}
.dt-option-sublabel {
font-size: 0.75rem;
color: var(--ink-soft);
line-height: 1.4;
font-weight: 400;
}
*/
/**
* ─────────────────────────────────────────────────────────────────
* SUMMARY OF ALL CHANGES
* ─────────────────────────────────────────────────────────────────
*
* DiagnosticTree.js — 3 edits:
* 1. Add 'choice' entry to NODE_META (~1 line)
* 2. Add choice branch in render() dispatcher (~1 line)
* 3. Add option button click wiring in render() (~5 lines)
* 4. Add _choice() method above _question() (~20 lines)
*
* DiagnosticTree.css — 1 edit:
* Append ~50 lines of new rules at the end of the file.
* All rules use only existing CSS variables.
* No existing rules are modified.
*
* Backward compatibility:
* All existing binary (yes/no) nodes are completely unchanged.
* The new code only activates when node.type === 'choice'.
* Existing EarTMJ and FrontOfChest trees work identically.
*
* JSON schema for a choice node:
* {
* "type": "choice",
* "question": "...",
* "clinical_rationale": "...", <- optional
* "options": [
* { "label": "...", "sublabel": "...", "next": "node-id" },
* { "label": "...", "sublabel": "...", "next": "node-id" },
* { "label": "...", "sublabel": "...", "next": "node-id" }
* ]
* }
* sublabel is optional. Maximum 4 options (index 0–3 are styled).
* ─────────────────────────────────────────────────────────────────
*/