caterium-app/public/core/performance.js
2026-09-09 08:59:16 +00:00

133 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

(()=>{
'use strict';
const VERSION='17.7.3';
const RELEASE='20260909-v17-7-3-clients-server-read';
const critical=img=>img.closest('header,.brand,#sunCloudAuthGate,.sun-auth-gate')||img.id==='sunLoginLogo'||img.classList.contains('sun-live-catalog-logo');
const tune=img=>{
if(!(img instanceof HTMLImageElement)||critical(img))return;
if(!img.hasAttribute('loading'))img.loading='lazy';
if(!img.hasAttribute('decoding'))img.decoding='async';
if(!img.hasAttribute('fetchpriority'))img.setAttribute('fetchpriority','low');
};
const scan=root=>{
if(root instanceof HTMLImageElement)tune(root);
root?.querySelectorAll?.('img').forEach(tune);
};
const pendingImageRoots=new Set();let imageScanTimer=0;
function queueImageScan(root){if(!root||root.nodeType!==1)return;for(const existing of [...pendingImageRoots]){if(existing===root||existing.contains?.(root))return;if(root.contains?.(existing))pendingImageRoots.delete(existing)}pendingImageRoots.add(root);if(imageScanTimer)return;imageScanTimer=setTimeout(()=>{imageScanTimer=0;const roots=[...pendingImageRoots];pendingImageRoots.clear();roots.forEach(scan)},80);}
const MAX_FILE=15*1024*1024,TARGET=2*1024*1024,MAX_SIDE=2048;
const guardedInputs=new WeakSet();
const toast=(text,type='info',ms=4500)=>{try{return window.SunEnterprise?.toast?.(text,type,ms)}catch(_){}};
const jpegName=name=>String(name||'photo').replace(/\.[^.]+$/,'')+'.jpg';
const canvasBlob=(canvas,type,quality)=>new Promise((resolve,reject)=>canvas.toBlob(b=>b?resolve(b):reject(new Error('Не удалось сжать изображение.')),type,quality));
async function decodeImage(file){
if('createImageBitmap' in window){const bitmap=await createImageBitmap(file);return {source:bitmap,width:bitmap.width,height:bitmap.height,close:()=>bitmap.close?.()};}
const url=URL.createObjectURL(file);try{const img=new Image();await new Promise((resolve,reject)=>{img.onload=resolve;img.onerror=reject;img.src=url});return {source:img,width:img.naturalWidth,height:img.naturalHeight,close:()=>URL.revokeObjectURL(url)};}catch(e){URL.revokeObjectURL(url);throw e;}
}
async function compressImage(file){
if(!(file instanceof File)||!String(file.type||'').startsWith('image/')||/gif|svg/i.test(file.type||''))return file;
let decoded;try{decoded=await decodeImage(file);}catch(_){return file;}
try{
const {width,height,source}=decoded;if(!width||!height)return file;
if(file.size<=TARGET&&Math.max(width,height)<=MAX_SIDE)return file;
let scale=Math.min(1,MAX_SIDE/Math.max(width,height)),quality=.84,best=null;
for(let pass=0;pass<5;pass++){
const w=Math.max(1,Math.round(width*scale)),h=Math.max(1,Math.round(height*scale));
const canvas=document.createElement('canvas');canvas.width=w;canvas.height=h;
const ctx=canvas.getContext('2d',{alpha:false});if(!ctx)return file;ctx.fillStyle='#fff';ctx.fillRect(0,0,w,h);ctx.drawImage(source,0,0,w,h);
const blob=await canvasBlob(canvas,'image/jpeg',quality);if(!best||blob.size<best.size)best=blob;if(blob.size<=TARGET)break;
quality=Math.max(.60,quality-.08);scale*=.86;
}
if(!best||best.size>=file.size)return file;
return new File([best],jpegName(file.name),{type:'image/jpeg',lastModified:file.lastModified||Date.now()});
}finally{decoded.close?.();}
}
async function prepareAttachment(file){const prepared=await compressImage(file);if(prepared.size>MAX_FILE)throw new Error(`Файл «${file.name}» больше 15 МБ даже после оптимизации.`);return {file:prepared,originalSize:file.size,savedBytes:Math.max(0,file.size-prepared.size)};}
async function guardChatFiles(input){
const raw=[...(input.files||[])];if(!raw.length)return;
const prepared=[];let saved=0;
for(const f of raw){try{const p=await prepareAttachment(f);prepared.push(p.file);saved+=p.savedBytes;}catch(e){toast(e.message||String(e),'warn',6000);}}
if(!prepared.length){input.value='';return;}
const dt=new DataTransfer();prepared.forEach(f=>dt.items.add(f));input.files=dt.files;
guardedInputs.add(input);input.dispatchEvent(new Event('change',{bubbles:true}));guardedInputs.delete(input);
if(saved>256*1024)toast(`Фото оптимизированы: сэкономлено ${(saved/1024/1024).toFixed(1)} МБ.`,'success',4200);
}
document.addEventListener('change',e=>{
const input=e.target;if(!(input instanceof HTMLInputElement)||input.id!=='sunChatFilesV29'||guardedInputs.has(input)||typeof DataTransfer==='undefined')return;
e.preventDefault();e.stopImmediatePropagation();guardChatFiles(input).catch(err=>{console.error('[Caterium photo compression]',err);guardedInputs.add(input);input.dispatchEvent(new Event('change',{bubbles:true}));guardedInputs.delete(input);});
},true);
window.SunAttachmentGuard={VERSION,MAX_FILE,TARGET,MAX_SIDE,compressImage,prepareAttachment};
let memoryData=null,memoryAt=0,memoryPromise=null,memoryTimer=0;
const MEMORY_CACHE_MS=15000,MEMORY_REFRESH_MS=30000,MEMORY_TIMEOUT_MS=8000;
const esc=v=>window.SunSafe?.escapeHTML?window.SunSafe.escapeHTML(String(v??'')):String(v??'');
const developerVisible=()=>document.getElementById('sun-developer-console-v22')?.classList.contains('on');
async function loadMemory({force=false}={}){
const now=Date.now();if(memoryPromise)return memoryPromise;if(!force&&memoryData&&now-memoryAt<MEMORY_CACHE_MS)return memoryData;
const c=window.SunCloudV2?.getClient?.();if(!c)return null;
memoryPromise=(async()=>{
let timer=0;
try{
const timeout=new Promise((_,reject)=>{timer=setTimeout(()=>reject(new Error('Developer memory timeout')),MEMORY_TIMEOUT_MS)});
const rpc=Promise.resolve(c.rpc('sun_dev_dashboard'));
const r=await Promise.race([rpc,timeout]);
if(r?.error)throw r.error;memoryData=r?.data||null;memoryAt=Date.now();return memoryData;
}catch(error){console.warn('[Caterium developer memory]',error?.message||error);return memoryData}
finally{clearTimeout(timer);memoryPromise=null}
})();
return memoryPromise;
}
async function enhanceDeveloperMemory({force=false}={}){
const view=document.getElementById('sun-developer-console-v22');if(!view?.classList.contains('on'))return null;
const body=document.getElementById('sunDevBody');if(!body)return null;
const version=document.getElementById('sunDevReleaseVersion');if(version)version.textContent=VERSION;
const d=await loadMemory({force});if(!d||!developerVisible())return d;
let box=document.getElementById('sunDevMemoryV1761');if(!box){box=document.createElement('div');box.id='sunDevMemoryV1761';box.className='sun-dev-grid';box.style.marginBottom='12px';body.prepend(box);}
const html=`<div class="sun-dev-kpi"><small>Память сервера</small><b>${esc(d.server_size||d.database_size||'—')}</b></div><div class="sun-dev-kpi"><small>База PostgreSQL</small><b>${esc(d.database_size||'—')}</b></div><div class="sun-dev-kpi"><small>Файлы Storage</small><b>${esc(d.storage_size||'—')}</b></div><div class="sun-dev-kpi"><small>Объектов Storage</small><b>${Number(d.storage_objects||0)}</b></div>`;
if(box.innerHTML!==html)box.innerHTML=html;
return d;
}
function scheduleMemoryRefresh(delay=80,force=false){setTimeout(()=>{if(developerVisible())enhanceDeveloperMemory({force}).catch(()=>{})},delay)}
function startMemoryTimer(){if(memoryTimer)return;memoryTimer=setInterval(()=>{if(!document.hidden&&developerVisible())enhanceDeveloperMemory({force:true}).catch(()=>{})},MEMORY_REFRESH_MS)}
function loadDataLayer(){
if(window.CateriumDataV1773||document.getElementById('cateriumDataV1773Script'))return;
const script=document.createElement('script');script.id='cateriumDataV1773Script';script.src=`core/data-layer-v1773.js?v=${RELEASE}`;script.async=false;script.onerror=()=>console.error('[Caterium] Не загрузился data-layer-v1773.js');document.head.appendChild(script);
}
function loadServerAutomation(){
if(window.CateriumServerAutomationV1770||document.getElementById('cateriumServerAutomationV1770Script'))return;
const script=document.createElement('script');script.id='cateriumServerAutomationV1770Script';script.src=`core/server-automation-v1770.js?v=${RELEASE}`;script.async=true;script.onerror=()=>console.error('[Caterium] Не загрузился server-automation-v1770.js');document.head.appendChild(script);
}
function loadHotfix(){
if(window.SunHotfixV1763||document.getElementById('sunHotfixV1763Script'))return;
const script=document.createElement('script');script.id='sunHotfixV1763Script';script.src=`core/hotfix-v1763.js?v=${RELEASE}`;script.async=true;script.onerror=()=>console.error('[Caterium] Не загрузился модуль hotfix-v1763.js');document.head.appendChild(script);
}
function loadOpsUX(){
if(window.SunOpsUXV1762||document.getElementById('sunOpsUXV1762Script'))return;
const script=document.createElement('script');script.id='sunOpsUXV1762Script';script.src=`core/ops-ux-v1762.js?v=${RELEASE}`;script.async=true;script.onerror=()=>console.error('[Caterium] Не загрузился модуль ops-ux-v1762.js');document.head.appendChild(script);
}
function loadUXFix(){
if(window.SunUXFixV1764||document.getElementById('sunUXFixV1764Script'))return;
const script=document.createElement('script');script.id='sunUXFixV1764Script';script.src=`core/ux-fixes-v1764.js?v=${RELEASE}`;script.async=true;script.onerror=()=>console.error('[Caterium] Не загрузился модуль ux-fixes-v1764.js');document.head.appendChild(script);
}
function loadDeveloperUX(){
if(window.SunDeveloperUXV1768||document.getElementById('sunDeveloperUXV1768Script'))return;
const script=document.createElement('script');script.id='sunDeveloperUXV1768Script';script.src=`core/developer-console-v1768.js?v=${RELEASE}`;script.async=true;script.onerror=()=>console.error('[Caterium] Не загрузился модуль developer-console-v1768.js');document.head.appendChild(script);
}
function loadOfferWorkspace(){
if(window.SunOfferWorkspaceV1769||document.getElementById('sunOfferWorkspaceV1769Script'))return;
const script=document.createElement('script');script.id='sunOfferWorkspaceV1769Script';script.src=`core/offer-workspace-v1769.js?v=${RELEASE}`;script.async=true;script.onerror=()=>console.error('[Caterium] Не загрузился модуль offer-workspace-v1769.js');document.head.appendChild(script);
}
const start=()=>{
loadDataLayer();loadServerAutomation();loadHotfix();loadOpsUX();loadUXFix();loadDeveloperUX();loadOfferWorkspace();scan(document);startMemoryTimer();
const mo=new MutationObserver(records=>{records.forEach(r=>r.addedNodes.forEach(n=>{if(n.nodeType===1)queueImageScan(n)}));});
mo.observe(document.documentElement,{childList:true,subtree:true});
document.addEventListener('click',e=>{if(e.target.closest('#sunDeveloperNavV22,[data-dev-tab],#sunDevRefresh'))scheduleMemoryRefresh(100,true)},true);
window.addEventListener('sun:cloud-state-applied',()=>scheduleMemoryRefresh(180,true));
document.addEventListener('visibilitychange',()=>{if(!document.hidden&&developerVisible())scheduleMemoryRefresh(50,false)});
window.SunPerformance={VERSION,scanImages:()=>scan(document),refreshDeveloperMemory:(force=true)=>enhanceDeveloperMemory({force}),loadDataLayer,loadServerAutomation,loadHotfix,loadOpsUX,loadUXFix,loadDeveloperUX,loadOfferWorkspace,disconnect:()=>{mo.disconnect();if(memoryTimer){clearInterval(memoryTimer);memoryTimer=0}if(imageScanTimer){clearTimeout(imageScanTimer);imageScanTimer=0}pendingImageRoots.clear();}};
};
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',start,{once:true});else start();
})();