Files
Odoo-Modules/fusion_repairs/static/src/components/flowchart_designer/flowchart_designer.xml
gsinghpal 2414b6328e fix(fusion_repairs): designer setup() scope - onMounted/onWillUnmount were stranded outside, broke entire backend bundle
REGRESSION FROM b22bb11b (Wysiwyg integration).

While inserting the new Wysiwyg methods (wysiwygConfig getter, onWysiwygLoad,
onToggleSource) between setup() and the existing onMounted / onWillUnmount
hook calls, I accidentally closed setup() early with the new
`this.wysiwygEditors = {};` assignment. That left the original
`onMounted(async () => {...});` and `onWillUnmount(...);` calls dangling
INSIDE the class body but OUTSIDE any method - which is invalid JS.

JavaScript's class-body parser sees the bare `onMounted(async () => ...)`
and tries to interpret it as a method declaration where `onMounted` is the
name and the parens are the parameter list. `async () => {...}` is not a
valid parameter, so the bundle fails with:

  Uncaught SyntaxError: Unexpected token '('
  web.assets_web.min.js:28807

That single parse failure tanks the entire backend asset bundle, leaving
users with a completely blank screen on /odoo (and any other backend
route). Frontend bundle was unaffected.

FIX

Move the onMounted / onWillUnmount calls back inside setup() where they
belong. Add a load-bearing comment explaining why they must stay there so
this regression cannot silently come back during a future refactor.

