Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions www/controllers/Service/Unit/Timelapse.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@ public function run(): void
{
parent::log('Starting timelapse');

// Get list of cameras
$cameras = $this->cameraController->get();

while (true) {
// Always retrieve timelapse interval before running, in case it has been changed by the user
$timelapseInterval = parent::getSettings('Timelapse_interval');
Expand All @@ -44,6 +41,9 @@ public function run(): void
sleep($nextInterval - time());
}

// Get list of cameras
$cameras = $this->cameraController->get();

// For each camera, if timelapse is enabled, execute timelapse
foreach ($cameras as $camera) {
try {
Expand Down Expand Up @@ -73,7 +73,7 @@ public function run(): void

parent::log('Capture timelapse for camera #' . $camera['Id'] . ' (' . $configuration['name'] . ')');

$myprocess = new \Controllers\Process('/usr/bin/ffmpeg -rw_timeout 3000000 -loglevel error -i http://127.0.0.1:1984/api/frame.jpeg?src=camera_' . $camera['Id'] . ' -c:v libsvtav1 -crf 30 -preset 10 -threads 1 ' . $targetDir . '/timelapse_' . date('H-i-s') . '.avif');
$myprocess = new \Controllers\Process('/usr/bin/ffmpeg -rw_timeout 3000000 -loglevel error -i http://127.0.0.1:1984/api/frame.jpeg?src=camera_' . $camera['Id'] . ' -c:v libsvtav1 -crf 30 -preset 10 ' . $targetDir . '/timelapse_' . date('H-i-s') . '.avif');
$myprocess->execute();
$myprocess->close();

Expand All @@ -86,6 +86,7 @@ public function run(): void
}
}

// Just for safety
sleep(1);
}
}
Expand Down
61 changes: 50 additions & 11 deletions www/public/resources/js/classes/EChart.js
Original file line number Diff line number Diff line change
Expand Up @@ -1171,13 +1171,16 @@ class EChart
// Set options and render
chart.setOption(options);

// Force zoom reset if period changed
// Force zoom reset if period changed - this ensures consistent behavior
if (this._periodChanged) {
chart.dispatchAction({
type: 'dataZoom',
start: 0,
end: 100
});
// Add a small delay to ensure the chart is fully rendered
setTimeout(() => {
chart.dispatchAction({
type: 'dataZoom',
start: 0,
end: 100
});
}, 50);
}

