Files
flipnote-editor/assets/bootstrap.js
T
2026-10-29 11:56:15 +00:00

348 lines
26 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(function(){
var loader=document.getElementById('flipnote-editor-loader');
var statusEl=loader&&loader.querySelector('[data-flipnote-loader-status]');
var progressEl=loader&&loader.querySelector('.flipnote-editor-loader-progress');
var progressFill=loader&&loader.querySelector('[data-flipnote-loader-progress]');
var copyEl=loader&&loader.querySelector('.flipnote-editor-loader-copy');
var titleEl=loader&&loader.querySelector('.flipnote-editor-loader-title');
var drawingEngineScript='./assets/flipnote-editor/drawing-engine.js?v=2.47-advanced-eraser-paint-memory';
var textToolScript='./assets/flipnote-editor/text-tool.js?v=3.26.2';
var editorScript='./assets/flipnote-editor/editor.js?v=3.26.2-canvas';
// The server enumerates every image in web/static/flipnote-editor on each request.
// This means newly-added button states (including manually-added tab art) join the
// preload automatically without needing another JavaScript manifest update.
// Keep a small explicit list for image assets used by the editor but stored elsewhere.
var imageUrls=[];
var imageCacheBank=document.getElementById('flipnote-editor-image-cache');
var decodedImageCache=window.__flipnoteEditorImageCache=window.__flipnoteEditorImageCache||new Map();
document.querySelectorAll('img[src]').forEach(function(img){imageUrls.push(img.getAttribute('src'));});
document.querySelectorAll('*').forEach(function(node){
Array.from(node.attributes||[]).forEach(function(attr){
if(/^data-.*src$/i.test(attr.name)&&attr.value)imageUrls.push(attr.value);
});
});
imageUrls=Array.from(new Set(imageUrls.concat(["./assets/flipnote-editor/base.png","./assets/flipnote-editor/checker.png","./assets/flipnote-editor/clear.png","./assets/flipnote-editor/clone-placeholder.png","./assets/flipnote-editor/closepages.png","./assets/flipnote-editor/colour.png","./assets/flipnote-editor/copy.png","./assets/flipnote-editor/copy_pressed.png","./assets/flipnote-editor/cut.png","./assets/flipnote-editor/delete-frame.png","./assets/flipnote-editor/downloadwebm.png","./assets/flipnote-editor/Draw.png","./assets/flipnote-editor/Draw_selected.png","./assets/flipnote-editor/earsHurt.png","./assets/flipnote-editor/editpattern.png","./assets/flipnote-editor/Erase.png","./assets/flipnote-editor/erase1.png","./assets/flipnote-editor/erase1_Adv.png","./assets/flipnote-editor/erase2.png","./assets/flipnote-editor/erase2_Adv.png","./assets/flipnote-editor/erase3.png","./assets/flipnote-editor/erase3_Adv.png","./assets/flipnote-editor/erase4.png","./assets/flipnote-editor/erase4_Adv.png","./assets/flipnote-editor/erase5.png","./assets/flipnote-editor/erase5_Adv.png","./assets/flipnote-editor/erase6.png","./assets/flipnote-editor/erase6_Adv.png","./assets/flipnote-editor/erase7.png","./assets/flipnote-editor/erase7_Adv.png","./assets/flipnote-editor/erase8.png","./assets/flipnote-editor/erase8_Adv.png","./assets/flipnote-editor/eraseaudio.png","./assets/flipnote-editor/erasebig.png","./assets/flipnote-editor/eraser.png","./assets/flipnote-editor/Erase_selected.png","./assets/flipnote-editor/fill.png","./assets/flipnote-editor/fill1.png","./assets/flipnote-editor/fill2.png","./assets/flipnote-editor/fill3.png","./assets/flipnote-editor/fill4.png","./assets/flipnote-editor/fill5.png","./assets/flipnote-editor/fill6.png","./assets/flipnote-editor/fill7.png","./assets/flipnote-editor/fill8.png","./assets/flipnote-editor/fill_selected.png","./assets/flipnote-editor/filmstrip-dots.png","./assets/flipnote-editor/first-frame.png","./assets/flipnote-editor/flip_x.png","./assets/flipnote-editor/flip_y.png","./assets/flipnote-editor/goodVolume.png","./assets/flipnote-editor/ImportImage.png","./assets/flipnote-editor/invert.png","./assets/flipnote-editor/last-frame.png","./assets/flipnote-editor/layer1.png","./assets/flipnote-editor/layer1_inactive.png","./assets/flipnote-editor/layer2.png","./assets/flipnote-editor/layer2_inactive.png","./assets/flipnote-editor/lightbox-placeholder.png","./assets/flipnote-editor/lightbox.png","./assets/flipnote-editor/lightbox_pressed.png","./assets/flipnote-editor/loop_disabled.png","./assets/flipnote-editor/loop_enabled.png","./assets/flipnote-editor/musicBarEmpty.png","./assets/flipnote-editor/musicImportalt.png","./assets/flipnote-editor/musicPlay.png","./assets/flipnote-editor/musicRec.png","./assets/flipnote-editor/musicStop.png","./assets/flipnote-editor/new-frame.png","./assets/flipnote-editor/next-frame.png","./assets/flipnote-editor/openpages.png","./assets/flipnote-editor/openppm.png","./assets/flipnote-editor/pageCopy.png","./assets/flipnote-editor/pageErase.png","./assets/flipnote-editor/pageInsert.png","./assets/flipnote-editor/pagePaste.png","./assets/flipnote-editor/Paint.png","./assets/flipnote-editor/paint1.png","./assets/flipnote-editor/paint1_Adv.png","./assets/flipnote-editor/paint2.png","./assets/flipnote-editor/paint2_Adv.png","./assets/flipnote-editor/paint3.png","./assets/flipnote-editor/paint3_Adv.png","./assets/flipnote-editor/paint4.png","./assets/flipnote-editor/paint4_Adv.png","./assets/flipnote-editor/paint5.png","./assets/flipnote-editor/paint5_Adv.png","./assets/flipnote-editor/paint6.png","./assets/flipnote-editor/paint6_Adv.png","./assets/flipnote-editor/paint7.png","./assets/flipnote-editor/paint7_Adv.png","./assets/flipnote-editor/paint8.png","./assets/flipnote-editor/paint8_Adv.png","./assets/flipnote-editor/Paint_selected.png","./assets/flipnote-editor/paper-placeholder.png","./assets/flipnote-editor/paperB.png","./assets/flipnote-editor/paperW.png","./assets/flipnote-editor/paste.png","./assets/flipnote-editor/pen.png","./assets/flipnote-editor/pen1.png","./assets/flipnote-editor/pen1_Adv.png","./assets/flipnote-editor/pen2.png","./assets/flipnote-editor/pen2_Adv.png","./assets/flipnote-editor/pen3.png","./assets/flipnote-editor/pen3_Adv.png","./assets/flipnote-editor/pen4.png","./assets/flipnote-editor/pen4_Adv.png","./assets/flipnote-editor/pen5.png","./assets/flipnote-editor/pen5_Adv.png","./assets/flipnote-editor/pen6.png","./assets/flipnote-editor/pen6_Adv.png","./assets/flipnote-editor/pen7.png","./assets/flipnote-editor/pen7_Adv.png","./assets/flipnote-editor/pen8.png","./assets/flipnote-editor/pen8_Adv.png","./assets/flipnote-editor/play.png","./assets/flipnote-editor/previous-frame.png","./assets/flipnote-editor/redo.png","./assets/flipnote-editor/redo_grey.png","./assets/flipnote-editor/resize_larger.png","./assets/flipnote-editor/resize_smaller.png","./assets/flipnote-editor/rotate_anticlockwise.png","./assets/flipnote-editor/rotate_clockwise.png","./assets/flipnote-editor/selbase.png","./assets/flipnote-editor/select.png","./assets/flipnote-editor/selectionCopy.png","./assets/flipnote-editor/selectionCut.png","./assets/flipnote-editor/select_pressed.png","./assets/flipnote-editor/select_y.png","./assets/flipnote-editor/shiftl.png","./assets/flipnote-editor/shiftl_active.png","./assets/flipnote-editor/soundBarEmpry.png","./assets/flipnote-editor/soundImport1.png","./assets/flipnote-editor/soundImport1alt.png","./assets/flipnote-editor/soundImport2.png","./assets/flipnote-editor/soundImport2alt.png","./assets/flipnote-editor/soundImport3.png","./assets/flipnote-editor/soundImport3alt.png","./assets/flipnote-editor/soundPlay1.png","./assets/flipnote-editor/soundPlay2.png","./assets/flipnote-editor/soundPlay3.png","./assets/flipnote-editor/soundRecord1.png","./assets/flipnote-editor/soundRecord2.png","./assets/flipnote-editor/soundRecord3.png","./assets/flipnote-editor/soundStop1.png","./assets/flipnote-editor/soundStop2.png","./assets/flipnote-editor/soundStop3.png","./assets/flipnote-editor/spacer.png","./assets/flipnote-editor/speed1.png","./assets/flipnote-editor/speed2.png","./assets/flipnote-editor/speed3.png","./assets/flipnote-editor/speed4.png","./assets/flipnote-editor/speed5.png","./assets/flipnote-editor/speed6.png","./assets/flipnote-editor/speed7.png","./assets/flipnote-editor/speed8.png","./assets/flipnote-editor/stop.png","./assets/flipnote-editor/tab-1.png","./assets/flipnote-editor/tab-2.png","./assets/flipnote-editor/tab-3.png","./assets/flipnote-editor/tab-4.png","./assets/flipnote-editor/tab-5.png","./assets/flipnote-editor/tab1.png","./assets/flipnote-editor/tab1_selected.png","./assets/flipnote-editor/tab2.png","./assets/flipnote-editor/tab2_selected.png","./assets/flipnote-editor/tab3.png","./assets/flipnote-editor/tab3_selected.png","./assets/flipnote-editor/tab4.png","./assets/flipnote-editor/tab4_selected.png","./assets/flipnote-editor/tab5.png","./assets/flipnote-editor/tab5_selected.png","./assets/flipnote-editor/transform.png","./assets/flipnote-editor/transform_active.png","./assets/flipnote-editor/undo.png","./assets/flipnote-editor/undo_grey.png","./assets/flipnote-editor/webm.png"]).filter(Boolean)));
var fontJobs=[{css:'16px "NDS BIOS"',sample:'Preloading assets 00 / 00'}];
var completed=0;
var total=imageUrls.length+fontJobs.length+5;
function updateProgress(){
var safeTotal=Math.max(total,1);
var percent=Math.max(0,Math.min(100,Math.round((completed/safeTotal)*100)));
if(statusEl)statusEl.textContent='Preloading assets '+Math.min(completed,total)+' / '+total;
if(progressFill)progressFill.style.width=percent+'%';
if(progressEl)progressEl.setAttribute('aria-valuenow',String(percent));
}
function doneOne(){completed+=1;updateProgress();}
function preloadImage(url){
var cached=decodedImageCache.get(url);
if(cached&&cached.promise)return cached.promise.then(function(){doneOne();});
var image=new Image();
image.alt='';
image.decoding='sync';
image.loading='eager';
image.setAttribute('aria-hidden','true');
if(imageCacheBank)imageCacheBank.appendChild(image);
var promise=new Promise(function(resolve,reject){
var finished=false;
var decoding=false;
var finish=function(ok){
if(finished)return;
finished=true;
if(!ok){reject(new Error('asset '+url));return;}
resolve(image);
};
var decodeLoaded=function(){
if(finished||decoding)return;
decoding=true;
if(typeof image.decode==='function'){
image.decode().then(function(){finish(true);}).catch(function(){
finish(image.complete&&image.naturalWidth>0);
});
}else{
finish(image.complete&&image.naturalWidth>0);
}
};
image.onload=decodeLoaded;
image.onerror=function(){finish(false);};
image.src=url;
if(image.complete&&image.naturalWidth>0)decodeLoaded();
});
decodedImageCache.set(url,{image:image,promise:promise});
return promise.then(function(){doneOne();});
}
function preloadFont(job){
if(!document.fonts||!document.fonts.load)return Promise.reject(new Error('font loading unsupported'));
return document.fonts.load(job.css,job.sample).then(function(faces){
if(!faces||!faces.length)throw new Error('font '+job.css);
doneOne();
});
}
function loadFonts(){
return Promise.all(fontJobs.map(preloadFont)).then(function(){
if(loader)loader.classList.add('fonts-ready');
if(copyEl)copyEl.setAttribute('aria-hidden','false');
});
}
function loadScript(url,label){
return new Promise(function(resolve,reject){
var script=document.createElement('script');
script.src=url;
script.onload=function(){doneOne();resolve();};
script.onerror=function(){reject(new Error(label||'editor script'));};
document.body.appendChild(script);
});
}
function loadEditor(){return loadScript('./assets/vendor/flipnote.js','PPM reader').then(function(){return loadScript('./assets/ppm-import.js','PPM importer');}).then(function(){return loadScript(drawingEngineScript,'drawing engine');}).then(function(){return loadScript(textToolScript,'text tool');}).then(function(){return loadScript(editorScript,'editor');});}
function installEditorInputFixes(){
var titleField=document.getElementById('filename');
if(titleField&&!titleField.dataset.titleLimitFix){
titleField.dataset.titleLimitFix='1';
var titleLimit=80;
function titleText(){return String(titleField.textContent||'').replace(/ /g,' ');}
function selectionLength(){
var selection=window.getSelection&&window.getSelection();
if(!selection||!selection.rangeCount)return 0;
var range=selection.getRangeAt(0);
if(!titleField.contains(range.commonAncestorContainer))return 0;
return range.toString().length;
}
function putCaretAtEnd(){
var selection=window.getSelection&&window.getSelection();
if(!selection)return;
var range=document.createRange();
range.selectNodeContents(titleField);
range.collapse(false);
selection.removeAllRanges();
selection.addRange(range);
}
titleField.addEventListener('keydown',function(event){
if(event.defaultPrevented||event.ctrlKey||event.metaKey||event.altKey)return;
if(event.key&&event.key.length===1&&titleText().length-selectionLength()>=titleLimit){
event.preventDefault();
event.stopImmediatePropagation();
}
},true);
titleField.addEventListener('beforeinput',function(event){
if(event.defaultPrevented||!event.inputType||event.inputType.indexOf('insert')!==0)return;
var incoming=typeof event.data==='string'?event.data:'';
if(event.inputType==='insertLineBreak'||event.inputType==='insertParagraph'){
event.preventDefault();
return;
}
if(titleText().length-selectionLength()+(incoming?incoming.length:1)>titleLimit){
event.preventDefault();
}
},true);
titleField.addEventListener('paste',function(event){
var clipboard=event.clipboardData||window.clipboardData;
if(!clipboard)return;
var incoming=String(clipboard.getData('text')||'').replace(/[\r\n]+/g,' ');
var room=Math.max(0,titleLimit-(titleText().length-selectionLength()));
if(incoming.length<=room)return;
event.preventDefault();
if(room>0){
var partial=incoming.slice(0,room);
if(document.execCommand)document.execCommand('insertText',false,partial);
}
},true);
titleField.addEventListener('input',function(){
var value=titleText();
if(value.length<=titleLimit)return;
titleField.textContent=value.slice(0,titleLimit);
putCaretAtEnd();
},true);
}
var paperTool=document.getElementById('paper-tool');
function pageUsesBlackPaper(){
if(!paperTool)return false;
if(paperTool.getAttribute('aria-pressed')==='true')return true;
var image=paperTool.querySelector('img');
return !!(image&&/paperB\.png(?:[?#]|$)/i.test(image.getAttribute('src')||''));
}
function syncMonochromeInkLabel(){
var label=pageUsesBlackPaper()?'White':'Black';
['flipnote-image-import-colour','flipnote-image-import-second-colour'].forEach(function(id){
var select=document.getElementById(id);
var option=select&&select.querySelector('option[value="black"]');
if(option)option.textContent=label;
});
document.querySelectorAll('.flipnote-editor-layer-color[data-layer-color="black"]').forEach(function(button){
var layer=Number(button.getAttribute('data-layer'))+1;
button.title=label;
button.setAttribute('aria-label','Layer '+layer+' '+label.toLowerCase());
});
}
syncMonochromeInkLabel();
if(paperTool&&!paperTool.dataset.rexiPaperInkLabelFix){
paperTool.dataset.rexiPaperInkLabelFix='1';
paperTool.addEventListener('click',function(){setTimeout(syncMonochromeInkLabel,0);});
var paperObserver=new MutationObserver(syncMonochromeInkLabel);
paperObserver.observe(paperTool,{attributes:true,attributeFilter:['aria-pressed','title'],subtree:true});
var paperImage=paperTool.querySelector('img');
if(paperImage)paperObserver.observe(paperImage,{attributes:true,attributeFilter:['src']});
}
var imageImportButton=document.getElementById('flipnote-import-image-button');
if(imageImportButton&&!imageImportButton.dataset.rexiInkLabelFix){
imageImportButton.dataset.rexiInkLabelFix='1';
imageImportButton.addEventListener('click',function(){setTimeout(syncMonochromeInkLabel,0);});
}
var imageApply=document.getElementById('flipnote-image-import-apply');
if(imageApply&&!imageApply.dataset.rexiImageColourFix){
imageApply.dataset.rexiImageColourFix='1';
imageApply.addEventListener('click',function(){
var mode=document.getElementById('flipnote-image-import-mode');
var first=document.getElementById('flipnote-image-import-colour');
var second=document.getElementById('flipnote-image-import-second-colour');
var layer=document.getElementById('flipnote-image-import-layer');
var chosenMode=mode?mode.value:'single';
var firstColour=first?first.value:'black';
var secondColour=second?second.value:'red';
var chosenLayer=Math.max(0,Math.min(1,Number(layer&&layer.value)||0));
function applyLayerColour(index,colour){
var button=document.querySelector('.flipnote-editor-layer-color[data-layer="'+index+'"][data-layer-color="'+colour+'"]');
if(button)button.click();
}
setTimeout(function(){
if(chosenMode==='dual'){
applyLayerColour(0,firstColour);
applyLayerColour(1,secondColour);
}else{
applyLayerColour(chosenLayer,firstColour);
}
syncMonochromeInkLabel();
},0);
},true);
}
}
function installMobileShiftToggle(){
var button=document.getElementById('flipnote-mobile-shift-toggle');
if(!button)return;
var ua=String(navigator.userAgent||'');
var uaMobile=!!(navigator.userAgentData&&navigator.userAgentData.mobile);
var ipadLike=navigator.platform==='MacIntel'&&Number(navigator.maxTouchPoints||0)>1;
var mobileOrTablet=uaMobile||ipadLike||/(Android|iPad|iPhone|iPod|Mobile|Tablet|Silk|Kindle)/i.test(ua);
var desktopSetting=document.getElementById('flipnote-editor-desktop-shift');
var desktopSettingKey='flipnote-editor.flipnote-editor.desktop-shift.v1';
var desktopEnabled=false;
try{desktopEnabled=window.localStorage.getItem(desktopSettingKey)==='1';}catch(_error){}
if(desktopSetting)desktopSetting.checked=desktopEnabled;
if(!mobileOrTablet&&desktopSetting)desktopSetting.addEventListener('change',function(){
desktopEnabled=desktopSetting.checked;
try{window.localStorage.setItem(desktopSettingKey,desktopEnabled?'1':'0');}catch(_error){}
if(!desktopEnabled)setVirtualShift(false);
button.hidden=!desktopEnabled;
});
var image=button.querySelector('img');
var normalSrc=image&&image.getAttribute('data-normal-src')||'./assets/flipnote-editor/shiftl.png';
var activeSrc=image&&image.getAttribute('data-active-src')||'./assets/flipnote-editor/shiftl_active.png';
var active=false;
function makeShiftEvent(type,down){
var event;
try{
event=new KeyboardEvent(type,{key:'Shift',code:'ShiftLeft',location:1,shiftKey:!!down,bubbles:true,cancelable:true});
}catch(_error){
event=document.createEvent('Event');
event.initEvent(type,true,true);
}
try{Object.defineProperty(event,'key',{configurable:true,get:function(){return 'Shift';}});}catch(_error){}
try{Object.defineProperty(event,'code',{configurable:true,get:function(){return 'ShiftLeft';}});}catch(_error){}
try{Object.defineProperty(event,'keyCode',{configurable:true,get:function(){return 16;}});}catch(_error){}
try{Object.defineProperty(event,'which',{configurable:true,get:function(){return 16;}});}catch(_error){}
try{Object.defineProperty(event,'shiftKey',{configurable:true,get:function(){return !!down;}});}catch(_error){}
var target=document.body||document.documentElement||document;
target.dispatchEvent(event);
}
function setVirtualShift(next){
next=!!next;
if(active===next)return;
active=next;
button.setAttribute('aria-pressed',active?'true':'false');
button.title=active?'Shift enabled':'Shift';
if(image)image.src=active?activeSrc:normalSrc;
makeShiftEvent(active?'keydown':'keyup',active);
}
function forceShiftModifier(event){
if(!active||!event)return;
try{Object.defineProperty(event,'shiftKey',{configurable:true,get:function(){return true;}});}catch(_error){}
if(typeof event.getModifierState==='function'){
var original=event.getModifierState.bind(event);
try{Object.defineProperty(event,'getModifierState',{configurable:true,value:function(key){return String(key||'').toLowerCase()==='shift'?true:original(key);}});}catch(_error){}
}
}
['pointerdown','pointerup','mousedown','mouseup','click','dblclick'].forEach(function(type){
document.addEventListener(type,forceShiftModifier,true);
});
function consumesOneShotShift(target){
if(!target||!target.closest)return false;
var slot=target.closest('[data-tool-slot]');
if(slot){
var slotImage=slot.querySelector('img');
var slotSrc=slotImage&&String(slotImage.getAttribute('src')||'');
return slotSrc.toLowerCase().indexOf('_adv.png')!==-1;
}
return !!target.closest('#copy-tool,.flipnote-editor-more-tool,[data-audio-control]');
}
button.addEventListener('click',function(){setVirtualShift(!active);});
document.addEventListener('click',function(event){
if(!active||button.contains(event.target)||!consumesOneShotShift(event.target))return;
window.setTimeout(function(){setVirtualShift(false);},0);
},true);
document.addEventListener('keyup',function(event){
if(active&&event.key==='Shift')window.setTimeout(function(){if(active)makeShiftEvent('keydown',true);},0);
});
window.addEventListener('focus',function(){if(active)makeShiftEvent('keydown',true);});
window.addEventListener('pagehide',function(){if(active)makeShiftEvent('keyup',false);});
button.hidden=!(mobileOrTablet||desktopEnabled);
}
function reveal(){
updateProgress();
document.body.classList.remove('flipnote-editor-loading');
if(!loader)return;
loader.classList.add('is-exiting');
var removed=false;
var removeLoader=function(){
if(removed)return;
removed=true;
loader.remove();
};
loader.addEventListener('transitionend',function(event){
if(event.target===loader&&event.propertyName==='transform')removeLoader();
});
window.setTimeout(removeLoader,900);
}
function fail(error){ if(loader)loader.classList.add('load-failed'); if(titleEl)titleEl.textContent='Flipnote Editor could not load'; if(statusEl)statusEl.textContent=error&&error.message?error.message:'Try refreshing the page.'; }
updateProgress();
var imageJobs=imageUrls.map(preloadImage);
var fontPromise=loadFonts();
function settleDecodedImages(){
return new Promise(function(resolve){
requestAnimationFrame(function(){requestAnimationFrame(resolve);});
});
}
Promise.all(imageJobs.concat([fontPromise])).then(settleDecodedImages).then(loadEditor).then(function(){installEditorInputFixes();installMobileShiftToggle();return settleDecodedImages();}).then(reveal).catch(fail);
})();