Release v2.14.1
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile, web, pulse-signage-web) (push) Successful in 1m53s
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile.player, player, pulse-signage-player) (push) Successful in 32s

This commit is contained in:
lzstealth committed 2026-10-11 18:54:43 +01:00
1 parent 635e1a9942
commit c3299abaac
174 files changed
+4483 -1687

No files matched your search

-6
View File
@@ -162,12 +162,6 @@
width: 100%;
height: 100%;
object-fit: cover;
transition: transform .2s ease;
}
.media-gallery-preview:hover img,
.media-gallery-preview:hover video {
transform: scale(1.03);
}
.media-gallery-preview:focus-visible {
+138
View File
@@ -204,6 +204,87 @@
}
}
function queueUploadCleanup(input, uploadPath) {
var path = String(uploadPath || '').trim();
var cleanupUrl = String(input && input.dataset.uploadCleanupUrl || '').trim();
if (!path || !cleanupUrl) {
return;
}
var payload = JSON.stringify({ uploadPaths: [path] });
if (navigator.sendBeacon) {
try {
navigator.sendBeacon(cleanupUrl, new Blob([payload], { type: 'application/json' }));
return;
} catch (_error) {
// Fall through to fetch when sendBeacon is unavailable.
}
}
fetch(cleanupUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
body: payload,
credentials: 'same-origin',
keepalive: true
}).catch(function () {});
}
async function uploadSelectedFile(input, file) {
var uploadUrl = String(input.dataset.uploadUrl || '').trim();
var hidden = document.getElementById(String(input.dataset.uploadHidden || '').trim());
if (!uploadUrl || !hidden || !file) {
return;
}
var maxBytes = Number(input.dataset.imageMaxBytes || 0);
if (maxBytes > 0 && file.size > maxBytes) {
input.value = '';
window.PulseUpload.showMessage('This file is larger than the current upload limit of ' + window.PulseUpload.formatUploadLimit(maxBytes) + '.');
return;
}
var formData = new FormData();
formData.append('file', file, file.name || 'upload');
input.setCustomValidity('Upload is still in progress.');
try {
var response = await fetch(uploadUrl, {
method: 'POST',
body: formData,
credentials: 'same-origin',
headers: { Accept: 'application/json', 'X-Requested-With': 'XMLHttpRequest' }
});
var payload = await response.json().catch(function () { return {}; });
if (!response.ok || !payload.path) {
throw new Error(payload.error || 'Unable to upload media.');
}
var previousPath = String(input.dataset.uploadedPath || '').trim();
if (previousPath && previousPath !== payload.path) {
queueUploadCleanup(input, previousPath);
}
input.dataset.uploadedPath = payload.path;
input.dataset.uploadedNeedsCleanup = '1';
input.value = '';
input.setCustomValidity('');
hidden.value = payload.path;
hidden.dispatchEvent(new Event('change', { bubbles: true }));
updatePreview(hidden, payload.path, 'image');
} catch (error) {
input.value = '';
var currentPath = String(hidden.value || '').trim();
var backgroundPreview = document.getElementById('background-preview');
var backgroundEmpty = document.getElementById('background-empty');
if (currentPath) {
updatePreview(hidden, currentPath, 'image');
} else if (backgroundPreview) {
backgroundPreview.removeAttribute('src');
backgroundPreview.style.display = 'none';
if (backgroundEmpty) backgroundEmpty.style.display = 'flex';
}
input.setCustomValidity('');
window.PulseUpload.showMessage(error.message || 'Unable to upload media.');
} finally {
}
}
document.addEventListener('click', function (event) {
var uploadTrigger = event.target && event.target.closest ? event.target.closest('[data-media-picker-upload]') : null;
if (uploadTrigger) {
@@ -247,8 +328,46 @@
updateSelection();
});
document.addEventListener('click', function (event) {
var removeBackground = event.target && event.target.closest ? event.target.closest('#remove-background-image') : null;
if (!removeBackground) {
return;
}
var input = document.getElementById('background-image');
if (input && input.dataset.uploadedNeedsCleanup === '1') {
queueUploadCleanup(input, input.dataset.uploadedPath);
delete input.dataset.uploadedNeedsCleanup;
delete input.dataset.uploadedPath;
}
});
if (search) search.addEventListener('input', resetVisibleItemLimit);
if (sort) sort.addEventListener('change', resetVisibleItemLimit);
document.addEventListener('change', function (event) {
var input = event.target;
if (!input || input.type !== 'file' || !input.dataset) {
return;
}
var file = input.files && input.files[0];
if (input.dataset.uploadUrl) {
input.setCustomValidity('');
if (file) {
uploadSelectedFile(input, file);
}
return;
}
if (!input.dataset.uploadMaxBytes) {
return;
}
var maxBytes = Number(input.dataset.uploadMaxBytes);
if (!file || !Number.isFinite(maxBytes) || maxBytes <= 0 || file.size <= maxBytes) {
input.setCustomValidity('');
return;
}
input.value = '';
input.setCustomValidity('Image must be ' + Math.round(maxBytes / 1024 / 1024) + ' MB or smaller.');
input.reportValidity();
});
if (loadMoreButton) loadMoreButton.addEventListener('click', async function () {
loadMoreButton.disabled = true;
try {
@@ -274,12 +393,31 @@
closePicker();
return;
}
var input = getActiveFileInput();
if (input && input.dataset.uploadedNeedsCleanup === '1' && input.dataset.uploadedPath !== pendingPath) {
queueUploadCleanup(input, input.dataset.uploadedPath);
delete input.dataset.uploadedNeedsCleanup;
delete input.dataset.uploadedPath;
}
hidden.value = pendingPath;
updatePreview(hidden, pendingPath, pendingType);
hidden.dispatchEvent(new Event('change', { bubbles: true }));
closePicker();
});
if (cancelButton) cancelButton.addEventListener('click', closePicker);
document.querySelectorAll('input[type="file"][data-upload-url]').forEach(function (input) {
var form = input.form;
if (form) {
form.addEventListener('submit', function () {
delete input.dataset.uploadedNeedsCleanup;
});
}
});
window.addEventListener('beforeunload', function () {
document.querySelectorAll('input[type="file"][data-upload-url][data-uploaded-needs-cleanup="1"]').forEach(function (input) {
queueUploadCleanup(input, input.dataset.uploadedPath);
});
});
modal.addEventListener('hidden.bs.modal', function () {
activeButton = null;
currentPath = '';
+257
View File
@@ -0,0 +1,257 @@
// Normalize chart configurations and source snapshots for editor previews and player playback.
(function () {
var root = window;
var allowedTypes = ['line', 'bar', 'doughnut', 'pie'];
function getPath(value, path) {
var current = value;
var parts = String(path || '').trim().replace(/\[([0-9]+)\]/g, '.$1').split('.').filter(Boolean);
for (var index = 0; index < parts.length; index += 1) {
if (current === undefined || current === null) {
return undefined;
}
current = current[parts[index]];
}
return current;
}
function toNumber(value) {
if (typeof value === 'number') {
return Number.isFinite(value) ? value : null;
}
var normalized = String(value === undefined || value === null ? '' : value).replace(/,/g, '').trim();
if (!normalized) {
return null;
}
var parsed = Number(normalized);
return Number.isFinite(parsed) ? parsed : null;
}
function normalizeChartType(value) {
var type = String(value || 'bar').trim().toLowerCase();
return allowedTypes.indexOf(type) !== -1 ? type : 'bar';
}
function getApiItems(response, itemsPath) {
var selected = itemsPath ? getPath(response, itemsPath) : response;
if (Array.isArray(selected)) {
return selected;
}
if (Array.isArray(response)) {
return response;
}
if (response && Array.isArray(response.items)) return response.items;
if (response && Array.isArray(response.results)) return response.results;
if (response && Array.isArray(response.data)) return response.data;
return response && typeof response === 'object' ? [response] : [];
}
function normalizeRows(rows, mapping) {
var labels = [];
var values = [];
var labelPath = String(mapping && (mapping.label || mapping.label_path) || 'label').trim();
var valuePath = String(mapping && (mapping.value || mapping.value_path) || 'value').trim();
(Array.isArray(rows) ? rows : []).forEach(function (row, index) {
var label = getPath(row, labelPath);
var value = toNumber(getPath(row, valuePath));
if (value === null) {
return;
}
labels.push(label === undefined || label === null || label === '' ? String(index + 1) : String(label));
values.push(value);
});
return { labels: labels, values: values };
}
function normalizeConfig(content) {
var current = content && typeof content === 'object' ? content : {};
var mapping = current.mapping && typeof current.mapping === 'object' ? current.mapping : {};
var rawDatasets = Array.isArray(current.datasets) ? current.datasets : [];
var datasets = rawDatasets.slice(0, 12).map(function (dataset, index) {
var item = dataset && typeof dataset === 'object' ? dataset : {};
return {
label: String(item.label || 'Series ' + (index + 1)).trim() || 'Series ' + (index + 1),
value_path: String(item.value_path || item.value || '').trim(),
color: /^#[0-9a-fA-F]{3,8}$/.test(String(item.color || '').trim()) ? String(item.color).trim() : ''
};
}).filter(function (dataset) { return dataset.value_path; });
var palette = Array.isArray(current.colors)
? current.colors
: String(current.color_palette || '').split(',');
palette = palette.map(function (color) {
return String(color || '').trim();
}).filter(function (color) {
return /^#[0-9a-fA-F]{3,8}$/.test(color);
}).slice(0, 24);
var fallbackColor = /^#[0-9a-fA-F]{3,8}$/.test(String(current.color || '').trim()) ? String(current.color).trim() : '#36a269';
var chartOptions = current.chart_options && typeof current.chart_options === 'object' ? current.chart_options : {};
var sourceOptions = current.source_options && typeof current.source_options === 'object' ? current.source_options : {};
return {
type: normalizeChartType(current.chart_type || current.chartType),
source_kind: String(current.source_kind || current.sourceKind || 'manual').trim().toLowerCase() === 'api' ? 'api' : 'manual',
source_id: current.source_id === undefined || current.source_id === '' ? '' : Number(current.source_id),
items_path: String(current.items_path || '').trim(),
label_path: String(current.label_path || mapping.label || '').trim(),
value_path: String(current.value_path || mapping.value || '').trim(),
dataset_label: String(current.dataset_label || 'Value').trim() || 'Value',
color: fallbackColor,
datasets: datasets,
colors: palette.length ? palette : [fallbackColor, '#3b82f6', '#f59e0b', '#ef4444', '#8b5cf6', '#14b8a6'],
manual_rows: Array.isArray(current.manual_rows) ? current.manual_rows : [],
chart_options: {
legend: ['automatic', 'show', 'hide'].indexOf(String(chartOptions.legend || 'automatic')) !== -1 ? String(chartOptions.legend || 'automatic') : 'automatic',
begin_at_zero: chartOptions.begin_at_zero !== false,
y_axis_min: chartOptions.y_axis_min === undefined || chartOptions.y_axis_min === '' ? '' : Number(chartOptions.y_axis_min),
y_axis_max: chartOptions.y_axis_max === undefined || chartOptions.y_axis_max === '' ? '' : Number(chartOptions.y_axis_max),
line_points: ['automatic', 'show', 'hide'].indexOf(String(chartOptions.line_points || 'automatic')) !== -1 ? String(chartOptions.line_points || 'automatic') : 'automatic',
line_fill: chartOptions.line_fill === true,
bar_orientation: chartOptions.bar_orientation === 'horizontal' ? 'horizontal' : 'vertical',
value_format: ['number', 'percent', 'currency'].indexOf(String(chartOptions.value_format || 'number')) !== -1 ? String(chartOptions.value_format || 'number') : 'number',
decimal_places: Math.max(0, Math.min(4, Number(chartOptions.decimal_places) || 0)),
text_size: Math.max(8, Math.min(48, Number(chartOptions.text_size) || 12)),
grid_color: /^#[0-9a-fA-F]{6}$/.test(String(chartOptions.grid_color || '').trim()) ? String(chartOptions.grid_color).trim() : '#000000',
text_color: /^#[0-9a-fA-F]{6}$/.test(String(chartOptions.text_color || '').trim()) ? String(chartOptions.text_color).trim() : '#000000'
},
source_options: {
sort: ['none', 'reverse-original', 'value-ascending', 'value-descending'].indexOf(String(sourceOptions.sort || 'none')) !== -1 ? String(sourceOptions.sort || 'none') : 'none',
limit: Math.max(0, Math.min(500, Number(sourceOptions.limit) || 0)),
missing: ['skip', 'zero', 'gap'].indexOf(String(sourceOptions.missing || 'gap')) !== -1 ? String(sourceOptions.missing || 'gap') : 'gap'
}
};
}
function isColor(value) {
return /^#[0-9a-fA-F]{3,8}$/.test(String(value || '').trim());
}
function getSeries(config) {
if (config.type === 'line' && config.datasets.length) {
return config.datasets;
}
var firstDataset = config.datasets[0] || {};
return [{
label: config.dataset_label,
value_path: config.value_path || firstDataset.value_path || 'value',
color: config.color || firstDataset.color
}];
}
function normalizeSeries(rows, config) {
var series = getSeries(config);
var labels = [];
var values = series.map(function () { return []; });
var colors = [];
var sourceOptions = config.source_options || {};
var sourceRows = (Array.isArray(rows) ? rows : []).slice();
if (sourceOptions.sort !== 'none' && sourceOptions.sort !== 'reverse-original') {
sourceRows.sort(function (left, right) {
var firstSeries = series[0] || {};
var leftValue = toNumber(getPath(left, firstSeries.value_path));
var rightValue = toNumber(getPath(right, firstSeries.value_path));
if (leftValue === null) return 1;
if (rightValue === null) return -1;
return (leftValue - rightValue) * (sourceOptions.sort === 'value-descending' ? -1 : 1);
});
}
if (sourceOptions.sort === 'reverse-original') sourceRows.reverse();
if (sourceOptions.limit > 0) sourceRows = sourceRows.slice(0, sourceOptions.limit);
sourceRows.forEach(function (row, rowIndex) {
var rowValues = series.map(function (dataset) {
return toNumber(getPath(row, dataset.value_path));
});
if (!rowValues.some(function (value) { return value !== null; })) {
return;
}
if (sourceOptions.missing === 'skip' && rowValues.some(function (value) { return value === null; })) {
return;
}
if (sourceOptions.missing === 'zero') {
rowValues = rowValues.map(function (value) { return value === null ? 0 : value; });
}
var label = getPath(row, config.label_path || 'label');
labels.push(label === undefined || label === null || label === '' ? String(rowIndex + 1) : String(label));
rowValues.forEach(function (value, datasetIndex) {
values[datasetIndex].push(value);
});
colors.push(isColor(row && row.color) ? String(row.color).trim() : config.colors[rowIndex % config.colors.length]);
});
return { labels: labels, values: values, colors: colors };
}
function fromContent(content, sources) {
var config = normalizeConfig(content);
var rows = config.manual_rows;
if (config.source_kind === 'api') {
var source = (Array.isArray(sources) ? sources : []).find(function (entry) {
return Number(entry.id) === Number(config.source_id);
});
rows = getApiItems(source && source.responseJson, config.items_path || source && (source.items_path || source.itemsPath) || '');
}
var normalizedRows = normalizeSeries(rows, config);
var series = getSeries(config);
var linePointRadius = config.chart_options.line_points === 'show' ? 3 : config.chart_options.line_points === 'hide' ? 0 : undefined;
return {
type: config.type,
options: { chart_options: config.chart_options },
labels: normalizedRows.labels,
datasets: series.map(function (dataset, index) {
var color = dataset.color || config.colors[index % config.colors.length];
var pointColors = config.type === 'bar' || config.type === 'pie' || config.type === 'doughnut' ? normalizedRows.colors : null;
return {
label: dataset.label,
data: normalizedRows.values[index],
backgroundColor: pointColors || color,
borderColor: pointColors || color,
borderWidth: 2,
tension: 0.25,
fill: config.type === 'line' && config.chart_options.line_fill,
pointRadius: linePointRadius
};
})
};
}
function getChartOptions(chartData) {
var chartOptions = chartData.options && chartData.options.chart_options || {};
var textSize = Math.max(8, Math.min(48, Number(chartOptions.text_size) || 12));
var legendDefault = chartData.type !== 'bar' && (chartData.type !== 'line' || (chartData.datasets || []).length > 1);
var legendDisplay = chartOptions.legend === 'show' ? true : chartOptions.legend === 'hide' ? false : legendDefault;
var scales = {};
var ticks = { color: chartOptions.text_color || '#000000', font: { size: textSize } };
if (chartData.type !== 'pie' && chartData.type !== 'doughnut') {
var valueScale = { beginAtZero: chartOptions.begin_at_zero !== false, grid: { color: chartOptions.grid_color || '#000000' }, ticks: ticks };
if (chartOptions.y_axis_min !== '') valueScale.min = chartOptions.y_axis_min;
if (chartOptions.y_axis_max !== '') valueScale.max = chartOptions.y_axis_max;
scales[chartData.type === 'bar' && chartOptions.bar_orientation === 'horizontal' ? 'x' : 'y'] = valueScale;
if (chartData.type === 'bar' && chartOptions.bar_orientation === 'horizontal') scales.y = { beginAtZero: true, grid: { color: chartOptions.grid_color || '#000000' }, ticks: ticks };
if (!scales.x) scales.x = { grid: { color: chartOptions.grid_color || '#000000' }, ticks: ticks };
if (!scales.y) scales.y = { grid: { color: chartOptions.grid_color || '#000000' }, ticks: ticks };
}
var tooltipLabel = function (context) {
var value = context.parsed && (context.parsed.y === undefined ? context.parsed : context.parsed.y);
var decimals = Number(chartOptions.decimal_places) || 0;
if (chartOptions.value_format === 'percent') return Number(value).toFixed(decimals) + '%';
if (chartOptions.value_format === 'currency') return '$' + Number(value).toFixed(decimals);
return Number(value).toLocaleString(undefined, { maximumFractionDigits: decimals });
};
return {
responsive: true,
maintainAspectRatio: false,
animation: false,
indexAxis: chartData.type === 'bar' && chartOptions.bar_orientation === 'horizontal' ? 'y' : 'x',
color: chartOptions.text_color || '#000000',
font: { size: textSize },
plugins: { legend: { display: legendDisplay, labels: { color: chartOptions.text_color || '#000000', font: { size: textSize } } }, tooltip: { titleColor: chartOptions.text_color || '#000000', bodyColor: chartOptions.text_color || '#000000', footerColor: chartOptions.text_color || '#000000', titleFont: { size: textSize }, bodyFont: { size: textSize }, footerFont: { size: textSize }, callbacks: { label: tooltipLabel } } },
scales: scales
};
}
root.pulseChartData = {
getPath: getPath,
normalizeConfig: normalizeConfig,
normalizeRows: normalizeRows,
fromContent: fromContent,
getChartOptions: getChartOptions
};
}());
+271
View File
@@ -0,0 +1,271 @@
// Chart region editor and preview helpers.
(function () {
var registry = window.pulseRegionTypes;
var chartData = window.pulseChartData;
var defaultSeriesColors = ['#36a269', '#3b82f6', '#f59e0b', '#ef4444', '#8b5cf6', '#14b8a6'];
function escapeHtml(value) {
return window.pulseRegionUtils && window.pulseRegionUtils.escapeHtml
? window.pulseRegionUtils.escapeHtml(value)
: String(value === undefined || value === null ? '' : value);
}
function parseRows(value) {
try {
var parsed = JSON.parse(String(value || '[]'));
return Array.isArray(parsed) ? parsed : [];
} catch (_error) {
return [];
}
}
function getCurrentConfig(region, existingContent) {
var current = existingContent && existingContent[region.region_key] ? existingContent[region.region_key] : {};
var config = chartData.normalizeConfig(current);
config.manual_rows_json = JSON.stringify(config.manual_rows, null, 2);
config.datasets_json = JSON.stringify(config.datasets, null, 2);
return config;
}
function renderPreview(region, content, context) {
var normalized = chartData.fromContent(content || {}, context && context.sources || []);
var chartId = 'chart-preview-' + region.id;
var label = escapeHtml((content && content.dataset_label) || 'Value');
return '<div class="template-region chart" style="width:100%;height:100%;overflow:hidden;position:relative;">' +
'<canvas id="' + chartId + '" data-chart-json="' + escapeHtml(JSON.stringify(normalized)) + '" aria-label="' + label + ' chart"></canvas>' +
'</div>';
}
function initPreview(root) {
if (typeof window.Chart !== 'function' || !root || !root.querySelectorAll) {
return;
}
root.querySelectorAll('.template-region.chart canvas').forEach(function (canvas) {
var data = {};
try { data = JSON.parse(canvas.getAttribute('data-chart-json') || '{}'); } catch (_error) { data = {}; }
new window.Chart(canvas.getContext('2d'), {
type: data.type || 'bar',
data: { labels: data.labels || [], datasets: data.datasets || [] },
options: chartData.getChartOptions(data)
});
});
}
function syncSourceFields(card) {
if (!card || !card.querySelector) {
return;
}
var sourceKind = card.querySelector('[name^="region_chart_source_kind_"]');
var isApi = sourceKind && sourceKind.value === 'api';
card.querySelectorAll('[data-chart-source-panel="api"], [data-chart-source-panel="manual"]').forEach(function (panel) {
var visible = panel.getAttribute('data-chart-source-panel') === (isApi ? 'api' : 'manual');
panel.hidden = !visible;
panel.querySelectorAll('input, select, textarea').forEach(function (input) {
input.disabled = !visible;
});
});
}
function renderDatasetRow(dataset, index, regionId) {
var current = dataset || {};
var hasColor = /^#[0-9a-fA-F]{3,8}$/.test(String(current.color || '').trim());
return '<div class="row g-2 align-items-end mb-2" data-chart-dataset-row>' +
'<div class="col-12 col-md-4"><label class="form-label" for="region_chart_dataset_label_' + regionId + '_' + index + '">Series label</label><input id="region_chart_dataset_label_' + regionId + '_' + index + '" class="form-control" data-chart-dataset-label value="' + escapeHtml(current.label || 'Series ' + (index + 1)) + '" /></div>' +
'<div class="col-12 col-md-4"><label class="form-label" for="region_chart_dataset_value_' + regionId + '_' + index + '">Value field</label><input id="region_chart_dataset_value_' + regionId + '_' + index + '" class="form-control" data-chart-dataset-value value="' + escapeHtml(current.value_path || '') + '" placeholder="sales" /></div>' +
'<div class="col-10 col-md-3"><label class="form-label" for="region_chart_dataset_color_' + regionId + '_' + index + '">Series color</label><input id="region_chart_dataset_color_' + regionId + '_' + index + '" class="form-control form-control-color w-100" data-chart-dataset-color data-chart-dataset-color-explicit="' + (hasColor ? 'true' : 'false') + '" type="color" value="' + escapeHtml(hasColor ? current.color : defaultSeriesColors[index % defaultSeriesColors.length]) + '" /></div>' +
'<div class="col-2 col-md-1"><button type="button" class="btn btn-outline-danger w-100" data-chart-dataset-remove aria-label="Remove series" title="Remove series">&times;</button></div>' +
'</div>';
}
function bindEditorCard(context) {
var card = context && context.card;
if (!card) {
return;
}
var sourceKind = card.querySelector('[name^="region_chart_source_kind_"]');
var chartType = card.querySelector('[name^="region_chart_type_"]');
var datasetEditor = card.querySelector('[data-chart-dataset-editor]');
var singleValueFields = card.querySelectorAll('[data-chart-single-value-field]');
if (sourceKind) {
sourceKind.addEventListener('change', function () {
syncSourceFields(card);
if (context.requestPreviewRender) context.requestPreviewRender();
});
}
function syncDatasetEditor() {
var usesDatasets = chartType && chartType.value === 'line';
if (datasetEditor) {
datasetEditor.hidden = !usesDatasets;
}
singleValueFields.forEach(function (field) {
field.hidden = usesDatasets;
});
card.querySelectorAll('[data-chart-option-types]').forEach(function (field) {
var types = String(field.getAttribute('data-chart-option-types') || '').split(' ');
var hidden = !chartType || types.indexOf(chartType.value) === -1;
field.hidden = hidden;
field.classList.toggle('d-none', hidden);
});
}
if (chartType) {
chartType.addEventListener('change', function () {
syncDatasetEditor();
if (context.requestPreviewRender) context.requestPreviewRender();
});
}
var regionId = card.getAttribute('data-region-id');
var datasetList = card.querySelector('[data-chart-datasets]');
var datasetInput = card.querySelector('[name="region_chart_datasets_' + regionId + '"]');
var addDatasetButton = card.querySelector('[data-chart-dataset-add]');
function syncDatasets() {
var datasets = [];
card.querySelectorAll('[data-chart-dataset-row]').forEach(function (row) {
var label = row.querySelector('[data-chart-dataset-label]');
var valuePath = row.querySelector('[data-chart-dataset-value]');
var color = row.querySelector('[data-chart-dataset-color]');
var explicitColor = color && color.getAttribute('data-chart-dataset-color-explicit') === 'true';
if (valuePath && valuePath.value.trim()) {
datasets.push({ label: label && label.value.trim() || 'Series ' + (datasets.length + 1), value_path: valuePath.value.trim(), color: explicitColor ? color.value : '' });
}
});
if (datasetInput) datasetInput.value = JSON.stringify(datasets);
if (context.requestPreviewRender) context.requestPreviewRender();
}
if (addDatasetButton && datasetList) {
addDatasetButton.addEventListener('click', function () {
var index = datasetList.querySelectorAll('[data-chart-dataset-row]').length;
datasetList.insertAdjacentHTML('beforeend', renderDatasetRow({ label: 'Series ' + (index + 1), value_path: '' }, index, regionId));
syncDatasets();
});
}
if (datasetList) {
datasetList.addEventListener('input', function (event) {
if (event.target.matches('[data-chart-dataset-color]')) {
event.target.setAttribute('data-chart-dataset-color-explicit', 'true');
}
syncDatasets();
});
datasetList.addEventListener('change', syncDatasets);
datasetList.addEventListener('click', function (event) {
var removeButton = event.target.closest('[data-chart-dataset-remove]');
if (!removeButton) return;
var rows = datasetList.querySelectorAll('[data-chart-dataset-row]');
if (rows.length > 1) removeButton.closest('[data-chart-dataset-row]').remove();
syncDatasets();
});
syncDatasets();
}
syncSourceFields(card);
syncDatasetEditor();
}
function renderEditorCard(context) {
var region = context.region;
var current = context.current || {};
var config = context.config || {};
var chartOptions = config.chart_options || {};
var sourceOptions = config.source_options || {};
var datasets = config.datasets && config.datasets.length ? config.datasets : [{ label: config.dataset_label || 'Value', value_path: config.value_path || 'value', color: '' }];
var datasetRows = datasets.map(function (dataset, index) { return renderDatasetRow(dataset, index, region.id); }).join('');
var accordionId = 'region-chart-options-' + region.id;
return '' +
'<div class="card card-outline card-secondary admin-form-card template-field-card mb-3" data-region-id="' + region.id + '" style="position:relative;overflow:visible !important;">' +
'<div class="card-header template-field-head"><strong>' + escapeHtml(region.label) + '</strong><div class="template-field-actions"><span class="chip">Chart</span></div></div>' +
'<div class="card-body p-3 d-grid gap-3">' +
'<div class="row g-3">' +
'<div class="col-12 col-md-4"><label class="form-label" for="region_chart_type_' + region.id + '">Chart type</label><select id="region_chart_type_' + region.id + '" name="region_chart_type_' + region.id + '" class="form-select"><option value="bar"' + (config.type === 'bar' ? ' selected' : '') + '>Bar</option><option value="line"' + (config.type === 'line' ? ' selected' : '') + '>Line</option><option value="doughnut"' + (config.type === 'doughnut' ? ' selected' : '') + '>Doughnut</option><option value="pie"' + (config.type === 'pie' ? ' selected' : '') + '>Pie</option></select></div>' +
'<div class="col-12 col-md-4"><label class="form-label" for="region_chart_source_kind_' + region.id + '">Data source</label><select id="region_chart_source_kind_' + region.id + '" name="region_chart_source_kind_' + region.id + '" class="form-select"><option value="manual"' + (config.source_kind === 'manual' ? ' selected' : '') + '>Manual rows</option><option value="api"' + (config.source_kind === 'api' ? ' selected' : '') + '>API source</option></select></div>' +
'<div class="col-12 col-md-4" data-chart-source-panel="api"><label class="form-label" for="region_chart_source_id_' + region.id + '">API source</label><select id="region_chart_source_id_' + region.id + '" name="region_chart_source_id_' + region.id + '" class="form-select"><option value="">Select a source</option>' + String(context.sourceOptions || '') + '</select></div>' +
'</div>' +
'<div class="row g-3" data-chart-source-panel="api">' +
'<div class="col-12 col-md-4"><label class="form-label" for="region_chart_items_path_' + region.id + '">Items path</label><input id="region_chart_items_path_' + region.id + '" name="region_chart_items_path_' + region.id + '" class="form-control" value="' + escapeHtml(config.items_path) + '" placeholder="results" /></div>' +
'<div class="col-12 col-md-4" data-chart-single-value-field><label class="form-label" for="region_chart_label_path_' + region.id + '">Label field</label><input id="region_chart_label_path_' + region.id + '" name="region_chart_label_path_' + region.id + '" class="form-control" value="' + escapeHtml(config.label_path) + '" placeholder="date" /></div>' +
'<div class="col-12 col-md-4" data-chart-single-value-field><label class="form-label" for="region_chart_value_path_' + region.id + '">Single value field</label><input id="region_chart_value_path_' + region.id + '" name="region_chart_value_path_' + region.id + '" class="form-control" value="' + escapeHtml(config.value_path) + '" placeholder="value" /></div>' +
'</div>' +
'<div class="row g-3">' +
'<div class="col-12" data-chart-source-panel="manual"><label class="form-label" for="region_chart_manual_rows_' + region.id + '">Manual rows (JSON)</label><textarea id="region_chart_manual_rows_' + region.id + '" name="region_chart_manual_rows_' + region.id + '" class="form-control" rows="5">' + escapeHtml(config.manual_rows_json) + '</textarea><div class="form-text">Rows use a label and one or more mapped values, for example [{"label":"Mon","value":12,"color":"#36a269"}].</div></div>' +
'<div class="col-12" data-chart-dataset-editor' + (config.type === 'line' ? '' : ' hidden') + '><div class="d-flex align-items-center justify-content-between gap-2"><label class="form-label mb-0">Data series</label><button type="button" class="btn btn-outline-secondary btn-sm" data-chart-dataset-add><i class="bi bi-plus-lg" aria-hidden="true"></i> Add series</button></div><div class="mt-2" data-chart-datasets>' + datasetRows + '</div><input type="hidden" name="region_chart_datasets_' + region.id + '" value="' + escapeHtml(JSON.stringify(datasets)) + '" /><div class="form-text">Add one section per API field or manual value. Set the color for each series here.</div></div>' +
'</div>' +
'<div class="accordion" id="' + accordionId + '">' +
'<div class="accordion-item overflow-hidden"><h2 class="accordion-header" id="' + accordionId + '-heading-chart"><button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#' + accordionId + '-chart" aria-expanded="false" aria-controls="' + accordionId + '-chart">Chart options</button></h2><div id="' + accordionId + '-chart" class="accordion-collapse collapse" aria-labelledby="' + accordionId + '-heading-chart" data-bs-parent="#' + accordionId + '"><div class="accordion-body"><div class="row g-3">' +
'<div class="col-12 col-md-4"><label class="form-label" for="region_chart_legend_' + region.id + '">Legend</label><select id="region_chart_legend_' + region.id + '" name="region_chart_legend_' + region.id + '" class="form-select"><option value="automatic"' + (chartOptions.legend === 'automatic' ? ' selected' : '') + '>Automatic</option><option value="show"' + (chartOptions.legend === 'show' ? ' selected' : '') + '>Show</option><option value="hide"' + (chartOptions.legend === 'hide' ? ' selected' : '') + '>Hide</option></select></div>' +
'<div class="col-12 col-md-4"><label class="form-label" for="region_chart_value_format_' + region.id + '">Value format</label><select id="region_chart_value_format_' + region.id + '" name="region_chart_value_format_' + region.id + '" class="form-select"><option value="number"' + (chartOptions.value_format === 'number' ? ' selected' : '') + '>Number</option><option value="percent"' + (chartOptions.value_format === 'percent' ? ' selected' : '') + '>Percent</option><option value="currency"' + (chartOptions.value_format === 'currency' ? ' selected' : '') + '>Currency</option></select></div>' +
'<div class="col-12 col-md-4"><label class="form-label" for="region_chart_decimal_places_' + region.id + '">Decimal places</label><input id="region_chart_decimal_places_' + region.id + '" name="region_chart_decimal_places_' + region.id + '" class="form-control" type="number" min="0" max="4" value="' + escapeHtml(chartOptions.decimal_places === undefined ? 0 : chartOptions.decimal_places) + '" /></div>' +
'<div class="col-12 col-md-4"><label class="form-label" for="region_chart_text_size_' + region.id + '">Text size (px)</label><input id="region_chart_text_size_' + region.id + '" name="region_chart_text_size_' + region.id + '" class="form-control" type="number" min="8" max="48" step="1" value="' + escapeHtml(chartOptions.text_size || 12) + '" /></div>' +
'<div class="col-12 col-md-4"><label class="form-label" for="region_chart_grid_color_' + region.id + '">Grid line color</label><input id="region_chart_grid_color_' + region.id + '" name="region_chart_grid_color_' + region.id + '" class="form-control form-control-color" type="color" value="' + escapeHtml(chartOptions.grid_color || '#000000') + '" title="Choose grid line color" /></div>' +
'<div class="col-12 col-md-4"><label class="form-label" for="region_chart_text_color_' + region.id + '">Text color</label><input id="region_chart_text_color_' + region.id + '" name="region_chart_text_color_' + region.id + '" class="form-control form-control-color" type="color" value="' + escapeHtml(chartOptions.text_color || '#000000') + '" title="Choose chart text color" /></div>' +
'<div class="col-12 col-md-4" data-chart-option-types="line bar"><label class="form-label" for="region_chart_y_axis_min_' + region.id + '">Y-axis minimum</label><input id="region_chart_y_axis_min_' + region.id + '" name="region_chart_y_axis_min_' + region.id + '" class="form-control" type="number" value="' + escapeHtml(chartOptions.y_axis_min) + '" placeholder="Automatic" /></div>' +
'<div class="col-12 col-md-4" data-chart-option-types="line bar"><label class="form-label" for="region_chart_y_axis_max_' + region.id + '">Y-axis maximum</label><input id="region_chart_y_axis_max_' + region.id + '" name="region_chart_y_axis_max_' + region.id + '" class="form-control" type="number" value="' + escapeHtml(chartOptions.y_axis_max) + '" placeholder="Automatic" /></div>' +
'<div class="col-12 col-md-4 d-flex align-items-end" data-chart-option-types="line bar"><div class="form-check mb-2"><input type="hidden" name="region_chart_begin_at_zero_control_' + region.id + '" value="1" /><input id="region_chart_begin_at_zero_' + region.id + '" name="region_chart_begin_at_zero_' + region.id + '" class="form-check-input" type="checkbox"' + (chartOptions.begin_at_zero !== false ? ' checked' : '') + ' /><label class="form-check-label" for="region_chart_begin_at_zero_' + region.id + '">Start Y-axis at zero</label></div></div>' +
'<div class="col-12 col-md-4" data-chart-option-types="line"><label class="form-label" for="region_chart_line_points_' + region.id + '">Line points</label><select id="region_chart_line_points_' + region.id + '" name="region_chart_line_points_' + region.id + '" class="form-select"><option value="automatic"' + (chartOptions.line_points === 'automatic' ? ' selected' : '') + '>Automatic</option><option value="show"' + (chartOptions.line_points === 'show' ? ' selected' : '') + '>Show</option><option value="hide"' + (chartOptions.line_points === 'hide' ? ' selected' : '') + '>Hide</option></select></div>' +
'<div class="col-12 col-md-4" data-chart-option-types="bar"><label class="form-label" for="region_chart_bar_orientation_' + region.id + '">Bar orientation</label><select id="region_chart_bar_orientation_' + region.id + '" name="region_chart_bar_orientation_' + region.id + '" class="form-select"><option value="vertical"' + (chartOptions.bar_orientation !== 'horizontal' ? ' selected' : '') + '>Vertical</option><option value="horizontal"' + (chartOptions.bar_orientation === 'horizontal' ? ' selected' : '') + '>Horizontal</option></select></div>' +
'<div class="col-12 col-md-4 d-flex align-items-end" data-chart-option-types="line"><div class="form-check mb-2"><input id="region_chart_line_fill_' + region.id + '" name="region_chart_line_fill_' + region.id + '" class="form-check-input" type="checkbox"' + (chartOptions.line_fill ? ' checked' : '') + ' /><label class="form-check-label" for="region_chart_line_fill_' + region.id + '">Fill line area</label></div></div>' +
'</div></div></div></div>' +
'<div class="accordion-item overflow-hidden"><h2 class="accordion-header" id="' + accordionId + '-heading-source"><button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#' + accordionId + '-source" aria-expanded="false" aria-controls="' + accordionId + '-source">Source options</button></h2><div id="' + accordionId + '-source" class="accordion-collapse collapse" aria-labelledby="' + accordionId + '-heading-source" data-bs-parent="#' + accordionId + '"><div class="accordion-body"><div class="row g-3">' +
'<div class="col-12 col-md-3"><label class="form-label" for="region_chart_source_sort_' + region.id + '">Order</label><select id="region_chart_source_sort_' + region.id + '" name="region_chart_source_sort_' + region.id + '" class="form-select"><option value="none"' + (sourceOptions.sort === 'none' ? ' selected' : '') + '>Original order</option><option value="reverse-original"' + (sourceOptions.sort === 'reverse-original' ? ' selected' : '') + '>Reverse original order</option><option value="value-ascending"' + (sourceOptions.sort === 'value-ascending' ? ' selected' : '') + '>Increasing values</option><option value="value-descending"' + (sourceOptions.sort === 'value-descending' ? ' selected' : '') + '>Decreasing values</option></select></div>' +
'<div class="col-12 col-md-3"><label class="form-label" for="region_chart_source_limit_' + region.id + '">Maximum items</label><input id="region_chart_source_limit_' + region.id + '" name="region_chart_source_limit_' + region.id + '" class="form-control" type="number" min="0" max="500" value="' + escapeHtml(sourceOptions.limit || 0) + '" placeholder="All" /></div>' +
'<div class="col-12 col-md-3"><label class="form-label" for="region_chart_source_missing_' + region.id + '">Missing values</label><select id="region_chart_source_missing_' + region.id + '" name="region_chart_source_missing_' + region.id + '" class="form-select"><option value="gap"' + (sourceOptions.missing === 'gap' ? ' selected' : '') + '>Leave gaps</option><option value="skip"' + (sourceOptions.missing === 'skip' ? ' selected' : '') + '>Skip row</option><option value="zero"' + (sourceOptions.missing === 'zero' ? ' selected' : '') + '>Use zero</option></select></div>' +
'</div></div></div></div>' +
'</div>' +
'</div>' +
'</div>';
}
function buildPreviewRenderContext(region, card, existingContent, sources) {
var current = getCurrentConfig(region, existingContent || {});
function value(name, fallback) {
var input = card && card.querySelector ? card.querySelector('[name="' + name + '_' + region.id + '"]') : null;
return input ? input.value : fallback;
}
function checked(name, fallback) {
var input = card && card.querySelector ? card.querySelector('[name="' + name + '_' + region.id + '"]') : null;
return input ? input.checked : fallback;
}
return {
type: 'chart',
chart_type: value('region_chart_type', current.type),
source_kind: value('region_chart_source_kind', current.source_kind),
source_id: value('region_chart_source_id', current.source_id),
items_path: value('region_chart_items_path', current.items_path),
label_path: value('region_chart_label_path', current.label_path),
value_path: value('region_chart_value_path', current.value_path),
dataset_label: value('region_chart_dataset_label', current.dataset_label),
color: value('region_chart_color', current.color),
datasets: parseRows(value('region_chart_datasets', current.datasets_json)),
manual_rows: parseRows(value('region_chart_manual_rows', current.manual_rows_json)),
chart_options: {
legend: value('region_chart_legend', current.chart_options.legend),
begin_at_zero: checked('region_chart_begin_at_zero', current.chart_options.begin_at_zero),
y_axis_min: value('region_chart_y_axis_min', current.chart_options.y_axis_min),
y_axis_max: value('region_chart_y_axis_max', current.chart_options.y_axis_max),
line_points: value('region_chart_line_points', current.chart_options.line_points),
line_fill: checked('region_chart_line_fill', current.chart_options.line_fill),
bar_orientation: value('region_chart_bar_orientation', current.chart_options.bar_orientation),
value_format: value('region_chart_value_format', current.chart_options.value_format),
decimal_places: value('region_chart_decimal_places', current.chart_options.decimal_places),
text_size: value('region_chart_text_size', current.chart_options.text_size),
grid_color: value('region_chart_grid_color', current.chart_options.grid_color),
text_color: value('region_chart_text_color', current.chart_options.text_color)
},
source_options: {
sort: value('region_chart_source_sort', current.source_options.sort),
limit: value('region_chart_source_limit', current.source_options.limit),
reverse: false,
missing: value('region_chart_source_missing', current.source_options.missing)
},
sources: Array.isArray(sources) ? sources : []
};
}
registry.register('chart', {
label: 'Chart',
getDefaultRegionSize: function () { return { width: 480, height: 280 }; },
getCurrentConfig: getCurrentConfig,
renderPreview: renderPreview,
initPreview: initPreview,
bindEditorCard: bindEditorCard,
renderEditorCard: renderEditorCard,
buildPreviewRenderContext: buildPreviewRenderContext
});
}());
+7 -15
View File
@@ -42,14 +42,6 @@ function getColumnsPerRow() {
return window.innerWidth >= 1200 ? 6 : window.innerWidth >= 992 ? 3 : window.innerWidth >= 576 ? 2 : 1;
}
function showUploadMessage(message) {
if (typeof window.showToast === 'function') {
window.showToast(message, 'warning');
return;
}
window.alert(message);
}
function showModal(target) {
if (window.pulseModal && typeof window.pulseModal.show === 'function') {
window.pulseModal.show(target);
@@ -229,12 +221,12 @@ function setUploadFile(file, shouldCrop) {
const matchesType = allowedTypes.length === 0 || allowedTypes.indexOf(fileType) !== -1;
const maxBytes = fileType.indexOf('video/') === 0 ? Number(uploadInput.dataset.videoMaxBytes) : Number(uploadInput.dataset.imageMaxBytes);
if (!matchesType) {
showUploadMessage('This media type is disabled in Media Uploads settings.');
window.PulseUpload.showMessage('This media type is disabled in Media Uploads settings.');
uploadInput.value = '';
return;
}
if (maxBytes && file.size > maxBytes) {
showUploadMessage('This file is larger than the configured upload limit.');
window.PulseUpload.showMessage('This file is larger than the current upload limit of ' + window.PulseUpload.formatUploadLimit(maxBytes) + '.');
uploadInput.value = '';
return;
}
@@ -310,7 +302,7 @@ if (cropApply) {
const outputType = cropSourceFile.type === 'image/jpeg' || cropSourceFile.type === 'image/webp' ? cropSourceFile.type : 'image/png';
cropper.getCroppedCanvas().toBlob(function (blob) {
if (!blob) {
showUploadMessage('Unable to crop this image.');
window.PulseUpload.showMessage('Unable to crop this image.');
return;
}
const extension = outputType === 'image/jpeg' ? '.jpg' : outputType === 'image/webp' ? '.webp' : '.png';
@@ -372,7 +364,7 @@ if (uploadForm) {
uploadForm.addEventListener('submit', function (event) {
if (uploadInput && !uploadInput.files.length) {
event.preventDefault();
showUploadMessage('Choose an image or video to upload.');
window.PulseUpload.showMessage('Choose an image or video to upload.');
}
});
}
@@ -476,7 +468,7 @@ async function refreshGallery() {
visibleRowLimit = 4;
updateSearchResults();
} catch (error) {
showUploadMessage(error.message || 'Unable to refresh media.');
window.PulseUpload.showMessage(error.message || 'Unable to refresh media.');
}
}
@@ -499,7 +491,7 @@ if (gallery) {
event.preventDefault();
event.stopPropagation();
if (column.dataset.mediaUsed === 'true') {
showUploadMessage('This media is in use and cannot be selected.');
window.PulseUpload.showMessage('This media is in use and cannot be selected.');
return;
}
const id = String(column.dataset.mediaId || '');
@@ -525,7 +517,7 @@ if (loadMoreButton) {
visibleRowLimit += 4;
updateSearchResults();
} catch (error) {
showUploadMessage(error.message || 'Unable to load more media.');
window.PulseUpload.showMessage(error.message || 'Unable to load more media.');
} finally {
loadMoreButton.disabled = false;
}
+21
View File
@@ -0,0 +1,21 @@
(function (window) {
var uploadUtils = window.PulseUpload || {};
uploadUtils.formatUploadLimit = function (bytes) {
var megabytes = Number(bytes || 0) / 1024 / 1024;
if (megabytes >= 1024 && megabytes % 1024 === 0) {
return (megabytes / 1024) + ' GB';
}
return (Number.isInteger(megabytes) ? megabytes : megabytes.toFixed(2).replace(/0+$/, '').replace(/\.$/, '')) + ' MB';
};
uploadUtils.showMessage = function (message) {
if (typeof window.showToast === 'function') {
window.showToast(message, 'warning');
return;
}
window.alert(message);
};
window.PulseUpload = uploadUtils;
})(window);
@@ -615,7 +615,7 @@ export function createSlideFormEditorController(options) {
promotion: false,
relative_urls: false,
remove_script_host: false,
convert_urls: true,
convert_urls: false,
paste_data_images: false,
paste_block_drop: true,
resize: false,
+10 -6
View File
@@ -539,7 +539,7 @@ export function createSlideFormRegionHelpers(options) {
if (module && typeof module.buildEditorCardContext === 'function') {
return module.buildEditorCardContext({
region: region,
current: region.region_type === 'rss' || region.region_type === 'api' || region.region_type === 'time-date' || region.region_type === 'timetable' || region.region_type === 'weather' ? (existingContent[region.region_key] || {}) : getCurrentRegionValue(region),
current: region.region_type === 'rss' || region.region_type === 'api' || region.region_type === 'time-date' || region.region_type === 'timetable' || region.region_type === 'weather' || region.region_type === 'chart' ? (existingContent[region.region_key] || {}) : getCurrentRegionValue(region),
currentDuration: getCurrentRegionVideoDuration(region),
regionRatio: reduceAspectRatio(region.width, region.height),
uploadMaxLabel: uploadMaxLabel,
@@ -548,7 +548,7 @@ export function createSlideFormRegionHelpers(options) {
uploadVideoMaxBytes: uploadVideoMaxBytes,
uploadMimeTypes: uploadMimeTypes,
disableAudio: (existingContent[region.region_key] || {}).disable_audio,
config: region.region_type === 'api' ? getCurrentApiConfig(region) : region.region_type === 'timetable' ? getCurrentTimetableConfig(region) : region.region_type === 'weather' ? { weather_location_id: (existingContent[region.region_key] || {}).weather_location_id || '' } : getCurrentRssConfig(region),
config: region.region_type === 'api' ? getCurrentApiConfig(region) : region.region_type === 'timetable' ? getCurrentTimetableConfig(region) : region.region_type === 'weather' ? { weather_location_id: (existingContent[region.region_key] || {}).weather_location_id || '' } : region.region_type === 'chart' && module && typeof module.getCurrentConfig === 'function' ? module.getCurrentConfig(region, existingContent) : getCurrentRssConfig(region),
style: getCurrentTextStyle(region),
fontSize: getCurrentTextStyle(region).font_size,
feedOptions: rssFeeds.map(function (feed) {
@@ -584,7 +584,7 @@ export function createSlideFormRegionHelpers(options) {
return {
region: region,
current: region.region_type === 'rss' || region.region_type === 'api' || region.region_type === 'weather' ? currentContent : getCurrentRegionValue(region),
current: region.region_type === 'rss' || region.region_type === 'api' || region.region_type === 'weather' || region.region_type === 'chart' ? currentContent : getCurrentRegionValue(region),
currentDuration: getCurrentRegionVideoDuration(region),
regionRatio: reduceAspectRatio(region.width, region.height),
uploadMaxLabel: uploadMaxLabel,
@@ -593,7 +593,7 @@ export function createSlideFormRegionHelpers(options) {
uploadVideoMaxBytes: uploadVideoMaxBytes,
uploadMimeTypes: uploadMimeTypes,
disableAudio: currentContent.disable_audio,
config: region.region_type === 'api' ? apiConfig : region.region_type === 'timetable' ? getCurrentTimetableConfig(region) : region.region_type === 'weather' ? { weather_location_id: currentContent.weather_location_id || '' } : rssConfig,
config: region.region_type === 'api' ? apiConfig : region.region_type === 'timetable' ? getCurrentTimetableConfig(region) : region.region_type === 'weather' ? { weather_location_id: currentContent.weather_location_id || '' } : region.region_type === 'chart' ? ((getRegionTypeModule('chart') || {}).getCurrentConfig ? getRegionTypeModule('chart').getCurrentConfig(region, existingContent) : {}) : rssConfig,
style: textStyle,
fontSize: textStyle.font_size,
feedOptions: rssFeeds.map(function (feed) {
@@ -636,11 +636,15 @@ export function createSlideFormRegionHelpers(options) {
Array.prototype.forEach.call(templateFields.querySelectorAll('[data-region-id]'), function (card) {
var module = null;
if (!card || !card.querySelector || !card.querySelector('input[type="url"][name^="region_qr_code_"]')) {
if (!card || !card.querySelector) {
return;
}
module = getRegionTypeModule('qr-code');
if (card.querySelector('input[type="url"][name^="region_qr_code_"]')) {
module = getRegionTypeModule('qr-code');
} else if (card.querySelector('select[name^="region_chart_source_kind_"]')) {
module = getRegionTypeModule('chart');
}
if (module && typeof module.bindEditorCard === 'function') {
module.bindEditorCard({
+10 -11
View File
@@ -6,14 +6,6 @@ import { createSlideFormPreviewHelpers } from '/assets/js/slides/slide-form-prev
(function () {
var DEFAULT_FONT_SIZE = 32;
function formatUploadLimitLabel(bytes) {
var megabytes = Number(bytes || 0) / 1024 / 1024;
if (megabytes >= 1024 && megabytes % 1024 === 0) {
return String(megabytes / 1024) + ' GB';
}
return (Number.isInteger(megabytes) ? String(megabytes) : megabytes.toFixed(2).replace(/0+$/, '').replace(/\.$/, '')) + ' MB';
}
var dataElement = document.getElementById('slide-editor-data');
if (!dataElement) {
return;
@@ -48,11 +40,11 @@ import { createSlideFormPreviewHelpers } from '/assets/js/slides/slide-form-prev
var openPreviewPopupButton = document.getElementById('open-preview-popup');
var uploadLimits = slideEditorData.uploadLimits || {};
var uploadMaxBytes = Number(uploadLimits.imageMaxBytes || 100 * 1024 * 1024);
var uploadMaxLabel = formatUploadLimitLabel(uploadMaxBytes);
var uploadMaxLabel = window.PulseUpload.formatUploadLimit(uploadMaxBytes);
var uploadVideoMaxBytes = Number(uploadLimits.videoMaxBytes || 1024 * 1024 * 1024);
var uploadVideoMaxLabel = formatUploadLimitLabel(uploadVideoMaxBytes);
var uploadVideoMaxLabel = window.PulseUpload.formatUploadLimit(uploadVideoMaxBytes);
var wysiwygImageUploadMaxBytes = Number(uploadLimits.wysiwygImageMaxBytes || 2 * 1024 * 1024);
var wysiwygImageUploadLimitLabel = formatUploadLimitLabel(wysiwygImageUploadMaxBytes);
var wysiwygImageUploadLimitLabel = window.PulseUpload.formatUploadLimit(wysiwygImageUploadMaxBytes);
var uploadMimeTypes = Array.isArray(uploadLimits.allowedMimeTypes) ? uploadLimits.allowedMimeTypes : [];
var videoDurationCache = Object.create(null);
var previewRenderFrame = 0;
@@ -690,6 +682,13 @@ import { createSlideFormPreviewHelpers } from '/assets/js/slides/slide-form-prev
return '<div class="slide-preview-region' + selected + '" style="left:' + box.left + 'px;top:' + box.top + 'px;width:' + box.width + 'px;height:' + box.height + 'px;">' + content + '</div>';
}).join('');
regions.forEach(function (region) {
var typeModule = getRegionTypeModule(region.region_type);
if (typeModule && typeof typeModule.initPreview === 'function') {
typeModule.initPreview(slidePreviewOverlay);
}
});
startPreviewVideoPlayback(slidePreviewOverlay);
syncPopupPreview();
@@ -1778,6 +1778,9 @@
}
if (backgroundInput) {
backgroundInput.addEventListener('change', function () {
if (backgroundInput.dataset.uploadUrl) {
return;
}
var file = backgroundInput.files && backgroundInput.files[0];
if (file) {
updateBackgroundPreview(file);
+14
View File
@@ -0,0 +1,14 @@
/*!
* Chart.js v4.5.1
* https://www.chartjs.org
* (c) 2025 Chart.js Contributors
* Released under the MIT License
*/
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).Chart=e()}(this,(function(){"use strict";var t=Object.freeze({__proto__:null,get Colors(){return Jo},get Decimation(){return ta},get Filler(){return ba},get Legend(){return Ma},get SubTitle(){return Pa},get Title(){return ka},get Tooltip(){return Na}});function e(){}const i=(()=>{let t=0;return()=>t++})();function s(t){return null==t}function n(t){if(Array.isArray&&Array.isArray(t))return!0;const e=Object.prototype.toString.call(t);return"[object"===e.slice(0,7)&&"Array]"===e.slice(-6)}function o(t){return null!==t&&"[object Object]"===Object.prototype.toString.call(t)}function a(t){return("number"==typeof t||t instanceof Number)&&isFinite(+t)}function r(t,e){return a(t)?t:e}function l(t,e){return void 0===t?e:t}const h=(t,e)=>"string"==typeof t&&t.endsWith("%")?parseFloat(t)/100:+t/e,c=(t,e)=>"string"==typeof t&&t.endsWith("%")?parseFloat(t)/100*e:+t;function d(t,e,i){if(t&&"function"==typeof t.call)return t.apply(i,e)}function u(t,e,i,s){let a,r,l;if(n(t))if(r=t.length,s)for(a=r-1;a>=0;a--)e.call(i,t[a],a);else for(a=0;a<r;a++)e.call(i,t[a],a);else if(o(t))for(l=Object.keys(t),r=l.length,a=0;a<r;a++)e.call(i,t[l[a]],l[a])}function f(t,e){let i,s,n,o;if(!t||!e||t.length!==e.length)return!1;for(i=0,s=t.length;i<s;++i)if(n=t[i],o=e[i],n.datasetIndex!==o.datasetIndex||n.index!==o.index)return!1;return!0}function g(t){if(n(t))return t.map(g);if(o(t)){const e=Object.create(null),i=Object.keys(t),s=i.length;let n=0;for(;n<s;++n)e[i[n]]=g(t[i[n]]);return e}return t}function p(t){return-1===["__proto__","prototype","constructor"].indexOf(t)}function m(t,e,i,s){if(!p(t))return;const n=e[t],a=i[t];o(n)&&o(a)?x(n,a,s):e[t]=g(a)}function x(t,e,i){const s=n(e)?e:[e],a=s.length;if(!o(t))return t;const r=(i=i||{}).merger||m;let l;for(let e=0;e<a;++e){if(l=s[e],!o(l))continue;const n=Object.keys(l);for(let e=0,s=n.length;e<s;++e)r(n[e],t,l,i)}return t}function b(t,e){return x(t,e,{merger:_})}function _(t,e,i){if(!p(t))return;const s=e[t],n=i[t];o(s)&&o(n)?b(s,n):Object.prototype.hasOwnProperty.call(e,t)||(e[t]=g(n))}const y={"":t=>t,x:t=>t.x,y:t=>t.y};function v(t){const e=t.split("."),i=[];let s="";for(const t of e)s+=t,s.endsWith("\\")?s=s.slice(0,-1)+".":(i.push(s),s="");return i}function M(t,e){const i=y[e]||(y[e]=function(t){const e=v(t);return t=>{for(const i of e){if(""===i)break;t=t&&t[i]}return t}}(e));return i(t)}function w(t){return t.charAt(0).toUpperCase()+t.slice(1)}const k=t=>void 0!==t,S=t=>"function"==typeof t,P=(t,e)=>{if(t.size!==e.size)return!1;for(const i of t)if(!e.has(i))return!1;return!0};function D(t){return"mouseup"===t.type||"click"===t.type||"contextmenu"===t.type}const C=Math.PI,O=2*C,A=O+C,T=Number.POSITIVE_INFINITY,L=C/180,E=C/2,R=C/4,I=2*C/3,z=Math.log10,F=Math.sign;function V(t,e,i){return Math.abs(t-e)<i}function B(t){const e=Math.round(t);t=V(t,e,t/1e3)?e:t;const i=Math.pow(10,Math.floor(z(t))),s=t/i;return(s<=1?1:s<=2?2:s<=5?5:10)*i}function W(t){const e=[],i=Math.sqrt(t);let s;for(s=1;s<i;s++)t%s==0&&(e.push(s),e.push(t/s));return i===(0|i)&&e.push(i),e.sort(((t,e)=>t-e)).pop(),e}function N(t){return!function(t){return"symbol"==typeof t||"object"==typeof t&&null!==t&&!(Symbol.toPrimitive in t||"toString"in t||"valueOf"in t)}(t)&&!isNaN(parseFloat(t))&&isFinite(t)}function H(t,e){const i=Math.round(t);return i-e<=t&&i+e>=t}function j(t,e,i){let s,n,o;for(s=0,n=t.length;s<n;s++)o=t[s][i],isNaN(o)||(e.min=Math.min(e.min,o),e.max=Math.max(e.max,o))}function $(t){return t*(C/180)}function Y(t){return t*(180/C)}function U(t){if(!a(t))return;let e=1,i=0;for(;Math.round(t*e)/e!==t;)e*=10,i++;return i}function X(t,e){const i=e.x-t.x,s=e.y-t.y,n=Math.sqrt(i*i+s*s);let o=Math.atan2(s,i);return o<-.5*C&&(o+=O),{angle:o,distance:n}}function q(t,e){return Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))}function K(t,e){return(t-e+A)%O-C}function G(t){return(t%O+O)%O}function J(t,e,i,s){const n=G(t),o=G(e),a=G(i),r=G(o-n),l=G(a-n),h=G(n-o),c=G(n-a);return n===o||n===a||s&&o===a||r>l&&h<c}function Z(t,e,i){return Math.max(e,Math.min(i,t))}function Q(t){return Z(t,-32768,32767)}function tt(t,e,i,s=1e-6){return t>=Math.min(e,i)-s&&t<=Math.max(e,i)+s}function et(t,e,i){i=i||(i=>t[i]<e);let s,n=t.length-1,o=0;for(;n-o>1;)s=o+n>>1,i(s)?o=s:n=s;return{lo:o,hi:n}}const it=(t,e,i,s)=>et(t,i,s?s=>{const n=t[s][e];return n<i||n===i&&t[s+1][e]===i}:s=>t[s][e]<i),st=(t,e,i)=>et(t,i,(s=>t[s][e]>=i));function nt(t,e,i){let s=0,n=t.length;for(;s<n&&t[s]<e;)s++;for(;n>s&&t[n-1]>i;)n--;return s>0||n<t.length?t.slice(s,n):t}const ot=["push","pop","shift","splice","unshift"];function at(t,e){t._chartjs?t._chartjs.listeners.push(e):(Object.defineProperty(t,"_chartjs",{configurable:!0,enumerable:!1,value:{listeners:[e]}}),ot.forEach((e=>{const i="_onData"+w(e),s=t[e];Object.defineProperty(t,e,{configurable:!0,enumerable:!1,value(...eLine truncated
/*!
* @kurkle/color v0.3.2
* https://github.com/kurkle/color#readme
* (c) 2023 Jukka Kurkela
* Released under the MIT License
*/function _t(t){return t+.5|0}const yt=(t,e,i)=>Math.max(Math.min(t,i),e);function vt(t){return yt(_t(2.55*t),0,255)}function Mt(t){return yt(_t(255*t),0,255)}function wt(t){return yt(_t(t/2.55)/100,0,1)}function kt(t){return yt(_t(100*t),0,100)}const St={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,A:10,B:11,C:12,D:13,E:14,F:15,a:10,b:11,c:12,d:13,e:14,f:15},Pt=[..."0123456789ABCDEF"],Dt=t=>Pt[15&t],Ct=t=>Pt[(240&t)>>4]+Pt[15&t],Ot=t=>(240&t)>>4==(15&t);function At(t){var e=(t=>Ot(t.r)&&Ot(t.g)&&Ot(t.b)&&Ot(t.a))(t)?Dt:Ct;return t?"#"+e(t.r)+e(t.g)+e(t.b)+((t,e)=>t<255?e(t):"")(t.a,e):void 0}const Tt=/^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;function Lt(t,e,i){const s=e*Math.min(i,1-i),n=(e,n=(e+t/30)%12)=>i-s*Math.max(Math.min(n-3,9-n,1),-1);return[n(0),n(8),n(4)]}function Et(t,e,i){const s=(s,n=(s+t/60)%6)=>i-i*e*Math.max(Math.min(n,4-n,1),0);return[s(5),s(3),s(1)]}function Rt(t,e,i){const s=Lt(t,1,.5);let n;for(e+i>1&&(n=1/(e+i),e*=n,i*=n),n=0;n<3;n++)s[n]*=1-e-i,s[n]+=e;return s}function It(t){const e=t.r/255,i=t.g/255,s=t.b/255,n=Math.max(e,i,s),o=Math.min(e,i,s),a=(n+o)/2;let r,l,h;return n!==o&&(h=n-o,l=a>.5?h/(2-n-o):h/(n+o),r=function(t,e,i,s,n){return t===n?(e-i)/s+(e<i?6:0):e===n?(i-t)/s+2:(t-e)/s+4}(e,i,s,h,n),r=60*r+.5),[0|r,l||0,a]}function zt(t,e,i,s){return(Array.isArray(e)?t(e[0],e[1],e[2]):t(e,i,s)).map(Mt)}function Ft(t,e,i){return zt(Lt,t,e,i)}function Vt(t){return(t%360+360)%360}function Bt(t){const e=Tt.exec(t);let i,s=255;if(!e)return;e[5]!==i&&(s=e[6]?vt(+e[5]):Mt(+e[5]));const n=Vt(+e[2]),o=+e[3]/100,a=+e[4]/100;return i="hwb"===e[1]?function(t,e,i){return zt(Rt,t,e,i)}(n,o,a):"hsv"===e[1]?function(t,e,i){return zt(Et,t,e,i)}(n,o,a):Ft(n,o,a),{r:i[0],g:i[1],b:i[2],a:s}}const Wt={x:"dark",Z:"light",Y:"re",X:"blu",W:"gr",V:"medium",U:"slate",A:"ee",T:"ol",S:"or",B:"ra",C:"lateg",D:"ights",R:"in",Q:"turquois",E:"hi",P:"ro",O:"al",N:"le",M:"de",L:"yello",F:"en",K:"ch",G:"arks",H:"ea",I:"ightg",J:"wh"},Nt={OiceXe:"f0f8ff",antiquewEte:"faebd7",aqua:"ffff",aquamarRe:"7fffd4",azuY:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"0",blanKedOmond:"ffebcd",Xe:"ff",XeviTet:"8a2be2",bPwn:"a52a2a",burlywood:"deb887",caMtXe:"5f9ea0",KartYuse:"7fff00",KocTate:"d2691e",cSO:"ff7f50",cSnflowerXe:"6495ed",cSnsilk:"fff8dc",crimson:"dc143c",cyan:"ffff",xXe:"8b",xcyan:"8b8b",xgTMnPd:"b8860b",xWay:"a9a9a9",xgYF:"6400",xgYy:"a9a9a9",xkhaki:"bdb76b",xmagFta:"8b008b",xTivegYF:"556b2f",xSange:"ff8c00",xScEd:"9932cc",xYd:"8b0000",xsOmon:"e9967a",xsHgYF:"8fbc8f",xUXe:"483d8b",xUWay:"2f4f4f",xUgYy:"2f4f4f",xQe:"ced1",xviTet:"9400d3",dAppRk:"ff1493",dApskyXe:"bfff",dimWay:"696969",dimgYy:"696969",dodgerXe:"1e90ff",fiYbrick:"b22222",flSOwEte:"fffaf0",foYstWAn:"228b22",fuKsia:"ff00ff",gaRsbSo:"dcdcdc",ghostwEte:"f8f8ff",gTd:"ffd700",gTMnPd:"daa520",Way:"808080",gYF:"8000",gYFLw:"adff2f",gYy:"808080",honeyMw:"f0fff0",hotpRk:"ff69b4",RdianYd:"cd5c5c",Rdigo:"4b0082",ivSy:"fffff0",khaki:"f0e68c",lavFMr:"e6e6fa",lavFMrXsh:"fff0f5",lawngYF:"7cfc00",NmoncEffon:"fffacd",ZXe:"add8e6",ZcSO:"f08080",Zcyan:"e0ffff",ZgTMnPdLw:"fafad2",ZWay:"d3d3d3",ZgYF:"90ee90",ZgYy:"d3d3d3",ZpRk:"ffb6c1",ZsOmon:"ffa07a",ZsHgYF:"20b2aa",ZskyXe:"87cefa",ZUWay:"778899",ZUgYy:"778899",ZstAlXe:"b0c4de",ZLw:"ffffe0",lime:"ff00",limegYF:"32cd32",lRF:"faf0e6",magFta:"ff00ff",maPon:"800000",VaquamarRe:"66cdaa",VXe:"cd",VScEd:"ba55d3",VpurpN:"9370db",VsHgYF:"3cb371",VUXe:"7b68ee",VsprRggYF:"fa9a",VQe:"48d1cc",VviTetYd:"c71585",midnightXe:"191970",mRtcYam:"f5fffa",mistyPse:"ffe4e1",moccasR:"ffe4b5",navajowEte:"ffdead",navy:"80",Tdlace:"fdf5e6",Tive:"808000",TivedBb:"6b8e23",Sange:"ffa500",SangeYd:"ff4500",ScEd:"da70d6",pOegTMnPd:"eee8aa",pOegYF:"98fb98",pOeQe:"afeeee",pOeviTetYd:"db7093",papayawEp:"ffefd5",pHKpuff:"ffdab9",peru:"cd853f",pRk:"ffc0cb",plum:"dda0dd",powMrXe:"b0e0e6",purpN:"800080",YbeccapurpN:"663399",Yd:"ff0000",Psybrown:"bc8f8f",PyOXe:"4169e1",saddNbPwn:"8b4513",sOmon:"fa8072",sandybPwn:"f4a460",sHgYF:"2e8b57",sHshell:"fff5ee",siFna:"a0522d",silver:"c0c0c0",skyXe:"87ceeb",UXe:"6a5acd",UWay:"708090",UgYy:"708090",snow:"fffafa",sprRggYF:"ff7f",stAlXe:"4682b4",tan:"d2b48c",teO:"8080",tEstN:"d8bfd8",tomato:"ff6347",Qe:"40e0d0",viTet:"ee82ee",JHt:"f5deb3",wEte:"ffffff",wEtesmoke:"f5f5f5",Lw:"ffff00",LwgYF:"9acd32"};let Ht;function jt(t){Ht||(Ht=function(){const t={},e=Object.keys(Nt),i=Object.keys(Wt);let s,n,o,a,r;for(s=0;s<e.length;s++){for(a=r=e[s],n=0;n<i.length;n++)o=i[n],r=r.replace(o,Wt[o]);o=parseInt(Nt[a],16),t[r]=[o>>16&255,o>>8&255,255&o]}return t}(),Ht.transparent=[0,0,0,0]);const e=Ht[t.toLowerCase()];return e&&{r:e[0],g:e[1],b:e[2],a:4===e.length?e[3]:255}}const $t=/^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/;const Yt=t=>t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055,Ut=t=>t<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4);function Xt(t,e,i){if(t){let s=It(t);s[e]=Math.max(0,Math.min(s[e]+s[e]*i,0===e?360:1)),Line truncated
//# sourceMappingURL=chart.umd.js.map