feat: add robust panel type image normalization, text fallback rendering, and corresponding styles
This commit is contained in:
parent
dcf5477199
commit
9ae7b0631b
5 changed files with 467 additions and 33 deletions
|
|
@ -385,6 +385,168 @@
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizePanelTypeCodeForImage(code) {
|
||||||
|
let normalized = String(code || '').trim();
|
||||||
|
if (!normalized) return '';
|
||||||
|
|
||||||
|
const upper = normalized.toUpperCase();
|
||||||
|
if (upper === 'XA') {
|
||||||
|
normalized = 'GXA';
|
||||||
|
} else if (upper === 'XB') {
|
||||||
|
normalized = 'GXB';
|
||||||
|
} else if (upper === 'XD') {
|
||||||
|
normalized = 'GXD';
|
||||||
|
} else if (upper.startsWith('Z') && upper.length > 1) {
|
||||||
|
normalized = normalized.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalized.replace(/["']/g, '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildPanelTypeImageCandidates(code) {
|
||||||
|
const normalizedCode = normalizePanelTypeCodeForImage(code);
|
||||||
|
if (!normalizedCode) return [];
|
||||||
|
|
||||||
|
const upperCode = normalizedCode.toUpperCase();
|
||||||
|
const baseSet = new Set([upperCode]);
|
||||||
|
|
||||||
|
const strippedPrefix = upperCode.replace(/^[-_]+/, '');
|
||||||
|
if (strippedPrefix) {
|
||||||
|
baseSet.add(strippedPrefix);
|
||||||
|
baseSet.add(strippedPrefix.replace(/-/g, '_'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (upperCode.includes(' ')) {
|
||||||
|
baseSet.add(upperCode.replace(/\s+/g, ''));
|
||||||
|
baseSet.add(upperCode.replace(/\s+/g, '_'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (upperCode.includes('-')) {
|
||||||
|
baseSet.add(upperCode.replace(/-/g, '_'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const uniqueCodes = [];
|
||||||
|
baseSet.forEach(item => {
|
||||||
|
if (!item) return;
|
||||||
|
const up = item.toUpperCase();
|
||||||
|
const title = up.charAt(0) + up.slice(1).toLowerCase();
|
||||||
|
const low = up.toLowerCase();
|
||||||
|
[up, title, low].forEach(c => {
|
||||||
|
if (!uniqueCodes.includes(c)) uniqueCodes.push(c);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const candidates = [];
|
||||||
|
uniqueCodes.forEach(item => {
|
||||||
|
candidates.push(`/static/assets/road_signs/2025/${encodeURIComponent(item)}.svg`);
|
||||||
|
});
|
||||||
|
uniqueCodes.forEach(item => {
|
||||||
|
candidates.push(`/static/assets/road_signs/2025/${encodeURIComponent(item)}.png`);
|
||||||
|
});
|
||||||
|
|
||||||
|
return candidates;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPanelTextFallback(container, code, options = {}) {
|
||||||
|
if (!container) return;
|
||||||
|
const text = String(code || '').trim();
|
||||||
|
if (!text) {
|
||||||
|
container.innerHTML = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const len = text.length;
|
||||||
|
let fontSize;
|
||||||
|
let lineHeight = '1.1';
|
||||||
|
|
||||||
|
if (len <= 4) {
|
||||||
|
fontSize = '0.72rem';
|
||||||
|
lineHeight = '1.15';
|
||||||
|
} else if (len <= 8) {
|
||||||
|
fontSize = '0.62rem';
|
||||||
|
lineHeight = '1.1';
|
||||||
|
} else if (len <= 14) {
|
||||||
|
fontSize = '0.54rem';
|
||||||
|
lineHeight = '1.05';
|
||||||
|
} else if (len <= 24) {
|
||||||
|
fontSize = '0.48rem';
|
||||||
|
lineHeight = '1.05';
|
||||||
|
} else {
|
||||||
|
fontSize = '0.42rem';
|
||||||
|
lineHeight = '1.0';
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSmallThumb = options.width && parseInt(options.width, 10) < 35;
|
||||||
|
if (isSmallThumb) {
|
||||||
|
fontSize = len <= 4 ? '0.55rem' : '0.40rem';
|
||||||
|
}
|
||||||
|
|
||||||
|
container.innerHTML = '';
|
||||||
|
const fallbackBox = document.createElement('div');
|
||||||
|
fallbackBox.className = 'panel-text-fallback';
|
||||||
|
fallbackBox.title = text;
|
||||||
|
|
||||||
|
const span = document.createElement('span');
|
||||||
|
span.className = 'panel-fallback-text';
|
||||||
|
span.style.fontSize = fontSize;
|
||||||
|
span.style.lineHeight = lineHeight;
|
||||||
|
span.textContent = text;
|
||||||
|
|
||||||
|
fallbackBox.appendChild(span);
|
||||||
|
container.appendChild(fallbackBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendPanelTypeImage(container, code, imageUrl, options = {}) {
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const imageHeight = options.height || '40px';
|
||||||
|
const imageWidth = options.width || '40px';
|
||||||
|
const objectFit = options.objectFit || 'contain';
|
||||||
|
|
||||||
|
function createImage(src) {
|
||||||
|
const img = document.createElement('img');
|
||||||
|
img.src = src;
|
||||||
|
img.alt = code || '';
|
||||||
|
img.style.maxHeight = '100%';
|
||||||
|
img.style.maxWidth = '100%';
|
||||||
|
img.style.height = imageHeight;
|
||||||
|
img.style.width = imageWidth;
|
||||||
|
img.style.objectFit = objectFit;
|
||||||
|
return img;
|
||||||
|
}
|
||||||
|
|
||||||
|
const candidates = buildPanelTypeImageCandidates(code);
|
||||||
|
let candidateIndex = 0;
|
||||||
|
|
||||||
|
function tryFallbackCandidate() {
|
||||||
|
const src = candidates[candidateIndex++];
|
||||||
|
if (!src) {
|
||||||
|
renderPanelTextFallback(container, code, options);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fallbackImg = createImage(src);
|
||||||
|
fallbackImg.onerror = tryFallbackCandidate;
|
||||||
|
container.innerHTML = '';
|
||||||
|
container.appendChild(fallbackImg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imageUrl) {
|
||||||
|
const img = createImage(imageUrl);
|
||||||
|
img.onerror = tryFallbackCandidate;
|
||||||
|
container.innerHTML = '';
|
||||||
|
container.appendChild(img);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tryFallbackCandidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
global.normalizePanelTypeCodeForImage = normalizePanelTypeCodeForImage;
|
||||||
|
global.buildPanelTypeImageCandidates = buildPanelTypeImageCandidates;
|
||||||
|
global.renderPanelTextFallback = renderPanelTextFallback;
|
||||||
|
global.appendPanelTypeImage = appendPanelTypeImage;
|
||||||
|
|
||||||
global.SignPanelsShared = {
|
global.SignPanelsShared = {
|
||||||
getRenderedPanelHeight,
|
getRenderedPanelHeight,
|
||||||
calculatePanelOffsets,
|
calculatePanelOffsets,
|
||||||
|
|
@ -392,5 +554,9 @@
|
||||||
handleRoadSignStyleImageMissing,
|
handleRoadSignStyleImageMissing,
|
||||||
refreshPanelOffsetsForSource,
|
refreshPanelOffsetsForSource,
|
||||||
createPanelOffsetRefreshScheduler,
|
createPanelOffsetRefreshScheduler,
|
||||||
|
normalizePanelTypeCodeForImage,
|
||||||
|
buildPanelTypeImageCandidates,
|
||||||
|
renderPanelTextFallback,
|
||||||
|
appendPanelTypeImage,
|
||||||
};
|
};
|
||||||
})(window);
|
})(window);
|
||||||
|
|
|
||||||
|
|
@ -3127,11 +3127,35 @@ function buildPanelTypeImageCandidates(code) {
|
||||||
if (!normalizedCode) return [];
|
if (!normalizedCode) return [];
|
||||||
|
|
||||||
const upperCode = normalizedCode.toUpperCase();
|
const upperCode = normalizedCode.toUpperCase();
|
||||||
const titleCode = upperCode.charAt(0) + upperCode.slice(1).toLowerCase();
|
const baseSet = new Set([upperCode]);
|
||||||
const lowerCode = upperCode.toLowerCase();
|
|
||||||
const uniqueCodes = Array.from(new Set([upperCode, titleCode, lowerCode]));
|
|
||||||
const candidates = [];
|
|
||||||
|
|
||||||
|
const strippedPrefix = upperCode.replace(/^[-_]+/, '');
|
||||||
|
if (strippedPrefix) {
|
||||||
|
baseSet.add(strippedPrefix);
|
||||||
|
baseSet.add(strippedPrefix.replace(/-/g, '_'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (upperCode.includes(' ')) {
|
||||||
|
baseSet.add(upperCode.replace(/\s+/g, ''));
|
||||||
|
baseSet.add(upperCode.replace(/\s+/g, '_'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (upperCode.includes('-')) {
|
||||||
|
baseSet.add(upperCode.replace(/-/g, '_'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const uniqueCodes = [];
|
||||||
|
baseSet.forEach(item => {
|
||||||
|
if (!item) return;
|
||||||
|
const up = item.toUpperCase();
|
||||||
|
const title = up.charAt(0) + up.slice(1).toLowerCase();
|
||||||
|
const low = up.toLowerCase();
|
||||||
|
[up, title, low].forEach(c => {
|
||||||
|
if (!uniqueCodes.includes(c)) uniqueCodes.push(c);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const candidates = [];
|
||||||
uniqueCodes.forEach(item => {
|
uniqueCodes.forEach(item => {
|
||||||
candidates.push(`/static/assets/road_signs/2025/${encodeURIComponent(item)}.svg`);
|
candidates.push(`/static/assets/road_signs/2025/${encodeURIComponent(item)}.svg`);
|
||||||
});
|
});
|
||||||
|
|
@ -3142,6 +3166,55 @@ function buildPanelTypeImageCandidates(code) {
|
||||||
return candidates;
|
return candidates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderPanelTextFallback(container, code, options = {}) {
|
||||||
|
if (!container) return;
|
||||||
|
const text = String(code || '').trim();
|
||||||
|
if (!text) {
|
||||||
|
container.innerHTML = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const len = text.length;
|
||||||
|
let fontSize;
|
||||||
|
let lineHeight = '1.1';
|
||||||
|
|
||||||
|
if (len <= 4) {
|
||||||
|
fontSize = '0.72rem';
|
||||||
|
lineHeight = '1.15';
|
||||||
|
} else if (len <= 8) {
|
||||||
|
fontSize = '0.62rem';
|
||||||
|
lineHeight = '1.1';
|
||||||
|
} else if (len <= 14) {
|
||||||
|
fontSize = '0.54rem';
|
||||||
|
lineHeight = '1.05';
|
||||||
|
} else if (len <= 24) {
|
||||||
|
fontSize = '0.48rem';
|
||||||
|
lineHeight = '1.05';
|
||||||
|
} else {
|
||||||
|
fontSize = '0.42rem';
|
||||||
|
lineHeight = '1.0';
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSmallThumb = options.width && parseInt(options.width, 10) < 35;
|
||||||
|
if (isSmallThumb) {
|
||||||
|
fontSize = len <= 4 ? '0.55rem' : '0.40rem';
|
||||||
|
}
|
||||||
|
|
||||||
|
container.innerHTML = '';
|
||||||
|
const fallbackBox = document.createElement('div');
|
||||||
|
fallbackBox.className = 'panel-text-fallback';
|
||||||
|
fallbackBox.title = text;
|
||||||
|
|
||||||
|
const span = document.createElement('span');
|
||||||
|
span.className = 'panel-fallback-text';
|
||||||
|
span.style.fontSize = fontSize;
|
||||||
|
span.style.lineHeight = lineHeight;
|
||||||
|
span.textContent = text;
|
||||||
|
|
||||||
|
fallbackBox.appendChild(span);
|
||||||
|
container.appendChild(fallbackBox);
|
||||||
|
}
|
||||||
|
|
||||||
function appendPanelTypeImage(container, code, imageUrl, options = {}) {
|
function appendPanelTypeImage(container, code, imageUrl, options = {}) {
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
|
|
||||||
|
|
@ -3167,7 +3240,7 @@ function appendPanelTypeImage(container, code, imageUrl, options = {}) {
|
||||||
function tryFallbackCandidate() {
|
function tryFallbackCandidate() {
|
||||||
const src = candidates[candidateIndex++];
|
const src = candidates[candidateIndex++];
|
||||||
if (!src) {
|
if (!src) {
|
||||||
container.textContent = code || '';
|
renderPanelTextFallback(container, code, options);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3233,6 +3306,8 @@ function renderSignPanelTypeConditionalFilter(thematicCode, def, selectedValues)
|
||||||
#accordion-sign-panel-types .panel-item {
|
#accordion-sign-panel-types .panel-item {
|
||||||
transition: all 0.2s cubic-bezier(0.165, 0.84, 0.44, 1);
|
transition: all 0.2s cubic-bezier(0.165, 0.84, 0.44, 1);
|
||||||
border: 1px solid #dee2e6 !important;
|
border: 1px solid #dee2e6 !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
box-sizing: border-box !important;
|
||||||
}
|
}
|
||||||
#accordion-sign-panel-types .panel-item:hover {
|
#accordion-sign-panel-types .panel-item:hover {
|
||||||
transform: translateY(-3px);
|
transform: translateY(-3px);
|
||||||
|
|
@ -3242,6 +3317,55 @@ function renderSignPanelTypeConditionalFilter(thematicCode, def, selectedValues)
|
||||||
#accordion-sign-panel-types .panel-item.bg-primary {
|
#accordion-sign-panel-types .panel-item.bg-primary {
|
||||||
box-shadow: 0 4px 12px rgba(13, 110, 253, 0.15) !important;
|
box-shadow: 0 4px 12px rgba(13, 110, 253, 0.15) !important;
|
||||||
}
|
}
|
||||||
|
#accordion-sign-panel-types .panel-img-placeholder {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
box-sizing: border-box !important;
|
||||||
|
padding: 2px !important;
|
||||||
|
}
|
||||||
|
#accordion-sign-panel-types .panel-text-fallback,
|
||||||
|
.panel-text-fallback {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
max-height: 44px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border: 1px dashed #d0d5dd;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 2px 3px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: background-color 0.15s ease, border-color 0.15s ease;
|
||||||
|
}
|
||||||
|
#accordion-sign-panel-types .panel-fallback-text,
|
||||||
|
.panel-fallback-text {
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 4;
|
||||||
|
line-clamp: 4;
|
||||||
|
max-height: 40px;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
word-break: break-word;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #495057;
|
||||||
|
letter-spacing: -0.2px;
|
||||||
|
}
|
||||||
|
#accordion-sign-panel-types .panel-item.bg-primary .panel-text-fallback,
|
||||||
|
#accordion-sign-panel-types .panel-item.selected .panel-text-fallback {
|
||||||
|
background-color: rgba(255, 255, 255, 0.2) !important;
|
||||||
|
border-color: rgba(255, 255, 255, 0.4) !important;
|
||||||
|
}
|
||||||
|
#accordion-sign-panel-types .panel-item.bg-primary .panel-fallback-text,
|
||||||
|
#accordion-sign-panel-types .panel-item.selected .panel-fallback-text {
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
@ -3257,11 +3381,11 @@ function renderSignPanelTypeConditionalFilter(thematicCode, def, selectedValues)
|
||||||
data-id="${escapeHtml(option.value)}"
|
data-id="${escapeHtml(option.value)}"
|
||||||
data-code="${escapeHtml(option.code)}"
|
data-code="${escapeHtml(option.code)}"
|
||||||
data-image-url="${escapeHtml(option.imageUrl)}"
|
data-image-url="${escapeHtml(option.imageUrl)}"
|
||||||
style="cursor: pointer; width: 70px;"
|
style="cursor: pointer; width: 70px; overflow: hidden; box-sizing: border-box;"
|
||||||
title="${escapeHtml(title)}">
|
title="${escapeHtml(title)}">
|
||||||
<div class="panel-img-placeholder mb-1"
|
<div class="panel-img-placeholder mb-1"
|
||||||
style="height: 50px; display: flex; align-items: center; justify-content: center;"></div>
|
style="height: 50px; width: 100%; display: flex; align-items: center; justify-content: center; overflow: hidden; box-sizing: border-box;"></div>
|
||||||
<div class="small text-truncate fw-bold" style="font-size: 0.7rem;">
|
<div class="small text-truncate fw-bold" style="font-size: 0.7rem; width: 100%; max-width: 100%; overflow: hidden;">
|
||||||
${escapeHtml(option.code)}
|
${escapeHtml(option.code)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -3373,21 +3497,24 @@ function initSignPanelTypePickers() {
|
||||||
item.className = 'selected-panel-item d-flex justify-content-between align-items-center bg-white border rounded p-1 mb-1';
|
item.className = 'selected-panel-item d-flex justify-content-between align-items-center bg-white border rounded p-1 mb-1';
|
||||||
|
|
||||||
const content = document.createElement('div');
|
const content = document.createElement('div');
|
||||||
content.className = 'd-flex align-items-center';
|
content.className = 'd-flex align-items-center text-truncate me-2';
|
||||||
|
content.style.maxWidth = 'calc(100% - 30px)';
|
||||||
|
|
||||||
const thumb = document.createElement('div');
|
const thumb = document.createElement('div');
|
||||||
thumb.className = 'd-flex align-items-center justify-content-center me-2';
|
thumb.className = 'd-flex align-items-center justify-content-center me-2 flex-shrink-0';
|
||||||
thumb.style.width = '28px';
|
thumb.style.width = '28px';
|
||||||
thumb.style.height = '28px';
|
thumb.style.height = '28px';
|
||||||
|
thumb.style.overflow = 'hidden';
|
||||||
appendPanelTypeImage(thumb, code, imageUrl, { width: '28px', height: '28px' });
|
appendPanelTypeImage(thumb, code, imageUrl, { width: '28px', height: '28px' });
|
||||||
|
|
||||||
const label = document.createElement('span');
|
const label = document.createElement('span');
|
||||||
label.className = 'small fw-bold ms-1';
|
label.className = 'small fw-bold ms-1 text-truncate';
|
||||||
|
label.title = code;
|
||||||
label.textContent = code;
|
label.textContent = code;
|
||||||
|
|
||||||
const removeBtn = document.createElement('button');
|
const removeBtn = document.createElement('button');
|
||||||
removeBtn.type = 'button';
|
removeBtn.type = 'button';
|
||||||
removeBtn.className = 'btn btn-sm btn-link text-danger p-0 me-1 remove-panel-type';
|
removeBtn.className = 'btn btn-sm btn-link text-danger p-0 me-1 remove-panel-type flex-shrink-0';
|
||||||
removeBtn.dataset.id = id;
|
removeBtn.dataset.id = id;
|
||||||
removeBtn.innerHTML = '<i class="bi bi-x"></i>';
|
removeBtn.innerHTML = '<i class="bi bi-x"></i>';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2370,27 +2370,41 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
const imageUrl = panelItem.dataset.imageUrl;
|
const imageUrl = panelItem.dataset.imageUrl;
|
||||||
const panelCode = panelItem.dataset.code;
|
const panelCode = panelItem.dataset.code;
|
||||||
|
|
||||||
if (imageUrl) {
|
const thumbContainer = document.createElement('div');
|
||||||
imageHtml = `<img src="${imageUrl}" style="height: 40px; width: 40px; object-fit: contain; margin-right: 8px;">`;
|
thumbContainer.style.width = '40px';
|
||||||
|
thumbContainer.style.height = '40px';
|
||||||
|
thumbContainer.style.marginRight = '8px';
|
||||||
|
thumbContainer.style.display = 'flex';
|
||||||
|
thumbContainer.style.alignItems = 'center';
|
||||||
|
thumbContainer.style.justifyContent = 'center';
|
||||||
|
thumbContainer.style.overflow = 'hidden';
|
||||||
|
thumbContainer.style.flexShrink = '0';
|
||||||
|
|
||||||
|
if (typeof appendPanelTypeImage === 'function') {
|
||||||
|
appendPanelTypeImage(thumbContainer, panelCode, imageUrl, { width: '40px', height: '40px' });
|
||||||
|
} else if (imageUrl) {
|
||||||
|
thumbContainer.innerHTML = `<img src="${imageUrl}" style="height: 40px; width: 40px; object-fit: contain;">`;
|
||||||
} else if (typeof buildPanelIconElement === 'function') {
|
} else if (typeof buildPanelIconElement === 'function') {
|
||||||
const img = buildPanelIconElement(panelCode);
|
const img = buildPanelIconElement(panelCode);
|
||||||
img.style.height = '40px';
|
img.style.height = '40px';
|
||||||
img.style.width = '40px';
|
img.style.width = '40px';
|
||||||
img.style.marginRight = '8px';
|
thumbContainer.appendChild(img);
|
||||||
const tempDiv = document.createElement('div');
|
} else if (typeof renderPanelTextFallback === 'function') {
|
||||||
tempDiv.appendChild(img);
|
renderPanelTextFallback(thumbContainer, panelCode, { width: '40px', height: '40px' });
|
||||||
imageHtml = tempDiv.innerHTML;
|
|
||||||
} else {
|
} else {
|
||||||
imageHtml = `<span style="font-size: 0.8rem; margin-right: 8px;">${panelCode}</span>`;
|
thumbContainer.innerHTML = `<span style="font-size: 0.8rem;">${panelCode}</span>`;
|
||||||
}
|
}
|
||||||
|
const tempWrapper = document.createElement('div');
|
||||||
|
tempWrapper.appendChild(thumbContainer);
|
||||||
|
imageHtml = tempWrapper.innerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
const item = document.createElement('div');
|
const item = document.createElement('div');
|
||||||
item.className = 'd-flex align-items-center bg-white border rounded p-2';
|
item.className = 'd-flex align-items-center bg-white border rounded p-2';
|
||||||
item.innerHTML = `
|
item.innerHTML = `
|
||||||
${imageHtml}
|
${imageHtml}
|
||||||
<span class="fw-bold">${code}</span>
|
<span class="fw-bold text-truncate me-2" style="max-width: 240px;" title="${code}">${code}</span>
|
||||||
<button type="button" class="btn btn-sm btn-link text-danger p-0 ms-auto remove-panel-type-create">
|
<button type="button" class="btn btn-sm btn-link text-danger p-0 ms-auto remove-panel-type-create flex-shrink-0">
|
||||||
<i class="bi bi-x"></i>
|
<i class="bi bi-x"></i>
|
||||||
</button>
|
</button>
|
||||||
`;
|
`;
|
||||||
|
|
@ -2423,7 +2437,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
const imageUrl = item.dataset.imageUrl;
|
const imageUrl = item.dataset.imageUrl;
|
||||||
const code = item.dataset.code;
|
const code = item.dataset.code;
|
||||||
|
|
||||||
if (imageUrl) {
|
if (typeof appendPanelTypeImage === 'function') {
|
||||||
|
appendPanelTypeImage(placeholder, code, imageUrl);
|
||||||
|
} else if (imageUrl) {
|
||||||
const img = document.createElement('img');
|
const img = document.createElement('img');
|
||||||
img.src = imageUrl;
|
img.src = imageUrl;
|
||||||
img.style.maxHeight = '100%';
|
img.style.maxHeight = '100%';
|
||||||
|
|
@ -2434,6 +2450,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
img.style.height = '40px';
|
img.style.height = '40px';
|
||||||
img.style.width = '40px';
|
img.style.width = '40px';
|
||||||
placeholder.appendChild(img);
|
placeholder.appendChild(img);
|
||||||
|
} else if (typeof renderPanelTextFallback === 'function') {
|
||||||
|
renderPanelTextFallback(placeholder, code);
|
||||||
} else {
|
} else {
|
||||||
placeholder.textContent = code;
|
placeholder.textContent = code;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,8 @@
|
||||||
border: 1px solid #e9ecef !important;
|
border: 1px solid #e9ecef !important;
|
||||||
border-radius: 6px !important;
|
border-radius: 6px !important;
|
||||||
background-color: #ffffff !important;
|
background-color: #ffffff !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
box-sizing: border-box !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-item:hover, .panel-item-create:hover {
|
.panel-item:hover, .panel-item-create:hover {
|
||||||
|
|
@ -137,6 +139,65 @@
|
||||||
background-color: rgba(13, 110, 253, 0.05) !important;
|
background-color: rgba(13, 110, 253, 0.05) !important;
|
||||||
box-shadow: 0 4px 12px rgba(13, 110, 253, 0.1) !important;
|
box-shadow: 0 4px 12px rgba(13, 110, 253, 0.1) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.panel-img-placeholder {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
box-sizing: border-box !important;
|
||||||
|
padding: 2px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-text-fallback {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
max-height: 44px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border: 1px dashed #d0d5dd;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 2px 3px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: background-color 0.15s ease, border-color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-fallback-text {
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 4;
|
||||||
|
line-clamp: 4;
|
||||||
|
max-height: 40px;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
word-break: break-word;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #495057;
|
||||||
|
letter-spacing: -0.2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-item.selected .panel-text-fallback,
|
||||||
|
.panel-item-create.selected .panel-text-fallback,
|
||||||
|
.panel-item.bg-primary .panel-text-fallback,
|
||||||
|
.panel-item-create.bg-primary .panel-text-fallback {
|
||||||
|
background-color: rgba(13, 110, 253, 0.12) !important;
|
||||||
|
border-color: rgba(13, 110, 253, 0.35) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-item.selected .panel-fallback-text,
|
||||||
|
.panel-item-create.selected .panel-fallback-text {
|
||||||
|
color: #0d6efd !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-item.bg-primary .panel-fallback-text,
|
||||||
|
.panel-item-create.bg-primary .panel-fallback-text {
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock head %}
|
{% endblock head %}
|
||||||
|
|
||||||
|
|
@ -306,12 +367,12 @@
|
||||||
data-code="{{ panel.code }}"
|
data-code="{{ panel.code }}"
|
||||||
data-group="{{ group_key }}"
|
data-group="{{ group_key }}"
|
||||||
data-image-url="{{ panel.image_url|default:'' }}"
|
data-image-url="{{ panel.image_url|default:'' }}"
|
||||||
style="cursor: pointer; width: 68px; transition: transform 0.15s ease;"
|
style="cursor: pointer; width: 68px; overflow: hidden; box-sizing: border-box; transition: transform 0.15s ease;"
|
||||||
title="{{ panel.code }} - {{ panel.name }}">
|
title="{{ panel.code }} - {{ panel.name }}">
|
||||||
<div class="panel-img-placeholder mb-1" style="height: 48px; display: flex; align-items: center; justify-content: center;">
|
<div class="panel-img-placeholder mb-1" style="height: 48px; width: 100%; display: flex; align-items: center; justify-content: center; overflow: hidden; box-sizing: border-box;">
|
||||||
<!-- Content will be injected by JS -->
|
<!-- Content will be injected by JS -->
|
||||||
</div>
|
</div>
|
||||||
<div class="small text-truncate fw-bold text-muted" style="font-size: 0.65rem;">{{ panel.code }}</div>
|
<div class="small text-truncate fw-bold text-muted" style="font-size: 0.65rem; width: 100%; max-width: 100%; overflow: hidden;">{{ panel.code }}</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
@ -2016,7 +2077,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
if (placeholder && placeholder.children.length === 0) {
|
if (placeholder && placeholder.children.length === 0) {
|
||||||
const imageUrl = item.dataset.imageUrl;
|
const imageUrl = item.dataset.imageUrl;
|
||||||
const code = item.dataset.code;
|
const code = item.dataset.code;
|
||||||
if (imageUrl) {
|
if (typeof appendPanelTypeImage === 'function') {
|
||||||
|
appendPanelTypeImage(placeholder, code, imageUrl);
|
||||||
|
} else if (imageUrl) {
|
||||||
const img = document.createElement('img');
|
const img = document.createElement('img');
|
||||||
img.src = imageUrl;
|
img.src = imageUrl;
|
||||||
img.style.maxHeight = '100%';
|
img.style.maxHeight = '100%';
|
||||||
|
|
@ -2027,6 +2090,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
img.style.height = '40px';
|
img.style.height = '40px';
|
||||||
img.style.width = '40px';
|
img.style.width = '40px';
|
||||||
placeholder.appendChild(img);
|
placeholder.appendChild(img);
|
||||||
|
} else if (typeof renderPanelTextFallback === 'function') {
|
||||||
|
renderPanelTextFallback(placeholder, code);
|
||||||
} else {
|
} else {
|
||||||
placeholder.textContent = code;
|
placeholder.textContent = code;
|
||||||
}
|
}
|
||||||
|
|
@ -2177,8 +2242,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
const item = document.createElement('div');
|
const item = document.createElement('div');
|
||||||
item.className = 'd-flex justify-content-between align-items-center bg-white border rounded p-1 mb-1 shadow-sm';
|
item.className = 'd-flex justify-content-between align-items-center bg-white border rounded p-1 mb-1 shadow-sm';
|
||||||
item.innerHTML = `
|
item.innerHTML = `
|
||||||
<span class="small fw-bold ms-1 text-primary">${code}</span>
|
<span class="small fw-bold ms-1 text-primary text-truncate" style="max-width: 200px;" title="${code}">${code}</span>
|
||||||
<button type="button" class="btn btn-sm btn-link text-danger p-0 me-1 remove-panel-type" data-id="${id}" title="Retirer">
|
<button type="button" class="btn btn-sm btn-link text-danger p-0 me-1 remove-panel-type flex-shrink-0" data-id="${id}" title="Retirer">
|
||||||
<i class="bi bi-x"></i>
|
<i class="bi bi-x"></i>
|
||||||
</button>
|
</button>
|
||||||
`;
|
`;
|
||||||
|
|
@ -2195,8 +2260,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
const item = document.createElement('div');
|
const item = document.createElement('div');
|
||||||
item.className = 'd-flex justify-content-between align-items-center bg-white border rounded p-1 mb-1 shadow-sm';
|
item.className = 'd-flex justify-content-between align-items-center bg-white border rounded p-1 mb-1 shadow-sm';
|
||||||
item.innerHTML = `
|
item.innerHTML = `
|
||||||
<span class="small fw-bold ms-1 text-primary">${code}</span>
|
<span class="small fw-bold ms-1 text-primary text-truncate" style="max-width: 200px;" title="${code}">${code}</span>
|
||||||
<button type="button" class="btn btn-sm btn-link text-danger p-0 me-1 remove-panel-type" data-id="${id}" title="Retirer">
|
<button type="button" class="btn btn-sm btn-link text-danger p-0 me-1 remove-panel-type flex-shrink-0" data-id="${id}" title="Retirer">
|
||||||
<i class="bi bi-x"></i>
|
<i class="bi bi-x"></i>
|
||||||
</button>
|
</button>
|
||||||
`;
|
`;
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,8 @@
|
||||||
border: 1px solid #e9ecef !important;
|
border: 1px solid #e9ecef !important;
|
||||||
border-radius: 6px !important;
|
border-radius: 6px !important;
|
||||||
background-color: #ffffff !important;
|
background-color: #ffffff !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
box-sizing: border-box !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-item:hover, .panel-item-create:hover {
|
.panel-item:hover, .panel-item-create:hover {
|
||||||
|
|
@ -92,6 +94,62 @@
|
||||||
border-color: #0d6efd !important;
|
border-color: #0d6efd !important;
|
||||||
box-shadow: 0 4px 12px rgba(13, 110, 253, 0.15) !important;
|
box-shadow: 0 4px 12px rgba(13, 110, 253, 0.15) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.panel-img-placeholder {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
box-sizing: border-box !important;
|
||||||
|
padding: 2px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-text-fallback {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
max-height: 44px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border: 1px dashed #d0d5dd;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 2px 3px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: background-color 0.15s ease, border-color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-fallback-text {
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 4;
|
||||||
|
line-clamp: 4;
|
||||||
|
max-height: 40px;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
word-break: break-word;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #495057;
|
||||||
|
letter-spacing: -0.2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-item.bg-primary .panel-text-fallback,
|
||||||
|
.panel-item-create.bg-primary .panel-text-fallback,
|
||||||
|
.panel-item.selected .panel-text-fallback,
|
||||||
|
.panel-item-create.selected .panel-text-fallback {
|
||||||
|
background-color: rgba(255, 255, 255, 0.2) !important;
|
||||||
|
border-color: rgba(255, 255, 255, 0.4) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-item.bg-primary .panel-fallback-text,
|
||||||
|
.panel-item-create.bg-primary .panel-fallback-text,
|
||||||
|
.panel-item.selected .panel-fallback-text,
|
||||||
|
.panel-item-create.selected .panel-fallback-text {
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- Accordion for panel categories -->
|
<!-- Accordion for panel categories -->
|
||||||
|
|
@ -119,12 +177,12 @@
|
||||||
data-id="{{ panel.id }}"
|
data-id="{{ panel.id }}"
|
||||||
data-code="{{ panel.code }}"
|
data-code="{{ panel.code }}"
|
||||||
data-image-url="{{ panel.image_url|default:'' }}"
|
data-image-url="{{ panel.image_url|default:'' }}"
|
||||||
style="cursor: pointer; width: 68px; transition: transform 0.15s ease;"
|
style="cursor: pointer; width: 68px; overflow: hidden; box-sizing: border-box; transition: transform 0.15s ease;"
|
||||||
title="{{ panel.code }} - {{ panel.name }}">
|
title="{{ panel.code }} - {{ panel.name }}">
|
||||||
<div class="panel-img-placeholder mb-1" style="height: 48px; display: flex; align-items: center; justify-content: center;">
|
<div class="panel-img-placeholder mb-1" style="height: 48px; width: 100%; display: flex; align-items: center; justify-content: center; overflow: hidden; box-sizing: border-box;">
|
||||||
<!-- Content will be injected by JS -->
|
<!-- Content will be injected by JS -->
|
||||||
</div>
|
</div>
|
||||||
<div class="small text-truncate fw-bold text-muted" style="font-size: 0.65rem;">{{ panel.code }}</div>
|
<div class="small text-truncate fw-bold text-muted" style="font-size: 0.65rem; width: 100%; max-width: 100%; overflow: hidden;">{{ panel.code }}</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue