From 3125e7684c2459fe869842f61b0738b0ae6e9ae1 Mon Sep 17 00:00:00 2001 From: William Braeckman Date: Thu, 13 Mar 2025 10:02:43 +0100 Subject: [PATCH 1/2] [REF] runbot: extract diff display to own component Also modernize code of the diff match patch use. --- runbot/static/src/js/fields/diff_display.js | 79 +++++++++++++++++++ runbot/static/src/js/fields/diff_display.xml | 14 ++++ runbot/static/src/js/fields/tracking_value.js | 56 ++----------- .../static/src/js/fields/tracking_value.xml | 15 +--- 4 files changed, 102 insertions(+), 62 deletions(-) create mode 100644 runbot/static/src/js/fields/diff_display.js create mode 100644 runbot/static/src/js/fields/diff_display.xml diff --git a/runbot/static/src/js/fields/diff_display.js b/runbot/static/src/js/fields/diff_display.js new file mode 100644 index 000000000..d106eec2e --- /dev/null +++ b/runbot/static/src/js/fields/diff_display.js @@ -0,0 +1,79 @@ +import { Component, onWillRender } from '@odoo/owl'; + +import { diff_match_patch } from "@runbot/libs/diff_match_patch/diff_match_patch"; + + +export class DiffDisplay extends Component { + static template = 'runbot.DiffDisplay'; + static props = { + fromValue: { type: String }, + toValue: { type: String }, + lineFilter: { type: Function, optional: true }, + } + static defaultProps = { + lineFilter: (line) => line.type !== 'kept', + } + + setup() { + onWillRender(() => { + this.lines = this.makeLines(this.props.fromValue, this.props.toValue); + }); + } + + makeLines(oldValue, newValue) { + const diff = this.makeDiff(oldValue, newValue); + const lines = this.prepareForRendering(diff); + return lines; + } + + makeDiff(text1, text2) { + const dmp = new diff_match_patch(); + const a = dmp.diff_linesToChars_(text1, text2); + const lineText1 = a.chars1; + const lineText2 = a.chars2; + const lineArray = a.lineArray; + const diffs = dmp.diff_main(lineText1, lineText2, false); + dmp.diff_charsToLines_(diffs, lineArray); + dmp.diff_cleanupSemantic(diffs); + return diffs; + } + + prepareForRendering(diffs) { + let preLineCounter = 0; + let postLineCounter = 0; + return diffs.reduce((lines, {0: diff_type, 1: data}) => { + data.split('\n').forEach(line => { + line = line + .replace(/&/g, '&') + .replace(//g, '>'); + let type, colOne, colTwo; + switch (diff_type) { + case 0: //kept + type = 'kept' + colOne = '' + colTwo = postLineCounter; + preLineCounter++; postLineCounter++; + break; + case -1: //removed + type = 'removed'; + colOne = preLineCounter; + colTwo = '-'; + preLineCounter++; + break; + case 1: //added + type = 'added'; + colOne = '+'; + colTwo = postLineCounter; + postLineCounter++; + break; + default: + console.warn('Unknown diff_type', diff_type) + return; + } + lines.push({type, colOne, colTwo, line}); + }) + return lines + }, []); + } +} diff --git a/runbot/static/src/js/fields/diff_display.xml b/runbot/static/src/js/fields/diff_display.xml new file mode 100644 index 000000000..3e3f46ba0 --- /dev/null +++ b/runbot/static/src/js/fields/diff_display.xml @@ -0,0 +1,14 @@ + + + +
+ + + +
+ + +
+
+
+
diff --git a/runbot/static/src/js/fields/tracking_value.js b/runbot/static/src/js/fields/tracking_value.js index b6403748b..c5cae058c 100644 --- a/runbot/static/src/js/fields/tracking_value.js +++ b/runbot/static/src/js/fields/tracking_value.js @@ -2,6 +2,11 @@ import { patch } from "@web/core/utils/patch"; import { Message } from "@mail/core/common/message"; import { diff_match_patch } from "@runbot/libs/diff_match_patch/diff_match_patch"; +import { DiffDisplay } from './diff_display'; + +patch(Message, { + components: {...Message.components, DiffDisplay}, +}); patch(Message.prototype, { setup() { @@ -13,9 +18,6 @@ patch(Message.prototype, { const newValue = trackingValue.newValue.value; return ((oldValue && typeof oldValue=== 'string' && oldValue.includes('\n')) && (newValue && typeof oldValue=== 'string' && newValue.includes('\n'))) }, - formatTracking(trackingType, trackingValue) { - return super.formatTracking(trackingType, trackingValue) - }, toggleKept() { this.kept = !this.kept; }, @@ -29,52 +31,4 @@ patch(Message.prototype, { navigator.clipboard.writeText(trackingValue.newValue.value); }; }, - lines(trackingValue) { - const oldValue = trackingValue.oldValue.value; - const newValue = trackingValue.newValue.value; - const diff = this.makeDiff(oldValue, newValue); - const lines = this.prepareForRendering(diff); - return lines; - }, - makeDiff(text1, text2) { - var dmp = new diff_match_patch(); - var a = dmp.diff_linesToChars_(text1, text2); - var lineText1 = a.chars1; - var lineText2 = a.chars2; - var lineArray = a.lineArray; - var diffs = dmp.diff_main(lineText1, lineText2, false); - dmp.diff_charsToLines_(diffs, lineArray); - dmp.diff_cleanupSemantic(diffs); - return diffs; - }, - prepareForRendering(diffs) { - var lines = []; - var pre_line_counter = 0 - var post_line_counter = 0 - for (var x = 0; x < diffs.length; x++) { - var diff_type = diffs[x][0]; - var data = diffs[x][1]; - var data_lines = data.split('\n'); - for (var line_index in data_lines) { - var line = data_lines[line_index]; - line = line.replace(/&/g, '&'); - line = line.replace(//g, '>'); - //text = text.replace(/\n/g, '
'); - //text = text.replace(/ /g, '  '); - if (diff_type == -1) { - lines.push({type:'removed', pre_line_counter: pre_line_counter, post_line_counter: '-', line: line}) - pre_line_counter += 1 - } else if (diff_type == 0) { - lines.push({type:'kept', pre_line_counter: '', post_line_counter: post_line_counter, line: line}) - pre_line_counter += 1 - post_line_counter +=1 - } else if (diff_type == 1) { - lines.push({type:'added', pre_line_counter: '+', post_line_counter: post_line_counter, line: line}) - post_line_counter +=1 - } - } - } - return lines; - }, }); diff --git a/runbot/static/src/js/fields/tracking_value.xml b/runbot/static/src/js/fields/tracking_value.xml index 9aaca2336..16efe1353 100644 --- a/runbot/static/src/js/fields/tracking_value.xml +++ b/runbot/static/src/js/fields/tracking_value.xml @@ -10,17 +10,10 @@
()
-
- - - - - -
- - -
-
+ From ac424f43f08377033e222dfbccbcec5a1e50a1ca Mon Sep 17 00:00:00 2001 From: William Braeckman Date: Thu, 13 Mar 2025 10:19:08 +0100 Subject: [PATCH 2/2] [IMP] runbot: diffable error contents Adds the ability to make a diff between error contents in the error form view. --- .../src/js/fields/field_error_content.js | 84 +++++++++++++++++++ .../src/js/fields/field_error_content.xml | 27 ++++++ runbot/views/build_error_views.xml | 4 +- 3 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 runbot/static/src/js/fields/field_error_content.js create mode 100644 runbot/static/src/js/fields/field_error_content.xml diff --git a/runbot/static/src/js/fields/field_error_content.js b/runbot/static/src/js/fields/field_error_content.js new file mode 100644 index 000000000..f653a5ad9 --- /dev/null +++ b/runbot/static/src/js/fields/field_error_content.js @@ -0,0 +1,84 @@ +import { Component, useEffect, useState } from '@odoo/owl'; + +import { registry } from '@web/core/registry'; +import { useBus } from '@web/core/utils/hooks'; +import { standardFieldProps } from "@web/views/fields/standard_field_props"; +import { TextField } from '@web/views/fields/text/text_field'; +import { X2ManyField, x2ManyField } from "@web/views/fields/x2many/x2many_field"; +import { CheckBox } from "@web/core/checkbox/checkbox"; + +import { diff_match_patch } from "@runbot/libs/diff_match_patch/diff_match_patch"; +import { DiffDisplay } from './diff_display'; + + +export class ErrorContentOne2ManyList extends X2ManyField { + static template = 'runbot.ErrorContentOne2ManyList'; + static components = {...X2ManyField.components, CheckBox}; + + setup() { + super.setup(...arguments); + this.creates = []; + this.state = useState({ + useDiff: localStorage.getItem('runbot.error_content_diff_mode') === 'true', + }); + + useEffect(() => { + localStorage.setItem('runbot.error_content_diff_mode', this.state.useDiff); + this.env.bus.trigger( + 'RUNBOT.TOGGLE-DIFF-MODE', { + mode: this.state.useDiff, + }, + ); + }, () => [this.state.useDiff]); + } + + get displayControlPanelButtons() { + return true; + } + + onToggle(state) { + this.state.useDiff = state; + } +} + +export const errorContentOne2ManyList = { + ...x2ManyField, + component: ErrorContentOne2ManyList, +}; + +export class FieldErrorContentContent extends Component { + static template = 'runbot.FieldErrorContentContent'; + static components = { TextField, DiffDisplay }; + static props = {...standardFieldProps}; + + setup() { + // We assume that the content is readonly here + this.otherRecords = this.props.record.model.root.data.error_content_ids.records; + this.state = useState({ + useDiff: localStorage.getItem('runbot.error_content_diff_mode') === 'true', + }); + + useBus( + this.env.bus, 'RUNBOT.TOGGLE-DIFF-MODE', + ({detail: {mode}}) => this.state.useDiff = mode, + ); + } + + get isParent() { + return this.otherRecords[0] === this.props.record; + } + + get parent() { + return this.otherRecords[0]; + } +} + +export const fieldErrorContentContent = { + component: FieldErrorContentContent, + displayName: "Error Content diffable (list)", + supportedTypes: ["html", "text", "char"], +}; + + +registry.category('fields').add('error_content_list', errorContentOne2ManyList); +registry.category('fields').add('list.error_content_content', fieldErrorContentContent); diff --git a/runbot/static/src/js/fields/field_error_content.xml b/runbot/static/src/js/fields/field_error_content.xml new file mode 100644 index 000000000..0a9318397 --- /dev/null +++ b/runbot/static/src/js/fields/field_error_content.xml @@ -0,0 +1,27 @@ + + + +
+ o_x2m_control_panel d-empty-none mt-1 +
+
+ + Toggle diff mode + +
+
+ + + + + + + +
diff --git a/runbot/views/build_error_views.xml b/runbot/views/build_error_views.xml index b1c30c925..4da1b84f7 100644 --- a/runbot/views/build_error_views.xml +++ b/runbot/views/build_error_views.xml @@ -15,9 +15,9 @@