VERIFIED

  - line 51: setup() opens
  - lines 87, 93: onMounted, onWillUnmount calls INSIDE setup
  - line 142: _initDrawflow as a normal class method (outside setup)
  - upgrade clean
  - bundle 10029245 bytes, exactly one onMounted( occurrence in
    FlowchartDesigner class body
  - node --check on the freshly-rendered web.assets_web.min.js -> PARSE_OK

Bumped to 19.0.2.2.3.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 13:44:58 -04:00

153 lines
9.5 KiB
XML

<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="fusion_repairs.FlowchartDesigner">
<div class="fr-designer-wrap">
<div class="fr-designer-toolbar">
<span class="fr-designer-title" t-if="state.chart">
<strong t-out="state.chart.name"/>
<span class="text-muted ms-2">v<t t-out="state.chart.version"/></span>
</span>
<div class="fr-designer-toolbar-actions">
<div class="btn-group btn-group-sm me-2">
<button class="btn btn-outline-primary" t-on-click="() => this.onAddNode('question')">
<i class="fa fa-plus"/> Question
</button>
<button class="btn btn-outline-success" t-on-click="() => this.onAddNode('suggestion')">
<i class="fa fa-plus"/> Suggestion
</button>
<button class="btn btn-outline-secondary" t-on-click="() => this.onAddNode('info')">
<i class="fa fa-plus"/> Info
</button>
<button class="btn btn-outline-danger" t-on-click="() => this.onAddNode('outcome')">
<i class="fa fa-plus"/> Outcome
</button>
</div>
<div class="btn-group btn-group-sm me-2">
<button class="btn btn-light" t-on-click="onZoomOut"><i class="fa fa-search-minus"/></button>
<button class="btn btn-light" t-on-click="onZoomReset">100%</button>
<button class="btn btn-light" t-on-click="onZoomIn"><i class="fa fa-search-plus"/></button>
</div>
<button class="btn btn-primary btn-sm"
t-on-click="onSave"
t-att-disabled="state.saving">
<i class="fa fa-save"/>
<t t-if="state.saving">Saving...</t>
<t t-elif="state.dirty">Save *</t>
<t t-else="">Save</t>
</button>
</div>
</div>
<div class="fr-designer-body">
<div class="fr-designer-canvas-wrap">
<div t-ref="canvas" class="drawflow"/>
</div>
<div t-ref="editorPanel" class="fr-designer-editor">
<h6>Node Editor</h6>
<t t-if="!selectedMeta">
<p class="text-muted small">Click a node on the canvas to edit it. Drag from a node's right edge to another node to create an edge.</p>
</t>
<t t-if="selectedMeta">
<div class="mb-2">
<label class="form-label small mb-1">Title</label>
<input class="form-control form-control-sm"
t-att-value="selectedMeta.name"
t-on-change="(ev) => this.onEditorFieldChange('name', ev.target.value)"/>
</div>
<div class="mb-2">
<label class="form-label small mb-1">Type</label>
<select class="form-select form-select-sm"
t-on-change="(ev) => this.onEditorFieldChange('node_type', ev.target.value)">
<option value="question" t-att-selected="selectedMeta.node_type === 'question'">Question</option>
<option value="suggestion" t-att-selected="selectedMeta.node_type === 'suggestion'">Suggestion</option>
<option value="info" t-att-selected="selectedMeta.node_type === 'info'">Info</option>
<option value="outcome" t-att-selected="selectedMeta.node_type === 'outcome'">Outcome</option>
</select>
</div>
<div class="mb-2" t-if="selectedMeta.node_type === 'outcome'">
<label class="form-label small mb-1">Outcome Kind</label>
<select class="form-select form-select-sm"
t-on-change="(ev) => this.onEditorFieldChange('outcome_kind', ev.target.value)">
<option value="resolved" t-att-selected="selectedMeta.outcome_kind === 'resolved'">Resolved on call</option>
<option value="escalate" t-att-selected="selectedMeta.outcome_kind === 'escalate'">Escalate to tech</option>
<option value="order_part" t-att-selected="selectedMeta.outcome_kind === 'order_part'">Order part</option>
</select>
</div>
<div class="mb-2 fr-content-editor">
<div class="d-flex justify-content-between align-items-center mb-1">
<label class="form-label small mb-0">Content (shown to CS)</label>
<button type="button"
class="btn btn-link btn-sm p-0"
t-on-click="onToggleSource">
<i t-att-class="state.sourceMode ? 'fa fa-eye' : 'fa fa-code'"/>
<t t-if="state.sourceMode"> Rich Text</t>
<t t-else=""> HTML Source</t>
</button>
</div>
<!-- Rich text (default) - re-mounts when selecting a different node via t-key. -->
<t t-if="!state.sourceMode">
<div class="fr-wysiwyg-shell">
<Wysiwyg t-key="'wysiwyg-' + state.selectedNodeId"
config="wysiwygConfig"
onLoad.bind="onWysiwygLoad"
contentClass="'fr-wysiwyg-content'"/>
</div>
<div class="form-text">Bold, italic, lists, links and inline images supported. Click <em>HTML Source</em> to paste raw markup.</div>
</t>
<!-- HTML source mode (toggle) - power user escape hatch. -->
<t t-else="">
<textarea class="form-control form-control-sm font-monospace"
rows="10"
t-on-change="(ev) => this.onEditorFieldChange('content_html', ev.target.value)"
t-out="selectedMeta.content_html"/>
<div class="form-text">Raw HTML mode - what you type is rendered as-is.</div>
</t>
</div>
<div class="mb-2">
<label class="form-label small mb-1">
Media (<t t-out="selectedMeta.media.length"/>)
</label>
<input type="file" class="form-control form-control-sm"
multiple="multiple"
accept="image/*,video/*"
t-on-change="onUploadMedia"/>
<div t-if="selectedMeta.media.length" class="fr-media-thumbs mt-2">
<t t-foreach="selectedMeta.media" t-as="m" t-key="m.id">
<a t-att-href="m.url" target="_blank" class="me-1">
<img t-att-src="m.url" class="fr-media-thumb" t-att-alt="m.name"/>
</a>
</t>
</div>
</div>
<div class="mb-2">
<button class="btn btn-sm btn-outline-warning"
t-on-click="onSetStart"
t-att-disabled="selectedMeta.is_start">
<i class="fa fa-flag"/>
<t t-if="selectedMeta.is_start">Start Node</t>
<t t-else="">Make Start Node</t>
</button>
</div>
<hr/>
<div t-if="outgoingEdgesForSelected.length">
<h6 class="small">Outgoing Edges</h6>
<t t-foreach="outgoingEdgesForSelected" t-as="e" t-key="e.key">
<div class="mb-1">
<label class="form-label small mb-0">to <em t-out="e.target_name"/></label>
<input class="form-control form-control-sm"
placeholder="Label (Yes / No / ...)"
t-att-value="e.label"
t-on-change="(ev) => this.onEdgeLabelChange(ev, e.key)"/>
</div>
</t>
</div>
</t>
</div>
</div>
</div>
</t>
</templates>