Add files via upload
This commit is contained in:
18 files changed
+12656
No files matched your search
Vendored
+348
@@ -0,0 +1,348 @@
|
||||
|
||||
(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/flipnLine truncated
|
||||
|
||||
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);
|
||||
})();
|
||||
|
||||
+2462
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="240" height="240" viewBox="0 0 240 240">
|
||||
<path fill="#AE301F" fill-rule="evenodd" d="M64,48 L88,48 C96.836556,48 104,55.163444 104,64 L104,176 C104,184.836556 96.836556,192 88,192 L64,192 C55.163444,192 48,184.836556 48,176 L48,64 C48,55.163444 55.163444,48 64,48 Z M152,48 L176,48 C184.836556,48 192,55.163444 192,64 L192,176 C192,184.836556 184.836556,192 176,192 L152,192 C143.163444,192 136,184.836556 136,176 L136,64 C136,55.163444 143.163444,48 152,48 Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 519 B |
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 9.3 KiB |
@@ -0,0 +1,117 @@
|
||||
(function (global) {
|
||||
'use strict';
|
||||
|
||||
const WIDTH = 256;
|
||||
const HEIGHT = 192;
|
||||
const MAX_BYTES = 1024 * 1024;
|
||||
const MAX_PAGES = 999;
|
||||
const SAMPLE_RATE = 32768;
|
||||
|
||||
function checkFile(buffer) {
|
||||
if (!(buffer instanceof ArrayBuffer) || buffer.byteLength < 0x6C0 || buffer.byteLength > MAX_BYTES) {
|
||||
throw new Error('Choose a complete Flipnote Studio PPM, no larger than 1,024 KB.');
|
||||
}
|
||||
const view = new DataView(buffer);
|
||||
if (view.getUint32(0, false) !== 0x50415241) throw new Error('That file is not a Flipnote Studio PPM.');
|
||||
const pages = view.getUint16(12, true) + 1;
|
||||
const animationEnd = 0x6A0 + view.getUint32(4, true);
|
||||
if (pages > MAX_PAGES || animationEnd < 0x6A8 || animationEnd + pages > buffer.byteLength) {
|
||||
throw new Error('The PPM page data is incomplete or exceeds 999 pages.');
|
||||
}
|
||||
const tableBytes = view.getUint16(0x6A0, true);
|
||||
const firstFrame = 0x6A8 + tableBytes;
|
||||
if (tableBytes < pages * 4 || firstFrame >= animationEnd) throw new Error('The PPM page table is invalid.');
|
||||
for (let index = 0; index < pages; index++) {
|
||||
if (firstFrame + view.getUint32(0x6A8 + index * 4, true) >= animationEnd) {
|
||||
throw new Error('A PPM page points outside the animation data.');
|
||||
}
|
||||
}
|
||||
const soundStart = Math.ceil((animationEnd + pages) / 4) * 4;
|
||||
if (soundStart + 32 + view.getUint32(8, true) > buffer.byteLength) {
|
||||
throw new Error('The PPM audio data is incomplete.');
|
||||
}
|
||||
let audioBytes = 0;
|
||||
for (let index = 0; index < 4; index++) audioBytes += view.getUint32(soundStart + index * 4, true);
|
||||
if (audioBytes > view.getUint32(8, true)) throw new Error('The PPM audio track lengths are invalid.');
|
||||
return pages;
|
||||
}
|
||||
|
||||
function base64(bytes) {
|
||||
let text = '';
|
||||
for (let offset = 0; offset < bytes.length; offset += 8192) {
|
||||
text += String.fromCharCode(...bytes.subarray(offset, offset + 8192));
|
||||
}
|
||||
return btoa(text);
|
||||
}
|
||||
|
||||
function layerData(pixels) {
|
||||
const bytes = new Uint8Array(WIDTH * HEIGHT / 8);
|
||||
for (let index = 0; index < pixels.length; index++) {
|
||||
if (pixels[index]) bytes[index >> 3] |= 1 << (index & 7);
|
||||
}
|
||||
return base64(bytes);
|
||||
}
|
||||
|
||||
function pcmWav(pcm) {
|
||||
const bytes = new Uint8Array(44 + pcm.length * 2);
|
||||
const view = new DataView(bytes.buffer);
|
||||
const text = (offset, value) => { for (let i = 0; i < value.length; i++) bytes[offset + i] = value.charCodeAt(i); };
|
||||
text(0, 'RIFF'); view.setUint32(4, bytes.length - 8, true); text(8, 'WAVE');
|
||||
text(12, 'fmt '); view.setUint32(16, 16, true); view.setUint16(20, 1, true);
|
||||
view.setUint16(22, 1, true); view.setUint32(24, SAMPLE_RATE, true);
|
||||
view.setUint32(28, SAMPLE_RATE * 2, true); view.setUint16(32, 2, true); view.setUint16(34, 16, true);
|
||||
text(36, 'data'); view.setUint32(40, pcm.length * 2, true);
|
||||
for (let i = 0; i < pcm.length; i++) view.setInt16(44 + i * 2, pcm[i], true);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
function decode(buffer, filename = 'Flipnote.ppm') {
|
||||
checkFile(buffer);
|
||||
const parser = new global.flipnote.PpmParser(buffer);
|
||||
const flags = parser.decodeSoundFlags();
|
||||
const ink = index => index === 2 ? 'red' : index === 3 ? 'blue' : 'black';
|
||||
const frames = [];
|
||||
for (let index = 0; index < parser.frameCount; index++) {
|
||||
const layers = parser.decodeFrame(index);
|
||||
const palette = parser.getFramePaletteIndices(index);
|
||||
frames.push({
|
||||
layer1: layerData(layers[0]), layer2: layerData(layers[1]),
|
||||
colours: [ink(palette[1]), ink(palette[2])],
|
||||
paper: palette[0] === 1 ? 'black' : 'white',
|
||||
sounds: {sound1: flags[index][0], sound2: flags[index][1], sound3: flags[index][2]},
|
||||
});
|
||||
}
|
||||
const audio = {};
|
||||
const tracks = [['music', global.flipnote.FlipnoteAudioTrack.BGM],
|
||||
['sound1', global.flipnote.FlipnoteAudioTrack.SE1],
|
||||
['sound2', global.flipnote.FlipnoteAudioTrack.SE2],
|
||||
['sound3', global.flipnote.FlipnoteAudioTrack.SE3]];
|
||||
for (const [key, track] of tracks) {
|
||||
if (!parser.hasAudioTrack(track)) continue;
|
||||
const pcm = parser.getAudioTrackPcm(track, SAMPLE_RATE);
|
||||
if (!pcm.length) continue;
|
||||
audio[key] = {data: base64(pcmWav(pcm)), mime:'audio/wav', duration:pcm.length / SAMPLE_RATE,
|
||||
reference_fps:parser.framerate, source_name:filename, source_kind:'ppm_import'};
|
||||
}
|
||||
return {
|
||||
project: {
|
||||
title:String(filename).replace(/\.ppm$/i, '').slice(0, 32) || 'Imported Flipnote',
|
||||
speed_level:parser.frameSpeed, loop:parser.meta.loop,
|
||||
thumbnail_frame:Math.max(0, Math.min(frames.length - 1, parser.thumbFrameIndex)),
|
||||
layer_visibility:[parser.layerVisibility[1], parser.layerVisibility[2]], frames, audio,
|
||||
},
|
||||
source:{filename, original_author_name:parser.meta.current.username},
|
||||
};
|
||||
}
|
||||
|
||||
async function read(file) {
|
||||
if (!file || !/\.ppm$/i.test(file.name || '')) throw new Error('Choose a Flipnote Studio .PPM file.');
|
||||
if (file.size > MAX_BYTES) throw new Error('That PPM is larger than the 1,024 KB limit.');
|
||||
try { return decode(await file.arrayBuffer(), file.name); }
|
||||
catch (error) { throw new Error('Could not open this PPM: ' + (error.message || 'invalid file data')); }
|
||||
}
|
||||
|
||||
const api = Object.freeze({read, decode});
|
||||
global.FlipnotePpmImport = api;
|
||||
if (typeof module === 'object' && module.exports) module.exports = api;
|
||||
})(typeof window === 'object' ? window : globalThis);
|
||||
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 James Daniel
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
Vendored
+8232
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user
