Files
safetynet/apps/server/public/branding-admin.js
T

13 lines
3.6 KiB
JavaScript
Raw Normal View History

// Company identity uses the existing signed policy publication and review flow.
let companyLogoDataUrl;
const brandingFields=['brand-logo-file','brand-logo-remove','startpage-title','startpage-subtitle','startpage-news-enabled','startpage-feed-url'];
function previewCompanyLogo(){const image=$('brand-logo-preview');image.hidden=!companyLogoDataUrl;if(companyLogoDataUrl)image.src=companyLogoDataUrl;else image.removeAttribute('src');}
function fillCompanyBranding(p){companyLogoDataUrl=p.brand?.logoDataUrl;$('startpage-title').value=p.startPage?.title??'Welcome';$('startpage-subtitle').value=p.startPage?.subtitle??'';$('startpage-news-enabled').checked=p.startPage?.newsEnabled===true;$('startpage-feed-url').value=p.startPage?.feedUrl??'';$('brand-logo-file').value='';$('brand-logo-status').textContent='';previewCompanyLogo();}
const fillBeforeCompanyBranding=fill;fill=function(p){fillBeforeCompanyBranding(p);fillCompanyBranding(p);};
const draftBeforeCompanyBranding=draft;draft=function(){const p=draftBeforeCompanyBranding();p.brand={...(policy?.brand||{}),...p.brand};if(companyLogoDataUrl)p.brand.logoDataUrl=companyLogoDataUrl;else delete p.brand.logoDataUrl;p.startPage={...(policy?.startPage||{}),title:$('startpage-title').value.trim(),subtitle:$('startpage-subtitle').value.trim(),newsEnabled:$('startpage-news-enabled').checked};const url=$('startpage-feed-url').value.trim();if(url)p.startPage.feedUrl=url;else delete p.startPage.feedUrl;return p;};
const permissionsBeforeCompanyBranding=applyOperatorPermissions;applyOperatorPermissions=function(principal){permissionsBeforeCompanyBranding(principal);const allowed=principal.permissions?.includes('policy.write')||(independentApprovalRequired&&principal.role==='operator');for(const id of brandingFields)$(id).disabled=!allowed;};
async function encodeCompanyLogo(file){if(!file||file.type!=='image/png'||file.size>128*1024)throw Error('Choose a PNG no larger than 128 KiB.');const bytes=new Uint8Array(await file.arrayBuffer());if(bytes.length<24||[137,80,78,71,13,10,26,10].some((v,i)=>bytes[i]!==v)||[73,72,68,82].some((v,i)=>bytes[12+i]!==v))throw Error('The file is not a PNG.');const dimension=offset=>bytes[offset]*16777216+bytes[offset+1]*65536+bytes[offset+2]*256+bytes[offset+3];if(dimension(16)<1||dimension(20)<1||dimension(16)>1024||dimension(20)>1024)throw Error('PNG dimensions must be at most 1024 × 1024 pixels.');const image=await createImageBitmap(file);try{if(image.width<1||image.height<1||image.width>1024||image.height>1024)throw Error('PNG dimensions must be at most 1024 × 1024 pixels.');const canvas=document.createElement('canvas');canvas.width=image.width;canvas.height=image.height;canvas.getContext('2d').drawImage(image,0,0);const encoded=canvas.toDataURL('image/png');if(encoded.length>180000||Math.ceil((encoded.length-22)*3/4)>128*1024)throw Error('Re-encoded logo is too large. Use a simpler or smaller PNG.');return encoded;}finally{image.close();}}
$('brand-logo-file').addEventListener('change',task(async()=>{const file=$('brand-logo-file').files[0];if(!file)return;companyLogoDataUrl=await encodeCompanyLogo(file);previewCompanyLogo();$('brand-logo-status').textContent='Logo added to draft. Publish policy to roll it out.';updateDraftStatus();}));
$('brand-logo-remove').addEventListener('click',()=>{companyLogoDataUrl=undefined;previewCompanyLogo();$('brand-logo-file').value='';$('brand-logo-status').textContent='Logo removal is in the draft.';updateDraftStatus();});
for(const id of brandingFields.slice(2))$(id).addEventListener('change',()=>updateDraftStatus());
if(policy)fillCompanyBranding(policy);if(currentPrincipal)applyOperatorPermissions(currentPrincipal);