diff --git a/public/core/performance.js b/public/core/performance.js index ef022f3..7523561 100644 --- a/public/core/performance.js +++ b/public/core/performance.js @@ -11,11 +11,73 @@ if(root instanceof HTMLImageElement)tune(root); root?.querySelectorAll?.('img').forEach(tune); }; + + 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=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;} + if(typeof DataTransfer==='undefined')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))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:'17.6.1',MAX_FILE,TARGET,MAX_SIDE,compressImage,prepareAttachment}; + + let memoryData=null,memoryAt=0,memoryLoading=false; + const esc=v=>window.SunSafe?.escapeHTML?window.SunSafe.escapeHTML(String(v??'')):String(v??''); + async function loadMemory(){ + const now=Date.now();if(memoryLoading)return memoryData;if(memoryData&&now-memoryAt<5000)return memoryData; + const c=window.SunCloudV2?.getClient?.();if(!c)return null;memoryLoading=true; + try{const r=await c.rpc('sun_dev_dashboard');if(r.error)throw r.error;memoryData=r.data||null;memoryAt=Date.now();return memoryData;}catch(_){return null}finally{memoryLoading=false;} + } + async function enhanceDeveloperMemory(){ + const view=document.getElementById('sun-developer-console-v22');if(!view?.classList.contains('on'))return; + const body=document.getElementById('sunDevBody');if(!body)return; + const d=await loadMemory();if(!d||!view.classList.contains('on'))return; + 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);} + box.innerHTML=`
Память сервера${esc(d.server_size||d.database_size||'—')}
База PostgreSQL${esc(d.database_size||'—')}
Файлы Storage${esc(d.storage_size||'—')}
Объектов Storage${Number(d.storage_objects||0)}
`; + } const start=()=>{ scan(document); - const mo=new MutationObserver(records=>records.forEach(r=>r.addedNodes.forEach(n=>{if(n.nodeType===1)scan(n)}))); + const mo=new MutationObserver(records=>{records.forEach(r=>r.addedNodes.forEach(n=>{if(n.nodeType===1)scan(n)}));enhanceDeveloperMemory();}); mo.observe(document.documentElement,{childList:true,subtree:true}); - window.SunPerformance={scanImages:()=>scan(document),disconnect:()=>mo.disconnect()}; + document.addEventListener('click',e=>{if(e.target.closest('#sunDeveloperNavV22,[data-dev-tab],#sunDevRefresh'))setTimeout(enhanceDeveloperMemory,100)},true); + window.addEventListener('sun:cloud-state-applied',()=>setTimeout(enhanceDeveloperMemory,150)); + setInterval(()=>{if(document.getElementById('sun-developer-console-v22')?.classList.contains('on'))enhanceDeveloperMemory();},5000); + window.SunPerformance={scanImages:()=>scan(document),refreshDeveloperMemory:enhanceDeveloperMemory,disconnect:()=>mo.disconnect()}; }; if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',start,{once:true});else start(); -})(); +})(); \ No newline at end of file