WIP
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built package works basically. (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Has been cancelled
ChangeDetection.io App Test / lint-code (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-10 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-11 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-12 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-13 (push) Has been cancelled

This commit is contained in:
dgtlmoon
2025-10-29 17:08:13 +01:00
parent d4a7f6f198
commit 3e482014c0
10 changed files with 220 additions and 189 deletions
@@ -1,16 +1,12 @@
<ul id="highlightSnippetActions">
<li>
<button class="pure-button pure-button-primary" onclick="diffToJpeg()" title="Share diff as image">Share as Image</button><br>
<span>Creates an image that you can share.</span>
<button class="pure-button pure-button-primary" onclick="diffToJpeg()" title="Share diff as image">Share as Image</button>
</li>
<li>
<a class="pure-button pure-button-primary" data-mode="exact" href="javascript:void(0);">Ignore exact text</a><br>
<span>Ignore any change on any line which contains the selected text.</span>
<a class="pure-button pure-button-primary" data-mode="exact" href="javascript:void(0);">Ignore any lines matching</a>
</li>
<li>
<a class="pure-button pure-button-primary" data-mode="digit-regex" href="javascript:void(0);" >Ignore text including number changes</a><br>
<!-- ??? and the number or number wildcard -->
<span>Ignore any change on any line which contains the selected text.</span>
<a class="pure-button pure-button-primary" data-mode="digit-regex" href="javascript:void(0);" >Ignore any lines matching excluding digits</a>
</li>
</ul>
@@ -11,16 +11,17 @@
const watch_url= {{watch_a.link|tojson}};
</script>
<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
<script src="{{url_for('static_content', group='js', filename='plugins.js')}}"></script>
<script src="https://cdn.jsdelivr.net/npm/piexifjs@1.0.6/piexif.min.js"></script>
<script src="{{url_for('static_content', group='js', filename='snippet-to-image.js')}}"></script>
<script src="{{url_for('static_content', group='js', filename='diff-overview.js')}}" defer></script>
<div id="settings">
<form class="pure-form " action="{{ url_for("ui.ui_views.diff_history_page", uuid=uuid) }}" method="POST" id="diff-form">
<fieldset class="diff-fieldset">
{% if versions|length >= 1 %}
<strong>Compare</strong>
from {{from_version}} to {{to_version}}<br>
<del class="change"><span>from</span></del>
<select id="diff-version" name="from_version" class="needs-localtime">
{% for version in versions|reverse %}
@@ -94,25 +95,17 @@
<img id="error-screenshot-img" style="max-width: 80%" alt="Current error-ing screenshot from most recent request" >
</div>
<div class="tab-pane-inner" id="text">
<div class="tab-pane-inner" id="text">
{% if password_enabled_and_share_is_off %}
<div class="tip">Pro-tip: You can enable <strong>"share access when password is enabled"</strong> from settings</div>
{% endif %}
<div class="tip">Pro-tip: You can enable <strong>"share access when password is enabled"</strong> from settings.
</div>
{% endif %}
<div id="cell-diff-jump-visualiser"></div>
<div class="snapshot-age">{{ watch_a.snapshot_text_ctime|format_timestamp_timeago }}</div>
<pre id="difference" style="border-left: 2px solid #ddd;">{{ content| diff_unescape_difference_spans }}</pre>
</div>
<div class="snapshot-age">{{watch_a.snapshot_text_ctime|format_timestamp_timeago}}</div>
<table>
<tbody>
<tr>
<td id="diff-col" class="highlightable-filter">
<pre id="difference" style="border-left: 2px solid #ddd;">{{ content| diff_unescape_difference_spans }}</pre>
</td>
</tr>
</tbody>
</table>
</div>
<div class="tab-pane-inner" id="screenshot">
<div class="tab-pane-inner" id="screenshot">
<div class="tip">
For now, Differences are performed on text, not graphically, only the latest screenshot is available.
</div>
+1 -1
View File
@@ -283,10 +283,10 @@ def construct_blueprint(datastore: ChangeDetectionStore, update_q, queuedWatchMe
last_error=watch['last_error'],
last_error_screenshot=watch.get_error_snapshot(),
last_error_text=watch.get_error_text(),
left_sticky=True,
newest=to_version_file_contents,
newest_version_timestamp=dates[-1],
password_enabled_and_share_is_off=password_enabled_and_share_is_off,
pure_menu_fixed=False,
screenshot=screenshot_url,
to_version=str(to_version),
uuid=uuid,
+4 -5
View File
@@ -53,17 +53,16 @@ $(document).ready(function () {
}
}
}).done(function (data) {
$("#highlightSnippet").html(data)
// @todo some feedback
$("#highlightSnippet").html(data);
clean();
}).fail(function (data) {
console.log(data);
alert('There was an error communicating with the server.');
}).finally(function () {
clean();
});
})
});
function clean(event) {
$("#highlightSnippet").remove();
$('#bottom-horizontal-offscreen').hide();
}
+97 -1
View File
@@ -1,8 +1,79 @@
$(document).ready(function () {
var inputs;
// Find all <span> elements inside pre#difference
var inputs = $('#difference span').toArray();
inputs.current = 0;
// Build visual minimap of difference locations
var $visualizer = $('#cell-diff-jump-visualiser');
var $difference = $('#difference');
var visualizerResolutionCells = 100; // Fixed resolution to prevent high CPU usage with many changes
var cellHeight;
var cellData = {}; // Map cell index to change type
if ($difference.length && inputs.length) {
var docHeight = $difference[0].scrollHeight;
cellHeight = docHeight / visualizerResolutionCells;
// Map each span to a cell with its type based on color
var differenceTop = $difference.offset().top;
$(inputs).each(function() {
var spanTop = $(this).offset().top - differenceTop;
var cellIndex = Math.min(Math.floor(spanTop / cellHeight), visualizerResolutionCells - 1);
var bgColor = $(this).css('background-color');
var changeType;
// Determine type by background color
if (bgColor === 'rgb(250, 218, 215)' || bgColor === '#fadad7') {
changeType = 'deletion'; // Red background
} else if (bgColor === 'rgb(234, 242, 194)' || bgColor === '#eaf2c2') {
changeType = 'insertion'; // Green background
} else {
changeType = $(this).attr('role'); // Fallback to role attribute
}
// Track the type of change in each cell (prioritize mixed changes)
if (!cellData[cellIndex]) {
cellData[cellIndex] = changeType;
} else if (cellData[cellIndex] !== changeType) {
cellData[cellIndex] = 'mixed'; // Both deletion and insertion in same cell
}
});
// Create cell strip
for (var i = 0; i < visualizerResolutionCells; i++) {
var $cell = $('<div>');
var changeType = cellData[i];
if (changeType === 'deletion') {
$cell.addClass('deletion');
} else if (changeType === 'insertion') {
$cell.addClass('insertion');
} else if (changeType === 'note') {
$cell.addClass('note');
} else if (changeType === 'mixed') {
$cell.addClass('mixed');
}
// Add click handler to scroll to that position in the document
$cell.data('cellIndex', i);
$cell.on('click', function() {
var cellIndex = $(this).data('cellIndex');
var targetPositionInDifference = cellIndex * cellHeight;
var viewportOffset = 150; // Keep change visible with 150px offset
window.scrollTo({
top: $difference.offset().top + targetPositionInDifference - viewportOffset,
behavior: "smooth"
});
});
$visualizer.append($cell);
}
}
$('#jump-next-diff').click(function () {
if (!inputs || inputs.length === 0) return;
var element = inputs[inputs.current];
var headerOffset = 80;
@@ -20,6 +91,31 @@ $(document).ready(function () {
}
});
// Track current scroll position in visualizer
function updateVisualizerPosition() {
if (!$difference.length || visualizerResolutionCells === 0) return;
var scrollTop = $(window).scrollTop();
var differenceTop = $difference.offset().top;
var positionInDifference = scrollTop - differenceTop;
// Calculate which cell we're currently viewing
var currentCell = Math.floor(positionInDifference / cellHeight);
currentCell = Math.max(0, Math.min(currentCell, visualizerResolutionCells - 1));
// Remove previous active marker and add to current cell
$visualizer.find('> div').removeClass('current-position');
$visualizer.find('> div').eq(currentCell).addClass('current-position');
}
// Debounce scroll event to reduce CPU usage
$(window).on('scroll', updateVisualizerPosition.debounce(5));
// Initial position update
if ($difference.length && cellHeight) {
updateVisualizerPosition();
}
function changed() {
//$('#jump-next-diff').click();
}
+1 -141
View File
@@ -1,141 +1 @@
#diff-ui {
background: var(--color-background);
padding: 2em;
margin-left: 1em;
margin-right: 1em;
border-radius: 5px
}
#diff-ui #text {
font-size: 11px
}
#diff-ui table {
table-layout: fixed;
width: 100%
}
#diff-ui td {
padding: 3px 4px;
border: 1px solid rgba(0, 0, 0, 0);
vertical-align: top;
font: 1em monospace;
text-align: left;
overflow: clip
}
#diff-ui pre {
white-space: pre-wrap
}
h1 {
display: inline;
font-size: 100%
}
del {
text-decoration: none;
color: #b30000;
background: #fadad7
}
ins {
background: #eaf2c2;
color: #406619;
text-decoration: none
}
#result {
white-space: pre-wrap;
word-break: break-word;
overflow-wrap: break-word
}
#settings {
background: rgba(0, 0, 0, .05);
padding: 1em;
border-radius: 10px;
margin-bottom: 1em;
color: #fff;
font-size: 80%
}
#settings label {
margin-left: 1em;
display: inline-block;
font-weight: normal
}
#settings del {
padding: .5em
}
#settings ins {
padding: .5em
}
#settings option:checked {
font-weight: bold
}
#settings [type=radio], #settings [type=checkbox] {
vertical-align: middle
}
.source {
position: absolute;
right: 1%;
top: .2em
}
@-moz-document url-prefix() {
body {
height: 99%
}
}
td#diff-col div {
text-align: justify;
white-space: pre-wrap
}
.ignored {
background-color: #ccc;
opacity: .7
}
.triggered {
background-color: #1b98f8
}
.ignored.triggered {
background-color: red
}
.tab-pane-inner#screenshot {
text-align: center
}
.tab-pane-inner#screenshot img {
max-width: 99%
}
#highlightSnippet {
background: var(--color-background);
padding: 1em;
border-radius: 5px;
background: var(--color-background);
box-shadow: 1px 1px 4px var(--color-shadow-jump)
}
.pure-form button.reset-margin {
margin: 0px
}
.diff-fieldset {
display: flex;
align-items: center;
gap: 4px;
flex-wrap: wrap
}
#diff-ui{background:var(--color-background);padding:2em;margin-left:1em;margin-right:1em;border-radius:5px}#diff-ui #text{font-size:11px}#diff-ui table{table-layout:fixed;width:100%}#diff-ui td{padding:3px 4px;border:1px solid rgba(0,0,0,0);vertical-align:top;font:1em monospace;text-align:left;overflow:clip}#diff-ui pre{white-space:break-spaces}h1{display:inline;font-size:100%}del{text-decoration:none;color:#b30000;background:#fadad7}ins{background:#eaf2c2;color:#406619;text-decoration:none}#result{white-space:pre-wrap;word-break:break-word;overflow-wrap:break-word}#settings{background:rgba(0,0,0,.05);padding:1em;border-radius:10px;margin-bottom:1em;color:#fff;font-size:.9rem}#settings label{margin-left:1em;display:inline-block;font-weight:normal}#settings del{padding:.5em}#settings ins{padding:.5em}#settings option:checked{font-weight:bold}#settings [type=radio],#settings [type=checkbox]{vertical-align:middle}.source{position:absolute;right:1%;top:.2em}@-moz-document url-prefix(){body{height:99%}}td#diff-col div{text-align:justify;white-space:pre-wrap}.ignored{background-color:#ccc;opacity:.7}.triggered{background-color:#1b98f8}.ignored.triggered{background-color:red}.tab-pane-inner#screenshot{text-align:center}.tab-pane-inner#screenshot img{max-width:99%}.pure-form button.reset-margin{margin:0px}.diff-fieldset{display:flex;align-items:center;gap:4px;flex-wrap:wrap}ul#highlightSnippetActions{list-style-type:none;display:flex;align-items:center;justify-content:center;gap:1.5rem;flex-wrap:wrap;padding:0;margin:0}ul#highlightSnippetActions li{display:flex;flex-direction:column;align-items:center;text-align:center;padding:.5rem;gap:.3rem}ul#highlightSnippetActions li button,ul#highlightSnippetActions li a{white-space:nowrap}ul#highlightSnippetActions span{font-size:.8rem;color:var(--color-text-input-description)}#cell-diff-jump-visualiser{display:flex;flex-direction:row;gap:1px;background:var(--color-background);border-radius:3px;overflow-x:auto;position:sticky;top:0;z-index:10;padding-top:1rem;padding-bottom:1rem}#cell-diff-jump-visualiser>div{flex:1;min-width:1px;height:10px;background:var(--color-background-button-cancel);opacity:.3;border-radius:1px;transition:opacity .2s;position:relative}#cell-diff-jump-visualiser>div.deletion{background:#b30000;opacity:1}#cell-diff-jump-visualiser>div.insertion{background:#406619;opacity:1}#cell-diff-jump-visualiser>div.note{background:#406619;opacity:1}#cell-diff-jump-visualiser>div.mixed{background:linear-gradient(to right, #b30000 50%, #406619 50%);opacity:1}#cell-diff-jump-visualiser>div.current-position::after{content:"";position:absolute;bottom:-6px;left:50%;transform:translateX(-50%);width:0;height:0;border-left:4px solid rgba(0,0,0,0);border-right:4px solid rgba(0,0,0,0);border-bottom:4px solid var(--color-text)}#cell-diff-jump-visualiser>div:hover{opacity:.8;cursor:pointer}
+96 -12
View File
@@ -1,5 +1,3 @@
@use "parts/variables";
#diff-ui {
background: var(--color-background);
@@ -28,7 +26,7 @@
}
pre {
white-space: pre-wrap;
white-space: break-spaces;
}
}
@@ -65,7 +63,7 @@ ins {
border-radius: 10px;
margin-bottom: 1em;
color: #fff;
font-size: 80%;
font-size: 0.9rem;
label {
margin-left: 1em;
@@ -130,13 +128,6 @@ td#diff-col div {
}
}
#highlightSnippet {
background: var(--color-background);
padding: 1em;
border-radius: 5px;
background: var(--color-background);
box-shadow: 1px 1px 4px var(--color-shadow-jump);
}
// resets button margin to 0px
.pure-form button.reset-margin {
@@ -148,4 +139,97 @@ td#diff-col div {
align-items: center;
gap: 4px;
flex-wrap: wrap;
}
}
ul#highlightSnippetActions {
list-style-type: none;
display: flex;
align-items: center;
justify-content: center;
gap: 1.5rem;
flex-wrap: wrap;
padding: 0;
margin: 0;
li {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
padding: 0.5rem;
gap: 0.3rem;
button, a {
white-space: nowrap;
}
}
span {
font-size: 0.8rem;
color: var(--color-text-input-description);
}
}
#cell-diff-jump-visualiser {
display: flex;
flex-direction: row;
gap: 1px;
background: var(--color-background);
border-radius: 3px;
overflow-x: auto;
position: sticky;
top: 0;
z-index: 10;
padding-top: 1rem;
padding-bottom: 1rem;
> div {
flex: 1;
min-width: 1px;
height: 10px;
background: var(--color-background-button-cancel);
opacity: 0.3;
border-radius: 1px;
transition: opacity 0.2s;
position: relative;
&.deletion {
background: #b30000; // Red for deletions
opacity: 1;
}
&.insertion {
background: #406619; // Green for insertions
opacity: 1;
}
&.note {
background: #406619; // Orange for changed/notes
opacity: 1;
}
&.mixed {
background: linear-gradient(to right, #b30000 50%, #406619 50%); // Half red, half green
opacity: 1;
}
&.current-position::after {
content: '';
position: absolute;
bottom: -6px;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-bottom: 4px solid var(--color-text);
}
&:hover {
opacity: 0.8;
cursor: pointer;
}
}
}
@@ -1142,4 +1142,9 @@ ul {
// When JavaScript removes display:none, ensure it scrolls into view
scroll-margin-bottom: 10px;
// Center contents horizontally
display: flex;
justify-content: center;
align-items: center;
}
File diff suppressed because one or more lines are too long
+1 -3
View File
@@ -43,7 +43,7 @@
<body class="{{extra_classes}}">
<div class="header">
<div class="pure-menu-fixed" style="width: 100%;">
<div {% if pure_menu_fixed != False %}class="pure-menu-fixed"{% endif %} style="width: 100%;">
<div class="home-menu pure-menu pure-menu-horizontal" id="nav-menu">
{% if has_password and not current_user.is_authenticated %}
@@ -152,8 +152,6 @@
{% endif %}
{% if left_sticky %}
<div class="sticky-tab" id="left-sticky">
<a href="{{url_for('ui.ui_views.preview_page', uuid=uuid)}}">Show current snapshot</a><br>
Visualise <strong>triggers</strong> and <strong>ignored text</strong>
</div>
{% endif %}
{% if right_sticky %}