WIP
This commit is contained in:
@@ -84,7 +84,7 @@ def construct_blueprint(datastore: ChangeDetectionStore, update_q, queuedWatchMe
|
|||||||
has_proxies=datastore.proxy_list,
|
has_proxies=datastore.proxy_list,
|
||||||
has_unviewed=datastore.has_unviewed,
|
has_unviewed=datastore.has_unviewed,
|
||||||
hosted_sticky=os.getenv("SALTED_PASS", False) == False,
|
hosted_sticky=os.getenv("SALTED_PASS", False) == False,
|
||||||
now_time_server=time.time(),
|
now_time_server=round(time.time()),
|
||||||
pagination=pagination,
|
pagination=pagination,
|
||||||
queued_uuids=[q_uuid.item['uuid'] for q_uuid in update_q.queue],
|
queued_uuids=[q_uuid.item['uuid'] for q_uuid in update_q.queue],
|
||||||
search_q=request.args.get('q', '').strip(),
|
search_q=request.args.get('q', '').strip(),
|
||||||
|
|||||||
@@ -102,7 +102,7 @@
|
|||||||
|
|
||||||
{% set is_unviewed = watch.newest_history_key| int > watch.last_viewed and watch.history_n>=2 %}
|
{% set is_unviewed = watch.newest_history_key| int > watch.last_viewed and watch.history_n>=2 %}
|
||||||
{% set checking_now = is_checking_now(watch) %}
|
{% set checking_now = is_checking_now(watch) %}
|
||||||
<tr id="{{ watch.uuid }}" data-watch-uuid="{{ watch.uuid }}" data-history-n="{{ watch.history_n }}"
|
<tr id="{{ watch.uuid }}" data-watch-uuid="{{ watch.uuid }}"
|
||||||
class="{{ loop.cycle('pure-table-odd', 'pure-table-even') }} processor-{{ watch['processor'] }}
|
class="{{ loop.cycle('pure-table-odd', 'pure-table-even') }} processor-{{ watch['processor'] }}
|
||||||
{% if watch.last_error is defined and watch.last_error != False %}error{% endif %}
|
{% if watch.last_error is defined and watch.last_error != False %}error{% endif %}
|
||||||
{% if watch.last_notification_error is defined and watch.last_notification_error != False %}error{% endif %}
|
{% if watch.last_notification_error is defined and watch.last_notification_error != False %}error{% endif %}
|
||||||
@@ -111,7 +111,6 @@
|
|||||||
{% if watch.has_restock_info %} has-restock-info {% if watch['restock']['in_stock'] %}in-stock{% else %}not-in-stock{% endif %} {% else %}no-restock-info{% endif %}
|
{% if watch.has_restock_info %} has-restock-info {% if watch['restock']['in_stock'] %}in-stock{% else %}not-in-stock{% endif %} {% else %}no-restock-info{% endif %}
|
||||||
{% if watch.uuid in queued_uuids %}queued{% endif %}
|
{% if watch.uuid in queued_uuids %}queued{% endif %}
|
||||||
{% if checking_now %}checking-now{% endif %}
|
{% if checking_now %}checking-now{% endif %}
|
||||||
{% if watch.history_n >=2 %}has-history{% endif %}
|
|
||||||
">
|
">
|
||||||
<td class="inline checkbox-uuid" ><input name="uuids" type="checkbox" value="{{ watch.uuid}} " > <span>{{ loop.index+pagination.skip }}</span></td>
|
<td class="inline checkbox-uuid" ><input name="uuids" type="checkbox" value="{{ watch.uuid}} " > <span>{{ loop.index+pagination.skip }}</span></td>
|
||||||
<td class="inline watch-controls">
|
<td class="inline watch-controls">
|
||||||
@@ -144,7 +143,7 @@
|
|||||||
<a href="{{ url_for('settings.settings_page', uuid=watch.uuid) }}#proxies">Try other proxies/location</a>
|
<a href="{{ url_for('settings.settings_page', uuid=watch.uuid) }}#proxies">Try other proxies/location</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{{ url_for('settings.settings_page', uuid=watch.uuid) }}#proxies">Try adding external proxies/locations</a>
|
<a href="{{ url_for('settings.settings_page', uuid=watch.uuid) }}#proxies">Try adding external proxies/locations</a>
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if 'empty result or contain only an image' in watch.last_error %}
|
{% if 'empty result or contain only an image' in watch.last_error %}
|
||||||
<a href="https://github.com/dgtlmoon/changedetection.io/wiki/Detecting-changes-in-images">more help here</a>.
|
<a href="https://github.com/dgtlmoon/changedetection.io/wiki/Detecting-changes-in-images">more help here</a>.
|
||||||
@@ -192,25 +191,40 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{#last_checked becomes fetch-start-time#}
|
{#last_checked becomes fetch-start-time#}
|
||||||
<td class="last-checked" data-timestamp="{{ watch.last_checked }}" {% if checking_now %} data-fetchduration={{ watch.fetch_time }} data-eta_complete="{{ watch.last_checked+watch.fetch_time }}" {% endif %} >
|
<td class="last-checked" data-timestamp="{{ watch.last_checked }}" {% if checking_now %} data-fetchduration={{ watch.fetch_time }} data-eta_complete="{{ watch.last_checked+watch.fetch_time }}" {% endif %} >
|
||||||
{% if checking_now %}
|
|
||||||
<span class="spinner"></span><span> Checking now</span>
|
<span style="display:none;" class="spinner"></span><span class="spinner-text" style="display:none;" > Checking now</span>
|
||||||
|
|
||||||
|
{{watch|format_last_checked_time|safe}}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td class="last-changed" data-timestamp="{{ watch.last_changed }}">{% if watch.history_n >=2 and watch.last_changed >0 %}
|
||||||
|
{{watch.last_changed|format_timestamp_timeago}}
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="timeago">{{watch|format_last_checked_time|safe}} <!-- default --></span>
|
Not yet
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="last-changed" data-timestamp="{{ watch.last_changed }}">
|
|
||||||
<span class="timeago">{% if watch.history_n >=2 and watch.last_changed >0 %}{{watch|format_last_checked_time|safe}}{% else %}Not yet{% endif %}<!-- default --></span>
|
|
||||||
</td>
|
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ url_for('ui.form_watch_checknow', uuid=watch.uuid) }}" class="recheck pure-button pure-button-primary">Recheck</a>
|
<a {% if watch.uuid in queued_uuids %}disabled="true"{% endif %} href="{{ url_for('ui.form_watch_checknow', uuid=watch.uuid, tag=request.args.get('tag')) }}"
|
||||||
<a href="" disabled=disabled class="queue pure-button pure-button-primary" style="display: none;">Queued</a>
|
class="recheck pure-button pure-button-primary">{% if watch.uuid in queued_uuids %}Queued{% else %}Recheck{% endif %}</a>
|
||||||
<a href="{{ url_for('ui.ui_edit.edit_page', uuid=watch.uuid)}}#general" class="edit pure-button pure-button-primary">Edit</a>
|
<a href="{{ url_for('ui.ui_edit.edit_page', uuid=watch.uuid, tag=active_tag_uuid)}}#general" class="pure-button pure-button-primary">Edit</a>
|
||||||
|
|
||||||
{% set open_diff_in_new_tab = datastore.data['settings']['application']['ui'].get('open_diff_in_new_tab') %}
|
{% if watch.history_n >= 2 %}
|
||||||
{% set target_attr = ' target="' ~ watch.uuid ~ '"' if open_diff_in_new_tab else '' %}
|
|
||||||
|
|
||||||
<a href="{{ url_for('ui.ui_views.diff_history_page', uuid=watch.uuid)}}" {{target_attr}} class="history pure-button pure-button-primary diff-link" >History</a>
|
{% set open_diff_in_new_tab = datastore.data['settings']['application']['ui'].get('open_diff_in_new_tab') %}
|
||||||
<a href="{{ url_for('ui.ui_views.preview_page', uuid=watch.uuid)}}" {{target_attr}} class="preview pure-button pure-button-primary" style="display: none;">Preview</a>
|
{% set target_attr = ' target="' ~ watch.uuid ~ '"' if open_diff_in_new_tab else '' %}
|
||||||
|
|
||||||
|
{% if is_unviewed %}
|
||||||
|
<a href="{{ url_for('ui.ui_views.diff_history_page', uuid=watch.uuid, from_version=watch.get_from_version_based_on_last_viewed) }}" {{target_attr}} class="pure-button pure-button-primary diff-link">History</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{ url_for('ui.ui_views.diff_history_page', uuid=watch.uuid)}}" {{target_attr}} class="pure-button pure-button-primary diff-link">History</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
{% if watch.history_n == 1 or (watch.history_n ==0 and watch.error_text_ctime )%}
|
||||||
|
<a href="{{ url_for('ui.ui_views.preview_page', uuid=watch.uuid)}}" {{target_attr}} class="pure-button pure-button-primary">Preview</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -239,4 +253,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -5,16 +5,16 @@ import json
|
|||||||
import time
|
import time
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
|
from changedetectionio.flask_app import _jinja2_filter_datetime
|
||||||
|
|
||||||
|
|
||||||
class ChangeDetectionSocketIO:
|
class ChangeDetectionSocketIO:
|
||||||
def __init__(self, app, datastore):
|
def __init__(self, app, datastore):
|
||||||
self.main_app = app
|
self.main_app = app
|
||||||
self.datastore = datastore
|
self.datastore = datastore
|
||||||
|
|
||||||
# Create a separate app for Socket.IO
|
|
||||||
self.app = Flask(__name__)
|
|
||||||
|
|
||||||
# Use threading mode instead of eventlet
|
# Use threading mode instead of eventlet
|
||||||
self.socketio = SocketIO(self.app,
|
self.socketio = SocketIO(self.main_app,
|
||||||
async_mode='threading',
|
async_mode='threading',
|
||||||
cors_allowed_origins="*",
|
cors_allowed_origins="*",
|
||||||
logger=False,
|
logger=False,
|
||||||
@@ -28,23 +28,7 @@ class ChangeDetectionSocketIO:
|
|||||||
# Just start a background thread to periodically emit watch status
|
# Just start a background thread to periodically emit watch status
|
||||||
self.thread = None
|
self.thread = None
|
||||||
self.thread_lock = threading.Lock()
|
self.thread_lock = threading.Lock()
|
||||||
|
|
||||||
# Set up a simple index route for the Socket.IO app
|
|
||||||
@self.app.route('/')
|
|
||||||
def index():
|
|
||||||
return """
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>ChangeDetection.io Socket.IO Server</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>ChangeDetection.io Socket.IO Server</h1>
|
|
||||||
<p>This is the Socket.IO server for ChangeDetection.io real-time updates.</p>
|
|
||||||
<p>Socket.IO endpoint is available at: <code>/socket.io/</code></p>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"""
|
|
||||||
|
|
||||||
def start_background_task(self):
|
def start_background_task(self):
|
||||||
"""Start the background task if it's not already running"""
|
"""Start the background task if it's not already running"""
|
||||||
with self.thread_lock:
|
with self.thread_lock:
|
||||||
@@ -85,22 +69,17 @@ class ChangeDetectionSocketIO:
|
|||||||
for thread in threads_snapshot:
|
for thread in threads_snapshot:
|
||||||
if hasattr(thread, 'current_uuid') and thread.current_uuid:
|
if hasattr(thread, 'current_uuid') and thread.current_uuid:
|
||||||
currently_checking.append(thread.current_uuid)
|
currently_checking.append(thread.current_uuid)
|
||||||
|
self.socketio.emit("checking_now", list(currently_checking))
|
||||||
|
|
||||||
# Send all watch data periodically
|
# Send all watch data periodically
|
||||||
for uuid, watch in self.datastore.data['watching'].items():
|
for uuid, watch in self.datastore.data['watching'].items():
|
||||||
# Simplified watch data to avoid sending everything
|
# Simplified watch data to avoid sending everything
|
||||||
simplified_data = {
|
simplified_data = {
|
||||||
'uuid': uuid,
|
'uuid': uuid,
|
||||||
'url': watch.get('url', ''),
|
'last_checked': _jinja2_filter_datetime(watch),
|
||||||
'title': watch.get('title', ''),
|
# 'history_n': watch.history_n if hasattr(watch, 'history_n') else 0,
|
||||||
'last_checked': int(watch.get('last_checked', 0)),
|
|
||||||
'last_changed': int(watch.get('newest_history_key', 0)),
|
|
||||||
'history_n': watch.history_n if hasattr(watch, 'history_n') else 0,
|
|
||||||
'unviewed_history': int(watch.get('newest_history_key', 0)) > int(watch.get('last_viewed', 0)) and watch.history_n >=2,
|
|
||||||
'paused': watch.get('paused', False),
|
|
||||||
'checking': uuid in currently_checking
|
|
||||||
}
|
}
|
||||||
watches_data.append(simplified_data)
|
#watches_data.append(simplified_data)
|
||||||
|
|
||||||
# Emit all watch data periodically
|
# Emit all watch data periodically
|
||||||
self.socketio.emit('watch_data', watches_data)
|
self.socketio.emit('watch_data', watches_data)
|
||||||
@@ -123,4 +102,4 @@ class ChangeDetectionSocketIO:
|
|||||||
# Run the Socket.IO server
|
# Run the Socket.IO server
|
||||||
# Use 0.0.0.0 to listen on all interfaces
|
# Use 0.0.0.0 to listen on all interfaces
|
||||||
logger.info(f"Starting Socket.IO server on http://{host}:{port}")
|
logger.info(f"Starting Socket.IO server on http://{host}:{port}")
|
||||||
self.socketio.run(self.app, host=host, port=port, debug=False, use_reloader=False, allow_unsafe_werkzeug=True)
|
self.socketio.run(self.main_app, host=host, port=port, debug=False, use_reloader=False, allow_unsafe_werkzeug=True)
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
$(document).ready(function() {
|
|
||||||
// Global variables for resize functionality
|
|
||||||
let isResizing = false;
|
|
||||||
let initialX, initialLeftWidth;
|
|
||||||
|
|
||||||
// Setup document-wide mouse move and mouse up handlers
|
|
||||||
$(document).on('mousemove', handleMouseMove);
|
|
||||||
$(document).on('mouseup', function() {
|
|
||||||
isResizing = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle mouse move for resizing
|
|
||||||
function handleMouseMove(e) {
|
|
||||||
if (!isResizing) return;
|
|
||||||
|
|
||||||
const $container = $('#filters-and-triggers > div');
|
|
||||||
const containerWidth = $container.width();
|
|
||||||
const diffX = e.clientX - initialX;
|
|
||||||
const newLeftWidth = ((initialLeftWidth + diffX) / containerWidth) * 100;
|
|
||||||
|
|
||||||
// Limit the minimum width percentage
|
|
||||||
if (newLeftWidth > 20 && newLeftWidth < 80) {
|
|
||||||
$('#edit-text-filter').css('flex', `0 0 ${newLeftWidth}%`);
|
|
||||||
$('#text-preview').css('flex', `0 0 ${100 - newLeftWidth}%`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to create and setup the resizer
|
|
||||||
function setupResizer() {
|
|
||||||
// Only proceed if text-preview is visible
|
|
||||||
if (!$('#text-preview').is(':visible')) return;
|
|
||||||
|
|
||||||
// Don't add another resizer if one already exists
|
|
||||||
if ($('#column-resizer').length > 0) return;
|
|
||||||
|
|
||||||
// Create resizer element
|
|
||||||
const $resizer = $('<div>', {
|
|
||||||
class: 'column-resizer',
|
|
||||||
id: 'column-resizer'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Insert before the text preview div
|
|
||||||
const $container = $('#filters-and-triggers > div');
|
|
||||||
if ($container.length) {
|
|
||||||
$resizer.insertBefore('#text-preview');
|
|
||||||
|
|
||||||
// Setup mousedown handler for the resizer
|
|
||||||
$resizer.on('mousedown', function(e) {
|
|
||||||
isResizing = true;
|
|
||||||
initialX = e.clientX;
|
|
||||||
initialLeftWidth = $('#edit-text-filter').width();
|
|
||||||
|
|
||||||
// Prevent text selection during resize
|
|
||||||
e.preventDefault();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup resizer when preview is activated
|
|
||||||
$('#activate-text-preview').on('click', function() {
|
|
||||||
// Give it a small delay to ensure the preview is visible
|
|
||||||
setTimeout(setupResizer, 100);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Also setup resizer when the filters-and-triggers tab is clicked
|
|
||||||
$('#filters-and-triggers-tab a').on('click', function() {
|
|
||||||
// Give it a small delay to ensure everything is loaded
|
|
||||||
setTimeout(setupResizer, 100);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Run the setupResizer function when the page is fully loaded
|
|
||||||
// to ensure it's added if the text-preview is already visible
|
|
||||||
setTimeout(setupResizer, 500);
|
|
||||||
|
|
||||||
// Handle window resize events
|
|
||||||
$(window).on('resize', function() {
|
|
||||||
// Make sure the resizer is added if text-preview is visible
|
|
||||||
if ($('#text-preview').is(':visible')) {
|
|
||||||
setupResizer();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Keep checking if the resizer needs to be added
|
|
||||||
// This ensures it's restored if something removes it
|
|
||||||
setInterval(function() {
|
|
||||||
if ($('#text-preview').is(':visible')) {
|
|
||||||
setupResizer();
|
|
||||||
}
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
// Add a MutationObserver to watch for DOM changes
|
|
||||||
// This will help restore the resizer if it gets removed
|
|
||||||
const observer = new MutationObserver(function(mutations) {
|
|
||||||
mutations.forEach(function(mutation) {
|
|
||||||
if (mutation.type === 'childList' &&
|
|
||||||
$('#text-preview').is(':visible') &&
|
|
||||||
$('#column-resizer').length === 0) {
|
|
||||||
setupResizer();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Start observing the container for DOM changes
|
|
||||||
observer.observe(document.getElementById('filters-and-triggers'), {
|
|
||||||
childList: true,
|
|
||||||
subtree: true
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -14,13 +14,26 @@ $(document).ready(function() {
|
|||||||
socket.on('disconnect', function() {
|
socket.on('disconnect', function() {
|
||||||
console.log('Socket.IO disconnected');
|
console.log('Socket.IO disconnected');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socket.on('checking_now', function(uuid_list) {
|
||||||
|
console.log("Got checking now update");
|
||||||
|
// Remove 'checking-now' class where it should no longer be
|
||||||
|
$('.watch-table tbody tr.checking-now').each(function() {
|
||||||
|
if (!uuid_list.includes($(this).data('watch-uuid'))) {
|
||||||
|
$(this).removeClass('checking-now');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add the class on the rows where it should be
|
||||||
|
uuid_list.forEach(function(uuid) {
|
||||||
|
$('.watch-table tbody tr[data-watch-uuid="' + uuid + '"]').addClass('checking-now');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Listen for periodically emitted watch data
|
// Listen for periodically emitted watch data
|
||||||
socket.on('watch_data', function(watches) {
|
socket.on('watch_data', function(watches) {
|
||||||
console.log('Received watch data updates');
|
/* console.log('Received watch data updates');
|
||||||
|
|
||||||
// First, remove checking-now class from all rows
|
|
||||||
$('.checking-now').removeClass('checking-now');
|
|
||||||
|
|
||||||
// Update all watches with their current data
|
// Update all watches with their current data
|
||||||
watches.forEach(function(watch) {
|
watches.forEach(function(watch) {
|
||||||
@@ -28,12 +41,13 @@ $(document).ready(function() {
|
|||||||
if ($watchRow.length) {
|
if ($watchRow.length) {
|
||||||
updateWatchRow($watchRow, watch);
|
updateWatchRow($watchRow, watch);
|
||||||
}
|
}
|
||||||
});
|
});*/
|
||||||
});
|
});
|
||||||
|
|
||||||
// Function to update a watch row with new data
|
// Function to update a watch row with new data
|
||||||
function updateWatchRow($row, data) {
|
function updateWatchRow($row, data) {
|
||||||
// Update the last-checked time
|
// Update the last-checked time
|
||||||
|
return;
|
||||||
const $lastChecked = $row.find('.last-checked');
|
const $lastChecked = $row.find('.last-checked');
|
||||||
if ($lastChecked.length) {
|
if ($lastChecked.length) {
|
||||||
// Update data-timestamp attribute
|
// Update data-timestamp attribute
|
||||||
@@ -63,60 +77,13 @@ $(document).ready(function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the last-changed time
|
|
||||||
const $lastChanged = $row.find('.last-changed');
|
|
||||||
if ($lastChanged.length && data.last_changed) {
|
|
||||||
// Update data-timestamp attribute
|
|
||||||
$lastChanged.attr('data-timestamp', data.last_changed);
|
|
||||||
|
|
||||||
// Only update the text if we have history
|
|
||||||
if (data.history_n >= 2 && data.last_changed > 0) {
|
|
||||||
let $timeagoSpan = $lastChanged.find('.timeago');
|
|
||||||
|
|
||||||
// If there's no timeago span yet, create one
|
|
||||||
if (!$timeagoSpan.length) {
|
|
||||||
$lastChanged.html('<span class="timeago"></span>');
|
|
||||||
$timeagoSpan = $lastChanged.find('.timeago');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format as timeago
|
|
||||||
if (typeof timeago !== 'undefined') {
|
|
||||||
$timeagoSpan.text(timeago.format(data.last_changed * 1000));
|
|
||||||
} else {
|
|
||||||
// Simple fallback if timeago isn't available
|
|
||||||
const date = new Date(data.last_changed * 1000);
|
|
||||||
$timeagoSpan.text(date.toLocaleString());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$lastChanged.text('Not yet');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Toggle the unviewed class based on viewed status
|
// Toggle the unviewed class based on viewed status
|
||||||
$row.toggleClass('unviewed', data.unviewed_history === false);
|
// $row.toggleClass('unviewed', data.unviewed_history === false);
|
||||||
|
|
||||||
// If the watch is currently being checked
|
// If the watch is currently being checked
|
||||||
$row.toggleClass('checking-now', data.checking === true);
|
// $row.toggleClass('checking-now', data.checking === true);
|
||||||
|
|
||||||
// If a change was detected and not viewed, add highlight effect
|
|
||||||
if (data.history_n > 0 && data.viewed === false) {
|
|
||||||
// Don't add the highlight effect too often
|
|
||||||
if (!$row.hasClass('socket-highlight')) {
|
|
||||||
$row.addClass('socket-highlight');
|
|
||||||
setTimeout(function() {
|
|
||||||
$row.removeClass('socket-highlight');
|
|
||||||
}, 2000);
|
|
||||||
|
|
||||||
console.log('New change detected for:', data.title || data.url);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update any change count indicators if present
|
|
||||||
const $changeCount = $row.find('.change-count');
|
|
||||||
if ($changeCount.length) {
|
|
||||||
$changeCount.text(data.history_n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// If Socket.IO fails to initialize, just log it and continue
|
// If Socket.IO fails to initialize, just log it and continue
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ $(function () {
|
|||||||
if (eta_complete + 2 > nowtimeserver && fetch_duration > 3) {
|
if (eta_complete + 2 > nowtimeserver && fetch_duration > 3) {
|
||||||
const remaining_seconds = Math.abs(eta_complete) - nowtimeserver - 1;
|
const remaining_seconds = Math.abs(eta_complete) - nowtimeserver - 1;
|
||||||
|
|
||||||
let r = (1.0 - (remaining_seconds / fetch_duration)) * 100;
|
let r = Math.round((1.0 - (remaining_seconds / fetch_duration)) * 100);
|
||||||
if (r < 10) {
|
if (r < 10) {
|
||||||
r = 10;
|
r = 10;
|
||||||
}
|
}
|
||||||
@@ -76,7 +76,6 @@ $(function () {
|
|||||||
r = 100;
|
r = 100;
|
||||||
}
|
}
|
||||||
$(this).css('background-size', `${r}% 100%`);
|
$(this).css('background-size', `${r}% 100%`);
|
||||||
//$(this).text(`${r}% remain ${remaining_seconds}`);
|
|
||||||
} else {
|
} else {
|
||||||
$(this).css('background-size', `100% 100%`);
|
$(this).css('background-size', `100% 100%`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1 @@
|
|||||||
// Styles for Socket.IO real-time updates
|
// Styles for Socket.IO real-time updates
|
||||||
|
|
||||||
@keyframes socket-highlight-flash {
|
|
||||||
0% {
|
|
||||||
background-color: rgba(var(--color-change-highlight-rgb), 0);
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
background-color: rgba(var(--color-change-highlight-rgb), 0.4);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
background-color: rgba(var(--color-change-highlight-rgb), 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.socket-highlight {
|
|
||||||
animation: socket-highlight-flash 2s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Animation for the checking-now state
|
|
||||||
@keyframes checking-progress {
|
|
||||||
0% { background-size: 0% 100%; }
|
|
||||||
100% { background-size: 100% 100%; }
|
|
||||||
}
|
|
||||||
|
|
||||||
tr.checking-now .last-checked {
|
|
||||||
background-image: linear-gradient(to right, rgba(0, 120, 255, 0.2) 0%, rgba(0, 120, 255, 0.1) 100%);
|
|
||||||
background-size: 100% 100%;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
animation: checking-progress 10s linear;
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
/* table related */
|
/* table related */
|
||||||
.watch-table {
|
.watch-table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -7,33 +8,15 @@
|
|||||||
&.unviewed {
|
&.unviewed {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
&.has-history {
|
|
||||||
a.preview {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
&.history {
|
|
||||||
display: inline-block !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.error {
|
&.error {
|
||||||
color: var(--color-watch-table-error);
|
color: var(--color-watch-table-error);
|
||||||
}
|
}
|
||||||
&.queued {
|
|
||||||
a.queue {
|
|
||||||
display: inline-block !important;
|
|
||||||
}
|
|
||||||
a.recheck {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
color: var(--color-watch-table-row-text);
|
color: var(--color-watch-table-row-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
td {
|
td {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
&.title-col {
|
&.title-col {
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
@@ -64,120 +47,15 @@
|
|||||||
content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAQElEQVR42qXKwQkAIAxDUUdxtO6/RBQkQZvSi8I/pL4BoGw/XPkh4XigPmsUgh0626AjRsgxHTkUThsG2T/sIlzdTsp52kSS1wAAAABJRU5ErkJggg==);
|
content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAQElEQVR42qXKwQkAIAxDUUdxtO6/RBQkQZvSi8I/pL4BoGw/XPkh4XigPmsUgh0626AjRsgxHTkUThsG2T/sIlzdTsp52kSS1wAAAABJRU5ErkJggg==);
|
||||||
margin: 0 3px 0 5px;
|
margin: 0 3px 0 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Row with 'checking-now' */
|
||||||
|
tr.checking-now {
|
||||||
|
td.last-checked {
|
||||||
|
.spinner, .spinner-text {
|
||||||
|
display: inline-block !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@media only screen and (max-width: 760px),
|
|
||||||
(min-device-width: 768px) and (max-device-width: 800px) {
|
|
||||||
/*
|
|
||||||
Max width before this PARTICULAR table gets nasty
|
|
||||||
This query will take effect for any screen smaller than 760px
|
|
||||||
and also iPads specifically.
|
|
||||||
*/
|
|
||||||
.watch-table {
|
|
||||||
/* make headings work on mobile */
|
|
||||||
thead {
|
|
||||||
display: block;
|
|
||||||
|
|
||||||
tr {
|
|
||||||
th {
|
|
||||||
display: inline-block;
|
|
||||||
// Hide the "Last" text for smaller screens
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.hide-on-mobile {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-cell {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Force table to not be like tables anymore */
|
|
||||||
tbody {
|
|
||||||
td,
|
|
||||||
tr {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tbody {
|
|
||||||
tr {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
|
|
||||||
// The third child of each row will take up the remaining space
|
|
||||||
// This is useful for the URL column, which should expand to fill the remaining space
|
|
||||||
:nth-child(3) {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The last three children (from the end) of each row will take up the full width
|
|
||||||
// This is useful for the "Last Checked", "Last Changed", and the action buttons columns, which should each take up the full width
|
|
||||||
:nth-last-child(-n+3) {
|
|
||||||
flex-basis: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.last-checked {
|
|
||||||
> span {
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.last-checked::before {
|
|
||||||
color: var(--color-last-checked);
|
|
||||||
content: "Last Checked ";
|
|
||||||
}
|
|
||||||
|
|
||||||
.last-changed::before {
|
|
||||||
color: var(--color-last-checked);
|
|
||||||
content: "Last Changed ";
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Force table to not be like tables anymore */
|
|
||||||
td.inline {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pure-table td,
|
|
||||||
.pure-table th {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
/* Behave like a "row" */
|
|
||||||
border: none;
|
|
||||||
border-bottom: 1px solid var(--color-border-watch-table-cell);
|
|
||||||
vertical-align: middle;
|
|
||||||
|
|
||||||
&:before {
|
|
||||||
/* Top/left values mimic padding */
|
|
||||||
top: 6px;
|
|
||||||
left: 6px;
|
|
||||||
width: 45%;
|
|
||||||
padding-right: 10px;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.pure-table-striped {
|
|
||||||
tr {
|
|
||||||
background-color: var(--color-table-background);
|
|
||||||
}
|
|
||||||
|
|
||||||
tr:nth-child(2n-1) {
|
|
||||||
background-color: var(--color-table-stripe);
|
|
||||||
}
|
|
||||||
|
|
||||||
tr:nth-child(2n-1) td {
|
|
||||||
background-color: inherit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
@import "parts/_menu";
|
@import "parts/_menu";
|
||||||
@import "parts/_love";
|
@import "parts/_love";
|
||||||
@import "parts/preview_text_filter";
|
@import "parts/preview_text_filter";
|
||||||
|
@import "parts/_watch_table";
|
||||||
@import "parts/_edit";
|
@import "parts/_edit";
|
||||||
@import "parts/_conditions_table";
|
@import "parts/_conditions_table";
|
||||||
|
|
||||||
@@ -169,56 +170,6 @@ code {
|
|||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* table related */
|
|
||||||
.watch-table {
|
|
||||||
width: 100%;
|
|
||||||
font-size: 80%;
|
|
||||||
|
|
||||||
tr {
|
|
||||||
&.unviewed {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
&.error {
|
|
||||||
color: var(--color-watch-table-error);
|
|
||||||
}
|
|
||||||
color: var(--color-watch-table-row-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
td {
|
|
||||||
white-space: nowrap;
|
|
||||||
&.title-col {
|
|
||||||
word-break: break-all;
|
|
||||||
white-space: normal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
th {
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
a {
|
|
||||||
font-weight: normal;
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
font-weight: bolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.inactive {
|
|
||||||
.arrow {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.title-col a[target="_blank"]::after,
|
|
||||||
.current-diff-url::after {
|
|
||||||
content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAQElEQVR42qXKwQkAIAxDUUdxtO6/RBQkQZvSi8I/pL4BoGw/XPkh4XigPmsUgh0626AjRsgxHTkUThsG2T/sIlzdTsp52kSS1wAAAABJRU5ErkJggg==);
|
|
||||||
margin: 0 3px 0 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-tag {
|
.inline-tag {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
|||||||
@@ -523,6 +523,37 @@ body.preview-text-enabled {
|
|||||||
z-index: 3;
|
z-index: 3;
|
||||||
box-shadow: 1px 1px 4px var(--color-shadow-jump); }
|
box-shadow: 1px 1px 4px var(--color-shadow-jump); }
|
||||||
|
|
||||||
|
/* table related */
|
||||||
|
.watch-table {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 80%;
|
||||||
|
/* Row with 'checking-now' */ }
|
||||||
|
.watch-table tr {
|
||||||
|
color: var(--color-watch-table-row-text); }
|
||||||
|
.watch-table tr.unviewed {
|
||||||
|
font-weight: bold; }
|
||||||
|
.watch-table tr.error {
|
||||||
|
color: var(--color-watch-table-error); }
|
||||||
|
.watch-table td {
|
||||||
|
white-space: nowrap; }
|
||||||
|
.watch-table td.title-col {
|
||||||
|
word-break: break-all;
|
||||||
|
white-space: normal; }
|
||||||
|
.watch-table th {
|
||||||
|
white-space: nowrap; }
|
||||||
|
.watch-table th a {
|
||||||
|
font-weight: normal; }
|
||||||
|
.watch-table th a.active {
|
||||||
|
font-weight: bolder; }
|
||||||
|
.watch-table th a.inactive .arrow {
|
||||||
|
display: none; }
|
||||||
|
.watch-table .title-col a[target="_blank"]::after,
|
||||||
|
.watch-table .current-diff-url::after {
|
||||||
|
content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAQElEQVR42qXKwQkAIAxDUUdxtO6/RBQkQZvSi8I/pL4BoGw/XPkh4XigPmsUgh0626AjRsgxHTkUThsG2T/sIlzdTsp52kSS1wAAAABJRU5ErkJggg==);
|
||||||
|
margin: 0 3px 0 5px; }
|
||||||
|
.watch-table tr.checking-now td.last-checked .spinner, .watch-table tr.checking-now td.last-checked .spinner-text {
|
||||||
|
display: inline-block !important; }
|
||||||
|
|
||||||
ul#conditions_match_logic {
|
ul#conditions_match_logic {
|
||||||
list-style: none; }
|
list-style: none; }
|
||||||
ul#conditions_match_logic input, ul#conditions_match_logic label, ul#conditions_match_logic li {
|
ul#conditions_match_logic input, ul#conditions_match_logic label, ul#conditions_match_logic li {
|
||||||
@@ -735,34 +766,6 @@ code {
|
|||||||
background: var(--color-background-code);
|
background: var(--color-background-code);
|
||||||
color: var(--color-text); }
|
color: var(--color-text); }
|
||||||
|
|
||||||
/* table related */
|
|
||||||
.watch-table {
|
|
||||||
width: 100%;
|
|
||||||
font-size: 80%; }
|
|
||||||
.watch-table tr {
|
|
||||||
color: var(--color-watch-table-row-text); }
|
|
||||||
.watch-table tr.unviewed {
|
|
||||||
font-weight: bold; }
|
|
||||||
.watch-table tr.error {
|
|
||||||
color: var(--color-watch-table-error); }
|
|
||||||
.watch-table td {
|
|
||||||
white-space: nowrap; }
|
|
||||||
.watch-table td.title-col {
|
|
||||||
word-break: break-all;
|
|
||||||
white-space: normal; }
|
|
||||||
.watch-table th {
|
|
||||||
white-space: nowrap; }
|
|
||||||
.watch-table th a {
|
|
||||||
font-weight: normal; }
|
|
||||||
.watch-table th a.active {
|
|
||||||
font-weight: bolder; }
|
|
||||||
.watch-table th a.inactive .arrow {
|
|
||||||
display: none; }
|
|
||||||
.watch-table .title-col a[target="_blank"]::after,
|
|
||||||
.watch-table .current-diff-url::after {
|
|
||||||
content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAQElEQVR42qXKwQkAIAxDUUdxtO6/RBQkQZvSi8I/pL4BoGw/XPkh4XigPmsUgh0626AjRsgxHTkUThsG2T/sIlzdTsp52kSS1wAAAABJRU5ErkJggg==);
|
|
||||||
margin: 0 3px 0 5px; }
|
|
||||||
|
|
||||||
.inline-tag, .watch-tag-list, .tracking-ldjson-price-data, .restock-label {
|
.inline-tag, .watch-tag-list, .tracking-ldjson-price-data, .restock-label {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
|||||||
Reference in New Issue
Block a user