// Remove spinner
Expand Down Expand Up @@ -1376,18 +1379,28 @@ EChart.destroyInstance = function(chartId) {
* @param {*} id
* @param {*} autoUpdate
* @param {*} autoUpdateInterval
* @param {*} days
* @param {*} providedDays - Number of days for the chart. If null, preserves the current chart's days value.
*/
EChart.recreate = function(type, id, autoUpdate = true, autoUpdateInterval = 15000, days = 1) {
EChart.recreate = function(type, id, autoUpdate = true, autoUpdateInterval = 15000, providedDays = null) {
// Check if existing chart was in natural state before destroying
let wasInNaturalState = true;
let periodChanged = false;
let preservedCurrentType = type; // Default to original type
let instance = null; // Declare instance variable
let days = providedDays; // The final days value to use

try {
instance = EChart.instances[id];

// If no days specified, try to preserve current chart's days value
if (providedDays === null && instance) {
days = instance.days;
console.info('EChart.recreate: preserving current days value:', days);
} else if (providedDays === null) {
// Fallback if no instance exists
days = 1;
}

// Get current chart element for zoom state check
const chartElement = document.querySelector("#" + id);
if (chartElement && chartElement._chartInstance && instance) {
Expand All @@ -1396,19 +1409,29 @@ EChart.recreate = function(type, id, autoUpdate = true, autoUpdateInterval = 150
wasInNaturalState = instance.isInNaturalState(currentOption.dataZoom);
}

// Check if period has changed (using Number for comparison)
// Preserve the current chart type (may have been changed by magicType)
preservedCurrentType = instance.currentType || instance.type;
}

// Check if period has changed - do this after days value is determined
if (instance) {
const oldDays = Number(instance.days);
const newDays = Number(days);
periodChanged = oldDays !== newDays;

// Preserve the current chart type (may have been changed by magicType)
preservedCurrentType = instance.currentType || instance.type;
if (periodChanged) {
console.info('EChart.recreate: period changed from', oldDays, 'to', newDays, '- will reset zoom');
}
}
} catch (error) {
console.warn('EChart.recreate: Error checking zoom state, defaulting to natural state', error);
wasInNaturalState = true;
periodChanged = false;
preservedCurrentType = type;
// Ensure we have a fallback days value
if (days === null) {
days = 1;
}
}

if (EChart.destroyInstance(id)) {
Expand Down Expand Up @@ -1465,4 +1488,20 @@ $(document).ready(function () {
// Destroy and recreate chart with new days value (pass as number)
EChart.recreate(type, chartId, true, 15000, days);
});

// Listen for changes on any select element with class 'echart-range'
// TODO: ranges
// $('select.echart-range').on('change', function() {
// // Get chart Id
// const chartId = $(this).attr('chart-id');

// // Get selected value - convert to number to match internal storage
// const range = Number($(this).val());

// // Get chart type from 'type' or 'chart-type' attribute, default to 'line' if not specified
// const type = $('#' + chartId).attr('type') || $('#' + chartId).attr('chart-type') || 'line';

// // Destroy and recreate chart with new days value (pass as number)
// EChart.recreate(type, chartId, true, 15000, days);
// });
});
13 changes: 8 additions & 5 deletions www/public/resources/js/classes/Motion/Media.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class Media

// Get selected media Id
$(checkboxes).each(function () {
files.push({ fileId: $(this).attr('file-id'), filename: $(this).attr('file-name') });
files.push($(this).attr('file-name'));
});

// Append a temporary <a> element to download files
Expand All @@ -19,12 +19,15 @@ class Media
document.body.appendChild(temporaryDownloadLink);

for (var n = 0; n < files.length; n++) {
var download = files[n];
var path = files[n];
var filename = path.split('/').pop();

// Set the href attribute to the file path, also include the filename as a query parameter for the Android app
temporaryDownloadLink.setAttribute('href', '/media/' + path + '?filename=' + encodeURIComponent(filename));

// Set the href attribute to the file path, also include the filename for the android app to make sure it downloads the file with the correct name
temporaryDownloadLink.setAttribute('href', '/media?id=' + download.fileId + '&filename=' + download.filename);
// Set the download attribute to force download
temporaryDownloadLink.setAttribute('download', download.filename);
temporaryDownloadLink.setAttribute('download', filename);

// Trigger click on the temporary <a> element to start download
temporaryDownloadLink.click();
}
Expand Down
4 changes: 1 addition & 3 deletions www/public/resources/js/motion.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,9 @@ function eventDateSelect(dateStart, dateEnd)
* Event: vizualize event image
*/
$(document).on('click','.play-picture-btn',function () {
var fileId = $(this).attr('file-id');

html = '<div id="fullscreen">'
+ '<div class="flex align-item-center">'
+ '<img src="/media?id=' + fileId + '" title="Full screen event picture" />'
+ '<img src="' + $(this).attr('src') + '" title="Full screen event picture" />'
+ '</div>'
+ '<div class="flex align-item-center justify-center">'
+ '<img src="/assets/icons/close.svg" class="close-fullscreen-btn pointer lowopacity" title="Close fullscreen">'
Expand Down
21 changes: 21 additions & 0 deletions www/public/resources/styles/components/input.css
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,27 @@ input[type=checkbox].checkbox-warning:not(.onoff-switch-input) {
}
}

.input-daterangepicker {
width: 250px;
height: 35px;
margin-top: 4px;
margin-bottom: 4px;
padding: 0 10px;
border: none;
border-radius: 20px;
box-sizing: border-box;
font-size: 14px;
color: white;
background-color:#22384F;
}

.input-daterangepicker p {
line-height: 35px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

/* Textarea */
textarea {
width: 100%;
Expand Down
1 change: 1 addition & 0 deletions www/public/resources/styles/components/label.css
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
column-gap: 10px;
font-size: 12px;
padding: 4px 10px;
margin: 1px;
min-width: 50px;
min-height: 12px;
border-radius: 60px;
Expand Down
2 changes: 1 addition & 1 deletion www/version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
6.11.0
6.11.1
2 changes: 1 addition & 1 deletion www/views/includes/camera/edit/motion-config-form.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
<div class="flex align-item-center column-gap-10">
<h6 class="margin-top-0 <?= $required ?>"><?= $attributes['title'] ?></h6>
<code><?= $param ?></code>
<a href="https://motion-project.github.io/motionplus_config.html#<?= $param ?>" target="_blank" title="Open official Motion documentation for this parameter"><img src="/assets/icons/external-link.svg" class="icon-small mediumopacity" /></a>
<a href="https://motion-project.github.io/motionv5_config.html#<?= $param ?>" target="_blank" title="Open official Motion documentation for this parameter"><img src="/assets/icons/external-link.svg" class="icon-small mediumopacity" /></a>
</div>

<p class="note param-description" param-name="<?= $param ?>" title="<?= $attributes['description'] ?>"><?= $attributes['description'] ?></p>
Expand Down
8 changes: 4 additions & 4 deletions www/views/includes/tables/motion/events.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -170,19 +170,19 @@
<?php
} else {
$file = str_replace(CAPTURES_DIR . '/', '', $eventDetails['File']); ?>
<img src="/media/<?= $file ?>" class="play-picture-btn pointer" file-id="<?= $eventDetails['Id'] ?>" title="Visualize picture" />
<img src="/media/<?= $file ?>" class="play-picture-btn pointer" title="Visualize picture" />
<?php
} ?>

<div class="event-media-file-number">
<p class="font-size-11">#<?= $fileNumberCounter ?></p>
<p class="font-size-13">#<?= $fileNumberCounter ?></p>
</div>

<div class="event-media-checkbox-container">
<?php
if (file_exists($eventDetails['File'])) {
if (is_writeable($eventDetails['File'])) {
echo '<input type="checkbox" class="event-media-checkbox" file-name="' . basename($eventDetails['File']) . '" file-id="' . $eventDetails['Id'] . '" event-id="' . $eventId . '" title="Select media file" />';
echo '<input type="checkbox" class="event-media-checkbox" file-name="' . $file . '" file-id="' . $eventDetails['Id'] . '" event-id="' . $eventId . '" title="Select media file" />';
} else {
echo '<img src="/assets/icons/warning.svg" class="icon" title="File cannot be selected: not writeable" />';
}
Expand Down Expand Up @@ -254,7 +254,7 @@
<?php
if (file_exists($eventDetails['File'])) {
if (is_writeable($eventDetails['File'])) {
echo '<input type="checkbox" class="event-media-checkbox" file-name="' . basename($eventDetails['File']) . '" file-id="' . $eventDetails['Id'] . '" event-id="' . $eventId . '" title="Select media file" />';
echo '<input type="checkbox" class="event-media-checkbox" file-name="' . $file . '" file-id="' . $eventDetails['Id'] . '" event-id="' . $eventId . '" title="Select media file" />';
} else {
echo '<img src="/assets/icons/warning.svg" class="icon" title="File cannot be selected: not writeable" />';
}
Expand Down
Loading