VulpesOS/assets/webapps/homescreen.gaiamobile.org/js/apps.js
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

70 lines
No EOL
21 KiB
JavaScript

;'use strict';(function(exports){const RESIZE_TIMEOUT=500;const DIALOG_SHOW_TIMEOUT=50;const AUTOSCROLL_DISTANCE=40;const AUTOSCROLL_DELAY=750;const AUTOSCROLL_OVERFLOW_DELAY=500;const HIDDEN_ROLES=['system','input','homescreen','theme','addon','langpack'];const BLACKLIST=[];function Apps(){window.performance.mark('navigationLoaded');this.panel=document.getElementById('apps-panel');this.meta=document.head.querySelector('meta[name="theme-color"]');this.scrollable=document.querySelector('#apps-panel > .scrollable');this.icons=document.getElementById('apps');this.remove=document.getElementById('remove');this.rename=document.getElementById('rename');this.done=document.getElementById('done');this.cancelDownload=document.getElementById('cancel-download');this.resumeDownload=document.getElementById('resume-download');this.dialogs=[this.cancelDownload,this.resumeDownload];var dialog;for(dialog of this.dialogs){dialog.hide();}
var dialogVisibilityCallback=()=>{for(var dialog of this.dialogs){if(dialog.opened){this.meta.content='white';document.body.classList.add('dialog-active');return;}}
this.meta.content='transparent';document.body.classList.remove('dialog-active');};for(dialog of this.dialogs){var observer=new MutationObserver(dialogVisibilityCallback);observer.observe(dialog,{attributes:true,attributeFilter:['style']});}
this.resizeTimeout=null;this.pageHeight=1;this.gridHeight=1;this.pendingGridHeight=1;this.iconsPerPage=0;this.iconsLeft=0;this.iconsRight=0;this.appsVisible=false;this.dragging=false;this.draggedIndex=-1;this.autoScrollInterval=null;this.autoScrollOverflowTimeout=null;this.hoverIcon=null;this.editMode=false;this.shouldEnterEditMode=false;this.selectedIcon=null;this.rename.addEventListener('click',e=>{e.preventDefault();this.renameSelectedIcon();});this.remove.addEventListener('click',e=>{e.preventDefault();this.removeSelectedIcon();});this.done.addEventListener('click',e=>{e.preventDefault();this.exitEditMode();});this._iconSize=0;this.lastWindowWidth=window.innerWidth;this.lastWindowHeight=window.innerHeight;this.icons.addEventListener('activate',this);this.icons.addEventListener('drag-start',this);this.icons.addEventListener('drag-move',this);this.icons.addEventListener('drag-end',this);this.icons.addEventListener('drag-rearrange',this);this.icons.addEventListener('drag-finish',this);navigator.mozApps.mgmt.addEventListener('install',this);navigator.mozApps.mgmt.addEventListener('uninstall',this);window.addEventListener('localized',this);window.addEventListener('online',this);window.addEventListener('resize',this);window.addEventListener('settings-changed',this);this.settings=new Settings();this.icons.classList.toggle('small',this.settings.small);this.scrollable.classList.toggle('snapping',this.settings.scrollSnapping);this.metadataLoaded=0;this.startupMetadata=[];this.iconsToRetry=[];this.pendingIcons={};this.metadata=new AppsMetadata();this.bookmarks=new Datastore('bookmarks_store');this.visualLoadComplete=false;this.icons.freeze();this.icons.classList.add('loading');Promise.all([this.metadata.init().then(this.settings.firstRun?()=>{return LazyLoader.load('js/firstrun.js').then(()=>{return FirstRun().then((results)=>{this.toggleSmall(results.small);this.startupMetadata=results.order;this.settings.small=results.small;this.settings.save();return Promise.resolve();},(e)=>{console.error('Error running first-run script',e);return Promise.resolve();});},(e)=>{console.error('Failed to load first-run script');return Promise.resolve();});}:()=>{return this.metadata.getAll(result=>{this.startupMetadata.push(result);this.metadataLoaded++;var processResult=data=>{if(this.pendingIcons[data.id]){this.addAppIcon.apply(this,this.pendingIcons[data.id]);delete this.pendingIcons[data.id];}};if(!this.visualLoadComplete){processResult(result);this.refreshGridSize();}else if((this.metadataLoaded%this.iconsPerPage)===0){this.icons.freeze();for(var entry of this.startupMetadata){processResult(entry);}
this.refreshGridSize();this.icons.thaw();}}).then(Promise.resolve(),e=>{console.error('Failed to retrieve metadata entries',e);return Promise.resolve();});},(e)=>{console.error('Failed to initialise metadata db',e);return Promise.resolve();}),new Promise((resolve,reject)=>{var request=navigator.mozApps.mgmt.getAll();request.onsuccess=(e)=>{for(var app of request.result){this.addApp(app);}
resolve();};request.onerror=(e)=>{console.error('Error calling getAll: '+request.error.name);resolve();};}),this.bookmarks.init().then(()=>{document.addEventListener('bookmarks_store-set',(e)=>{var id=e.detail.id;this.bookmarks.get(id).then((bookmark)=>{for(var child of this.icons.children){var icon=child.firstElementChild;if(icon.bookmark&&icon.bookmark.id===id){icon.bookmark=bookmark.data;icon.refresh();return;}}
this.addAppIcon(bookmark.data);this.storeAppOrder();});});document.addEventListener('bookmarks_store-removed',(e)=>{var id=e.detail.id;for(var child of this.icons.children){var icon=child.firstElementChild;if(icon.bookmark&&icon.bookmark.id===id){this.icons.removeChild(child,()=>{this.storeAppOrder();this.refreshGridSize();this.snapScrollPosition();});this.metadata.remove(id);if(this.selectedIcon===icon){this.updateSelectedIcon(null);}
return;}}});document.addEventListener('bookmarks_store-cleared',()=>{for(var child of this.icons.children){var icon=child.firstElementChild;if(icon.bookmark){this.icons.removeChild(child);}}
this.storeAppOrder();this.refreshGridSize();this.snapScrollPosition();});},(e)=>{console.error('Error initialising bookmarks',e);return Promise.resolve();}).then(()=>{return this.bookmarks.getAll().then((bookmarks)=>{for(var bookmark of bookmarks){this.addAppIcon(bookmark.data);}},(e)=>{console.error('Error getting bookmarks',e);return Promise.resolve();});})]).then(()=>{var id;var pendingIcons=this.pendingIcons;this.pendingIcons={};for(id in pendingIcons){this.addAppIcon.apply(this,pendingIcons[id]);}
if(!this.visualLoadComplete){this.onVisualLoad();}
if(!this.settings.firstRun){for(var data of this.startupMetadata){console.log('Removing unknown app metadata entry',data.id);this.metadata.remove(data.id).then(()=>{},(e)=>{console.error('Error removing unknown app metadata entry',e);});}}
this.startupMetadata=null;for(var child of this.icons.children){var icon=child.firstElementChild;this.refreshIcon(icon);}
var newIcons=false;pendingIcons=this.pendingIcons;for(id in pendingIcons){this.addAppIcon.apply(this,pendingIcons[id]);newIcons=true;}
this.pendingIcons=null;if(newIcons){this.storeAppOrder();}else{this.refreshGridSize();}
window.performance.mark('fullyLoaded');});}
Apps.prototype={get iconSize(){if(!this._iconSize){var children=this.icons.children;for(var container of children){if(container.style.display!=='none'){this._iconSize=container.firstElementChild.size;break;}}}
return this._iconSize;},toggleSmall:function(small){if(this.icons.classList.contains('small')===small){return;}
this.icons.classList.toggle('small',small);this.icons.synchronise();this.refreshGridSize();this.snapScrollPosition();},toggleScrollSnapping:function(scrollSnapping){if(this.scrollable.classList.contains('snapping')===scrollSnapping){return;}
this.scrollable.classList.toggle('snapping',scrollSnapping);this.snapScrollPosition();},onVisualLoad:function(){this.visualLoadComplete=true;this.icons.thaw();this.icons.classList.remove('loading');window.performance.mark('visuallyLoaded');window.performance.mark('contentInteractive');},addApp:function(app){var manifest=app.manifest||app.updateManifest;if(!manifest){return;}
if(BLACKLIST.includes(app.origin)){return;}
if(manifest.entry_points){for(var entryPoint in manifest.entry_points){this.addAppIcon(app,entryPoint);}}else{this.addAppIcon(app);}},addIconContainer:function(icon,entry){var container=document.createElement('div');container.classList.add('icon-container');container.order=-1;container.appendChild(icon);if(entry!==-1&&this.startupMetadata[entry].order>=0){container.order=this.startupMetadata[entry].order;var children=this.icons.children;for(var i=0,iLen=children.length;i<iLen;i++){var child=children[i];if(child.order!==-1&&child.order<container.order){continue;}
this.icons.insertBefore(container,child);if(this.startupMetadata===null){this.iconAdded(container);}
break;}}
if(!container.parentNode){this.icons.appendChild(container);if(this.startupMetadata===null){this.iconAdded(container);}}
return container;},getIconId:function(appOrBookmark,entryPoint){if(appOrBookmark.id){return appOrBookmark.id;}else{return appOrBookmark.manifestURL+'/'+(entryPoint?entryPoint:'');}},addAppIcon:function(appOrBookmark,entryPoint){var id=this.getIconId(appOrBookmark,entryPoint);var entry=-1;if(this.startupMetadata!==null){entry=this.startupMetadata.findIndex(data=>{return data.id===id;});if(entry===-1){this.pendingIcons[id]=Array.slice(arguments);return;}}
var icon=document.createElement('gaia-app-icon');if(entryPoint){icon.entryPoint=entryPoint;}
var container=this.addIconContainer(icon,entry);if(appOrBookmark.id){icon.bookmark=appOrBookmark;this.refreshIcon(icon);}else{icon.app=appOrBookmark;var handleRoleChange=function(app,container){var manifest=app.manifest||app.updateManifest;var hidden=(manifest&&manifest.role&&HIDDEN_ROLES.includes(manifest.role));container.style.display=hidden?'none':'';};icon.app.addEventListener('downloadapplied',function(app,container){handleRoleChange(app,container);this.icons.synchronise();}.bind(this,icon.app,container));handleRoleChange(icon.app,container);}
this.iconsToRetry.push(id);icon.addEventListener('icon-loaded',function(icon,id){if(icon.isUserSet){return;}
icon.icon.then((blob)=>{var retryIndex=this.iconsToRetry.indexOf(id);if(retryIndex!==-1){this.iconsToRetry.splice(retryIndex,1);}
this.metadata.set([{id:id,icon:blob}]).then(()=>{},(e)=>{console.error('Error saving icon',e);});});}.bind(this,icon,id));icon.size=this.iconSize?this.iconSize:icon.size;if(entry!==-1){icon.icon=this.startupMetadata[entry].icon;this.startupMetadata.splice(entry,1);icon.updateName();if(window.VulpesCompat&&icon.app)icon.refresh();}else{icon.refresh();}
icon.addEventListener('activated',e=>{e.preventDefault();this.handleEvent({type:'activate',detail:{target:e.target.parentNode},preventDefault:()=>{}});});},refreshIcon:function(icon){icon.size=this.iconSize?this.iconSize:icon.size;if(icon.bookmark){IconsHelper.setElementIcon(icon,this.iconSize).then(()=>{},e=>{console.error('Error refreshing bookmark icon',e);if(!icon.isUserSet){icon.refresh();}});}else{icon.refresh();}},storeAppOrder:function(){var storedOrders=[];var children=this.icons.children;for(var i=0,iLen=children.length;i<iLen;i++){var appIcon=children[i].firstElementChild;var id=this.getIconId(appIcon.app?appIcon.app:appIcon.bookmark,appIcon.entryPoint);storedOrders.push({id:id,order:i});}
this.metadata.set(storedOrders).then(()=>{},(e)=>{console.error('Error storing app order',e);});},iconAdded:function(container){if(container.style.display==='none'){return;}
this.refreshGridSize();},refreshGridSize:function(){var children=this.icons.children;var cols=this.settings.small?4:3;var visibleChildren=0;var firstVisibleChild=-1;for(var i=0,iLen=children.length;i<iLen;i++){if(children[i].style.display!=='none'){visibleChildren++;if(firstVisibleChild===-1){firstVisibleChild=i;this.iconsLeft=this.icons.getChildOffsetRect(children[i]).left;}else if(visibleChildren===cols){this.iconsRight=this.icons.getChildOffsetRect(children[i]).right;}}}
if(visibleChildren<1){this.pendingGridHeight=this.gridHeight=0;this.pageHeight=this.scrollable.clientHeight;}else{var iconHeight=Math.round(children[firstVisibleChild].offsetHeight);var scrollHeight=this.scrollable.clientHeight;var rowsPerPage=Math.floor(scrollHeight/iconHeight);var pageHeight=rowsPerPage*iconHeight;var gridHeight;if(this.settings.scrollSnapping){gridHeight=(Math.ceil((iconHeight*Math.ceil(visibleChildren/cols))/pageHeight)*pageHeight)+(scrollHeight-pageHeight);}else{gridHeight=(Math.ceil(visibleChildren/cols)+1)*iconHeight;}
this.pageHeight=pageHeight;this.pendingGridHeight=gridHeight;this.iconsPerPage=rowsPerPage*cols;if(!this.visualLoadComplete&&Math.floor(visibleChildren/cols)*iconHeight>=scrollHeight){this.onVisualLoad();}}
this.scrollable.style.scrollSnapPointsY=`repeat(${this.pageHeight}px)`;this.icons.style.backgroundSize='100% '+(this.pageHeight*2)+'px';if(this.resizeTimeout!==null){clearTimeout(this.resizeTimeout);}
var setGridHeight=()=>{this.resizeTimeout=null;this.icons.style.height=gridHeight+'px';this.gridHeight=this.pendingGridHeight;};if(this.pendingGridHeight>this.gridHeight){setGridHeight();}else if(this.pendingGridHeight!==this.gridHeight){this.resizeTimeout=setTimeout(setGridHeight,RESIZE_TIMEOUT);}},snapScrollPosition:function(bias){bias=bias||0;var gridHeight=this.pendingGridHeight;var currentScroll=this.scrollable.scrollTop;var scrollHeight=this.scrollable.clientHeight;var destination;if(this.settings.scrollSnapping){destination=Math.min(gridHeight-scrollHeight,Math.round(currentScroll/this.pageHeight+bias)*this.pageHeight);}else{destination=Math.min(gridHeight-scrollHeight,currentScroll+(this.pageHeight*bias));}
if(Math.abs(destination-currentScroll)>1){this.scrollable.style.overflow='';this.scrollable.scrollTo({left:0,top:destination,behavior:'smooth'});if(this.autoScrollOverflowTimeout!==null){clearTimeout(this.autoScrollOverflowTimeout);this.autoScrollOverflowTimeout=null;}
if(this.dragging){document.body.classList.add('autoscroll');this.autoScrollOverflowTimeout=setTimeout(()=>{this.autoScrollOverflowTimeout=null;this.scrollable.style.overflow='hidden';document.body.classList.remove('autoscroll');this.scrollable.scrollTop=destination;},AUTOSCROLL_OVERFLOW_DELAY);}}},showActionDialog:function(dialog,args,callbacks){if(dialog.style.display!=='none'){return;}
function executeCallback(dialog,callback){callback();dialog.close();}
var actions=dialog.getElementsByClassName('action');for(var i=0,iLen=Math.min(actions.length,callbacks.length);i<iLen;i++){actions[i].onclick=executeCallback.bind(this,dialog,callbacks[i]);}
if(args){dialog.querySelector('.body').setAttribute('data-l10n-args',args);}
setTimeout(()=>{dialog.open();},DIALOG_SHOW_TIMEOUT);},getChildIndex:function(child){return this.icons.children.indexOf(child);},removeSelectedIcon:function(){if(!this.selectedIcon){return;}
if(this.selectedIcon.app){if(!this.selectedIcon.app.removable){return;}
navigator.mozApps.mgmt.uninstall(this.selectedIcon.app);}else if(this.selectedIcon.bookmark){var remove=new MozActivity({name:'remove-bookmark',data:{type:'url',url:this.selectedIcon.bookmark.id}});var icon=this.selectedIcon;remove.onsuccess=()=>{this.enterEditMode(null);};remove.onerror=()=>{this.enterEditMode(icon);};}},renameSelectedIcon:function(){if(!this.selectedIcon||!this.selectedIcon.bookmark){return;}
var rename=new MozActivity({name:'save-bookmark',data:{type:'url',url:this.selectedIcon.bookmark.id}});var icon=this.selectedIcon;rename.onsuccess=rename.onerror=()=>{this.enterEditMode(icon);};},iconIsEditable:function(icon){return(icon.bookmark||(icon.app&&icon.app.removable))?true:false;},updateSelectedIcon:function(icon){if(this.selectedIcon===icon){return;}
if(this.selectedIcon&&(!icon||this.iconIsEditable(icon))){this.selectedIcon.classList.remove('selected');this.selectedIcon.removeEventListener('touchstart',this);this.selectedIcon=null;}
var selectedRenameable=false;var selectedRemovable=false;if(icon){selectedRenameable=!!icon.bookmark;selectedRemovable=selectedRenameable||icon.app.removable;if(selectedRenameable||selectedRemovable){this.selectedIcon=icon;icon.classList.add('selected');icon.addEventListener('touchstart',this);this.rename.classList.toggle('active',selectedRenameable);this.remove.classList.toggle('active',selectedRemovable);}else if(!icon.classList.contains('uneditable')){icon.classList.add('uneditable');icon.addEventListener('animationend',function animEnd(){icon.removeEventListener('animationend',animEnd);icon.classList.remove('uneditable');});}}else{this.rename.classList.remove('active');this.remove.classList.remove('active');}},enterEditMode:function(icon){console.debug('Entering edit mode on '+(icon?icon.name:'no icon'));this.updateSelectedIcon(icon);if(this.editMode){return;}
this.editMode=true;document.body.classList.add('edit-mode');},exitEditMode:function(){if(!this.editMode){return;}
console.debug('Exiting edit mode');this.editMode=false;document.body.classList.remove('edit-mode');this.rename.classList.remove('active');this.remove.classList.remove('active');this.updateSelectedIcon(null);},handleEvent:function(e){var icon,child,id;switch(e.type){case'activate':e.preventDefault();icon=e.detail.target.firstElementChild;if(this.editMode){this.enterEditMode(icon);return;}
switch(icon.state){case'unrecoverable':navigator.mozApps.mgmt.uninstall(icon.app);break;case'installing':this.showActionDialog(this.cancelDownload,JSON.stringify({name:icon.name}),[()=>{icon.app.cancelDownload();}]);break;case'error':case'paused':this.showActionDialog(this.resumeDownload,JSON.stringify({name:icon.name}),[()=>{icon.app.download();}]);break;default:icon.launch();break;}
break;case'touchstart':this.icons.dragAndDropTimeout=0;break;case'drag-start':console.debug('Drag-start on '+
e.detail.target.firstElementChild.name);this.dragging=true;this.shouldEnterEditMode=true;document.body.classList.add('dragging');this.scrollable.style.overflow='hidden';this.draggedIndex=this.getChildIndex(e.detail.target);break;case'drag-finish':console.debug('Drag-finish');this.dragging=false;document.body.classList.remove('dragging');document.body.classList.remove('autoscroll');this.scrollable.style.overflow='';if(this.autoScrollInterval!==null){clearInterval(this.autoScrollInterval);this.autoScrollInterval=null;}
if(this.autoScrollOverflowTimeout!==null){clearTimeout(this.autoScrollOverflowTimeout);this.autoScrollOverflowTimeout=null;}
if(this.hoverIcon){this.hoverIcon.classList.remove('hover-before','hover-after');this.hoverIcon=null;}
this.icons.dragAndDropTimeout=-1;break;case'drag-end':console.debug('Drag-end, target: '+(e.detail.dropTarget?e.detail.dropTarget.firstElementChild.name:'none'));if(e.detail.dropTarget===null&&e.detail.clientX>=this.iconsLeft&&e.detail.clientX<this.iconsRight){e.preventDefault();var bottom=e.detail.clientY<this.lastWindowHeight/2;console.debug('Reordering dragged icon to '+
(bottom?'bottom':'top'));this.icons.reorderChild(e.detail.target,bottom?this.icons.firstChild:null,this.storeAppOrder.bind(this));break;}
if(e.detail.dropTarget===e.detail.target){icon=e.detail.target.firstElementChild;if(this.editMode||this.shouldEnterEditMode){e.preventDefault();this.enterEditMode(icon);}}
break;case'drag-rearrange':console.debug('Drag rearrange');this.storeAppOrder();break;case'drag-move':var inAutoscroll=false;if(e.detail.clientY>this.lastWindowHeight-AUTOSCROLL_DISTANCE){inAutoscroll=true;if(this.autoScrollInterval===null){this.autoScrollInterval=setInterval(()=>{this.shouldEnterEditMode=false;this.snapScrollPosition(1);return true;},AUTOSCROLL_DELAY);}}else if(e.detail.clientY<AUTOSCROLL_DISTANCE){inAutoscroll=true;if(this.autoScrollInterval===null){this.autoScrollInterval=setInterval(()=>{this.shouldEnterEditMode=false;this.snapScrollPosition(-1);return true;},AUTOSCROLL_DELAY);}}else{var hoverIcon=this.icons.getChildFromPoint(e.detail.clientX,e.detail.clientY);if(this.hoverIcon!==hoverIcon){if(this.hoverIcon){this.shouldEnterEditMode=false;this.hoverIcon.classList.remove('hover-before','hover-after');}
this.hoverIcon=(hoverIcon!==e.detail.target)?hoverIcon:null;if(this.hoverIcon){var offset=this.draggedIndex-
this.getChildIndex(this.hoverIcon);this.hoverIcon.classList.add((offset>=0)?'hover-before':'hover-after');}}}
if(!inAutoscroll&&this.autoScrollInterval!==null){clearInterval(this.autoScrollInterval);this.autoScrollInterval=null;}
break;case'install':var existing=false;for(child of this.icons.children){icon=child.firstElementChild;if(icon.app&&icon.app.manifestURL===e.application.manifestURL){icon.app=e.application;icon.refresh();existing=true;}}
if(existing){return;}
this.addApp(e.application);this.storeAppOrder();break;case'uninstall':var callback=()=>{this.storeAppOrder();this.refreshGridSize();this.snapScrollPosition();};for(child of this.icons.children){icon=child.firstElementChild;if(icon.app&&icon.app.manifestURL===e.application.manifestURL){id=this.getIconId(e.application,icon.entryPoint);this.metadata.remove(id).then(()=>{},(e)=>{console.error('Error removing uninstalled app',e);});this.icons.removeChild(child,callback);callback=null;if(this.selectedIcon===icon){this.updateSelectedIcon(null);}}}
break;case'localized':for(icon of this.icons.children){icon.firstElementChild.updateName();}
this.icons.synchronise();break;case'online':for(var i=0,iLen=this.iconsToRetry.length;i<iLen;i++){for(child of this.icons.children){icon=child.firstElementChild;id=this.getIconId(icon.app?icon.app:icon.bookmark,icon.entryPoint);if(id===this.iconsToRetry[i]){this.refreshIcon(icon);break;}}}
break;case'resize':if(this.lastWindowWidth===window.innerWidth&&this.lastWindowHeight===window.innerHeight){break;}
this.lastWindowWidth=window.innerWidth;this.lastWindowHeight=window.innerHeight;var oldIconSize=this.iconSize;this._iconSize=0;if(oldIconSize!==this.iconSize){for(child of this.icons.children){icon=child.firstElementChild;this.refreshIcon(icon);}}
this.icons.synchronise();this.refreshGridSize();this.snapScrollPosition();break;case'settings-changed':this.toggleSmall(this.settings.small);this.toggleScrollSnapping(this.settings.scrollSnapping);break;}}};exports.Apps=Apps;}(window));