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.
This commit is contained in:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
|
|
@ -0,0 +1,41 @@
|
|||
|
||||
define('modules/apn/apn_const',['require'],function(require){'use strict';var APN_TYPES=['default','mms','supl','dun','ims','fota'];var CP_APN_KEY='ril.data.cp.apns';var DEFAULT_APN_KEY='ril.data.default.apns';var CACHED_ICCIDS_KEY='apn.cached.iccids';var APN_LIST_KEY='apn.list';var APN_SELECTIONS_KEY='apn.selections';var APN_SETTINGS_KEY='ril.data.apnSettings';var DEFAULT_APN_SETTINGS_KEY='ril.data.default.apnSettings';var MCC_SETTINGS_KEY='operatorvariant.mcc';var MNC_SETTINGS_KEY='operatorvariant.mnc';var EU_ROAMING_ENABLED_KEY='eu-roaming.enabled';var EU_ROAMING_FILE_PATH='/resources/eu-roaming.json';var APN_PROPS=['carrier','apn','user','passwd','httpproxyhost','httpproxyport','mmsc','mmsproxy','mmsport','authtype','types','protocol','roaming_protocol','mtu','_id'];return{get APN_TYPES(){return APN_TYPES;},get CP_APN_KEY(){return CP_APN_KEY;},get DEFAULT_APN_KEY(){return DEFAULT_APN_KEY;},get CACHED_ICCIDS_KEY(){return CACHED_ICCIDS_KEY;},get APN_LIST_KEY(){return APN_LIST_KEY;},get APN_SELECTIONS_KEY(){return APN_SELECTIONS_KEY;},get APN_SETTINGS_KEY(){return APN_SETTINGS_KEY;},get DEFAULT_APN_SETTINGS_KEY(){return DEFAULT_APN_SETTINGS_KEY;},get MCC_SETTINGS_KEY(){return MCC_SETTINGS_KEY;},get MNC_SETTINGS_KEY(){return MNC_SETTINGS_KEY;},get EU_ROAMING_ENABLED_KEY(){return EU_ROAMING_ENABLED_KEY;},get EU_ROAMING_FILE_PATH(){return EU_ROAMING_FILE_PATH;},get APN_PROPS(){return APN_PROPS;}};});define('modules/apn/apn_item',['require'],function(require){'use strict';var APN_CATEGORY={PRESET:'preset',CUSTOM:'custom',EU:'eu'};function ApnItem(id,category,apn){this._id=id;this._category=category;this._apn=apn;}
|
||||
ApnItem.prototype={get id(){return this._id;},get category(){return this._category;},get apn(){return this._apn;}};var constructor=function ctor_apn_item(id,category,apn){return new ApnItem(id,category,apn);};constructor.APN_CATEGORY=APN_CATEGORY;return constructor;});define('modules/apn/apn_utils',['require','modules/settings_cache','shared/apn_helper','modules/apn/apn_const','modules/apn/apn_item','shared/lazy_loader'],function(require){'use strict';var SettingsCache=require('modules/settings_cache');var ApnHelper=require('shared/apn_helper');var ApnConst=require('modules/apn/apn_const');var ApnItem=require('modules/apn/apn_item');var LazyLoader=require('shared/lazy_loader');var CP_APN_KEY=ApnConst.CP_APN_KEY;var DEFAULT_APN_KEY=ApnConst.DEFAULT_APN_KEY;var MCC_SETTINGS_KEY=ApnConst.MCC_SETTINGS_KEY;var MNC_SETTINGS_KEY=ApnConst.MNC_SETTINGS_KEY;var APN_PROPS=ApnConst.APN_PROPS;var EU_ROAMING_FILE_PATH=ApnConst.EU_ROAMING_FILE_PATH;function _getOperatorCode(serviceId,type){var value;var key;if(type==='mcc'){value='000';key=MCC_SETTINGS_KEY;}else if(type==='mnc'){value='00';key=MNC_SETTINGS_KEY;}else{return Promise.reject('invalid type');}
|
||||
return new Promise(function(resolve,reject){SettingsCache.getSettings(function(results){var values=results[key];if(values&&Array.isArray(values)&&values[serviceId]){value=values[serviceId];}
|
||||
resolve(value);});});}
|
||||
function _apnTypeFilter(type,apnItem){if(!type||!apnItem||!apnItem.apn.types){return false;}else if(type==='*'){return true;}
|
||||
return apnItem.apn.types.indexOf(type)!=-1;}
|
||||
function _getDefaultApns(mcc,mnc,networkType){return new Promise(function(resolve,reject){SettingsCache.getSettings(function(results){var apns=results[DEFAULT_APN_KEY]||{};resolve(ApnHelper.getCompatible(apns,mcc,mnc,networkType));});});}
|
||||
function _getCpApns(mcc,mnc,networkType){return new Promise(function(resolve,reject){SettingsCache.getSettings(function(results){var apns=results[CP_APN_KEY]||{};resolve(ApnHelper.getCompatible(apns,mcc,mnc,networkType));});});}
|
||||
var _euApnChecked=false;var _euApns=null;function _getEuApns(){if(_euApnChecked){return Promise.resolve(_euApns);}else{return LazyLoader.getJSON(EU_ROAMING_FILE_PATH).then(function(result){_euApnChecked=true;if(result.home&&result.foreign&&Object.keys(result.home).length>0&&Object.keys(result.foreign).length>0){_euApns=result.defaultApns;}
|
||||
return _euApns;}).catch(function(){_euApnChecked=true;return null;});}}
|
||||
function _generateId(){return Math.random().toString(36).substr(2,9);}
|
||||
function _cloneApn(apn){var newApn={};for(var p in apn){newApn[p]=apn[p];}
|
||||
return newApn;}
|
||||
function _separateApnsByType(apns){if(!apns){return[];}
|
||||
return apns.reduce(function(result,apn){apn.types.forEach(function(type){var cloneApn=_cloneApn(apn);cloneApn.types=[type];result.push(cloneApn);});return result;},[]);}
|
||||
function _isMatchedApn(apn1,apn2){if(apn1==null||apn2==null){return false;}
|
||||
if(!apn1.types.every(function(type){return(apn2.types.indexOf(type)!==-1);})){return false;}
|
||||
return APN_PROPS.every(function(prop){if(prop==='types'){return true;}else{return apn1[prop]===apn2[prop];}});}
|
||||
function _sortByCategory(apn1,apn2){if(apn1.category===ApnItem.APN_CATEGORY.PRESET){return true;}else if(apn2.category===ApnItem.APN_CATEGORY.PRESET){return false;}else{return true;}}
|
||||
function _clone(apn){return JSON.parse(JSON.stringify(apn));}
|
||||
return{getOperatorCode:_getOperatorCode,apnTypeFilter:_apnTypeFilter,getDefaultApns:_getDefaultApns,getCpApns:_getCpApns,getEuApns:_getEuApns,generateId:_generateId,separateApnsByType:_separateApnsByType,isMatchedApn:_isMatchedApn,sortByCategory:_sortByCategory,clone:_clone};});define('modules/apn/apn_settings',['require','modules/settings_cache','modules/apn/apn_utils','modules/apn/apn_const'],function(require){'use strict';var SettingsCache=require('modules/settings_cache');var ApnUtils=require('modules/apn/apn_utils');var ApnConst=require('modules/apn/apn_const');var APN_SETTINGS_KEY=ApnConst.APN_SETTINGS_KEY;var DEFAULT_APN_SETTINGS_KEY=ApnConst.DEFAULT_APN_SETTINGS_KEY;function ApnSettings(){this._isReady=false;this._readyPromise=null;this._isWritingDB=false;this._isDirty=false;this._apnSettings=null;this._promiseChain=Promise.resolve();}
|
||||
ApnSettings.prototype={_schedule:function al__schedule(task){var that=this;this._promiseChain=this._promiseChain.then(function(){return task().then(function(){return that._commit();});});return this._promiseChain;},_commit:function al__commit(){if(!this._isDirty){return Promise.resolve();}
|
||||
var that=this;return new Promise(function(resolve){that._isWritingDB=true;var obj={};obj[APN_SETTINGS_KEY]=that._apnSettings;var req=navigator.mozSettings.createLock().set(obj);req.onsuccess=req.onerror=function(){that._isDirty=false;that._isWritingDB=false;resolve();};});},_addObservers:function as__registerListeners(){var mozSettings=window.navigator.mozSettings;if(!mozSettings){return;}
|
||||
mozSettings.addObserver(APN_SETTINGS_KEY,function(event){if(!this._isWritingDB){this._apnSettings=event.settingValue||[];}}.bind(this));},_ready:function as__ready(){if(this._isReady){return Promise.resolve();}else{if(!this._readyPromise){var that=this;this._readyPromise=new Promise(function(resolve){SettingsCache.getSettings(function(results){that._isReady=true;that._apnSettings=results[APN_SETTINGS_KEY]||[];that._addObservers();resolve();});});}
|
||||
return this._readyPromise;}},_update:function as__update(serviceId,apnType,newApn){return this._ready().then(function(){var apns=this._apnSettings[serviceId];if(!apns){apns=this._apnSettings[serviceId]=[];}
|
||||
var index=apns.findIndex(function(apn){return apn.types.some((type)=>apnType===type);});if(index===-1){if(newApn){this._isDirty=true;apns.push(newApn);}}else{if(newApn){if(!ApnUtils.isMatchedApn(apns[index],newApn)){this._isDirty=true;apns[index]=newApn;}}else{this._isDirty=true;apns.splice(index,1);}}}.bind(this));},getAll:function as_getAll(serviceId){return this._ready().then(function(){return this._apnSettings[serviceId];}.bind(this));},get:function as_get(serviceId,apnType){return this._ready().then(function(){var apns=this._apnSettings[serviceId];if(apns){return apns.find((apn)=>apn.types.indexOf(apnType)>=0);}else{return null;}}.bind(this));},update:function as_update(serviceId,apnType,apn){var apnClone=ApnUtils.clone(apn);return this._schedule(this._update.bind(this,serviceId,apnType,apnClone));},restore:function as_restore(serviceId){var that=this;return this._ready().then(function(){return new Promise(function(resolve){SettingsCache.getSettings(function(results){resolve(results[DEFAULT_APN_SETTINGS_KEY]||[]);});});}).then(function(defaultApnSettings){return that._schedule(function(){that._isDirty=true;that._apnSettings[serviceId]=defaultApnSettings[serviceId];return Promise.resolve();});});}};return function ctor_apn_settings(){return new ApnSettings();};});define('modules/apn/apn_list',['require','modules/async_storage','modules/apn/apn_utils','modules/apn/apn_item'],function(require){'use strict';var AsyncStorage=require('modules/async_storage');var ApnUtils=require('modules/apn/apn_utils');var ApnItem=require('modules/apn/apn_item');function ApnList(key){this._key=key;this._apnItems=null;this._pendingTaskCount=0;this._promiseChain=Promise.resolve();}
|
||||
ApnList.prototype={_schedule:function al__schedule(task){var that=this;this._promiseChain=this._promiseChain.then(function(){that._pendingTaskCount++;return task().then(function(result){that._pendingTaskCount--;if(that._pendingTaskCount===0){return that._commit().then(function(){return result;});}else{return result;}});});return this._promiseChain;},_export:function al__export(){return this._apnItems.map(function(apnItem){var apnClone=ApnUtils.clone(apnItem.apn);apnClone.id=apnItem.id;apnClone.category=apnItem.category;return apnClone;});},_commit:function al__commit(){if(!this._apnItems){return Promise.resolve();}else{var apns=this._export();return AsyncStorage.setItem(this._key,apns);}},_add:function al__add(apn,category){return this.items().then(function(apnItems){if(!apnItems){this._apnItems=apnItems=[];}
|
||||
category=category||ApnItem.APN_CATEGORY.CUSTOM;if(category===ApnItem.APN_CATEGORY.CUSTOM){apn.carrier='_custom_';}
|
||||
var apnItem=ApnItem(ApnUtils.generateId(),category,apn);apnItems.unshift(apnItem);return apnItem.id;}.bind(this));},_remove:function al__remove(id){return this.items().then(function(apnItems){if(!apnItems){return Promise.reject('no apn items');}
|
||||
var index=apnItems.findIndex((apnItem)=>apnItem.id===id);if(index>=0){apnItems=apnItems.splice(index,1);}else{return Promise.reject('apn not found');}}.bind(this));},_update:function al__update(id,apn){return this.items().then(function(apnItems){if(!apnItems){return Promise.reject('no apn items');}
|
||||
var index=apnItems.findIndex((apnItem)=>apnItem.id===id);if(index>=0){var currentApn=apnItems[index].apn;for(var p in apn){if(p==='id'||p==='category'){continue;}
|
||||
currentApn[p]=apn[p];}}else{return Promise.reject('apn not found');}}.bind(this));},items:function al_items(){if(this._apnItems){return Promise.resolve(this._apnItems);}else{return AsyncStorage.getItem(this._key).then(function(apns){if(apns){this._apnItems=apns.map(function(apn){return ApnItem(apn.id,apn.category,apn);});return this._apnItems;}else{return null;}}.bind(this));}},item:function al_item(id){return this.items().then(function(apnItems){if(!apnItems||!id){return null;}else{return apnItems.find((apnItem)=>apnItem.id===id);}});},add:function al_apns(apn,category){var apnClone=ApnUtils.clone(apn);return this._schedule(this._add.bind(this,apnClone,category));},remove:function al_remove(id){return this._schedule(this._remove.bind(this,id));},update:function al_update(id,apn){var apnClone=ApnUtils.clone(apn);return this._schedule(this._update.bind(this,id,apnClone));}};return function ctor_apn_list(key){return new ApnList(key);};});define('modules/apn/apn_selections',['require','modules/mvvm/observable','modules/settings_cache','modules/apn/apn_const'],function(require){'use strict';var Observable=require('modules/mvvm/observable');var SettingsCache=require('modules/settings_cache');var ApnConst=require('modules/apn/apn_const');var ICC_COUNT=navigator.mozMobileConnections.length;var APN_TYPES=ApnConst.APN_TYPES;var APN_SELECTIONS_KEY=ApnConst.APN_SELECTIONS_KEY;function ApnSelections(){this._readyPromise=null;this._apnSelections=[];}
|
||||
ApnSelections.prototype={_export:function as__export(){return this._apnSelections.map(function(apnSelection){var obj={};APN_TYPES.forEach(function(apnType){obj[apnType]=apnSelection[apnType];});return obj;});},_commit:function as__commit(){return new Promise(function(resolve){var obj={};obj[APN_SELECTIONS_KEY]=this._export();var req=navigator.mozSettings.createLock().set(obj);req.onsuccess=function(){resolve();};req.onerror=function(){console.error('write apn selections failed');resolve();};}.bind(this));},_observeApnSelections:function as__observeApnSelections(apnSelection){APN_TYPES.forEach(function(apnType){apnSelection.observe(apnType,this._commit.bind(this));},this);},_createEmptySelections:function as__createEmptySelections(){var emptySelections=[];var emptySelection={};APN_TYPES.forEach((apnType)=>emptySelection[apnType]=null);for(var i=0;i<ICC_COUNT;i++){emptySelections.push(emptySelection);}
|
||||
return new Promise(function(resolve,reject){var obj={};obj[APN_SELECTIONS_KEY]=emptySelections;var req=navigator.mozSettings.createLock().set(obj);req.onsuccess=req.onerror=function(){resolve(emptySelections);};}.bind(this));},_ready:function as__ready(){if(!this._readyPromise){var that=this;this._readyPromise=new Promise(function(resolve){SettingsCache.getSettings(function(results){resolve(results[APN_SELECTIONS_KEY]);});}).then(function(apnSelections){if(apnSelections){return apnSelections;}else{return that._createEmptySelections();}}).then(function(apnSelections){apnSelections.forEach(function(selection,index){var observableApnSelection=Observable(selection);that._observeApnSelections(observableApnSelection);that._apnSelections[index]=observableApnSelection;});navigator.mozSettings.addObserver(APN_SELECTIONS_KEY,function(event){if(event.settingValue===null){that._readyPromise=null;}});});}
|
||||
return this._readyPromise;},get:function as_get(serviceId){return this._ready().then(function(){return this._apnSelections[serviceId];}.bind(this));},clear:function as_clear(serviceId){var apnSelection=this._apnSelections[serviceId];if(!apnSelection){return Promise.resolve();}
|
||||
return this._ready().then(function(){APN_TYPES.forEach(function(apnType){apnSelection[apnType]=null;});});}};return function(){return new ApnSelections();};});define('modules/apn/apn_settings_manager',['require','modules/async_storage','modules/apn/apn_const','modules/apn/apn_utils','modules/apn/apn_item','modules/apn/apn_settings','modules/apn/apn_list','modules/apn/apn_selections'],function(require){'use strict';var AsyncStorage=require('modules/async_storage');var ApnConst=require('modules/apn/apn_const');var ApnUtils=require('modules/apn/apn_utils');var ApnItem=require('modules/apn/apn_item');var ApnSettings=require('modules/apn/apn_settings');var ApnList=require('modules/apn/apn_list');var ApnSelections=require('modules/apn/apn_selections');var APN_TYPES=ApnConst.APN_TYPES;var APN_LIST_KEY=ApnConst.APN_LIST_KEY;var CACHED_ICCIDS_KEY=ApnConst.CACHED_ICCIDS_KEY;var RESTORE_MODE={ALL:0,ONLY_APN_ITEMS:1,};function ApnSettingsManager(){this._apnLists={};this._apnSelections=ApnSelections();this._apnSettings=ApnSettings();this._readyPromises={};Object.defineProperty(this,'RESTORE_MODE',{configurable:false,get:function(){return RESTORE_MODE;}});}
|
||||
ApnSettingsManager.prototype={_ready:function asc_fromItems(serviceId){if(!this._readyPromises[serviceId]){this._readyPromises[serviceId]=AsyncStorage.getItem(CACHED_ICCIDS_KEY).then((cachedIccIds)=>{var cachedIccId=cachedIccIds&&cachedIccIds[serviceId];var curIccId=navigator.mozMobileConnections[serviceId].iccId;if(!curIccId||curIccId===cachedIccId){return;}
|
||||
cachedIccIds=cachedIccIds||{};cachedIccIds[serviceId]=curIccId;return Promise.all([this.restore(serviceId,RESTORE_MODE.ONLY_APN_ITEMS),AsyncStorage.setItem(CACHED_ICCIDS_KEY,cachedIccIds)]);});}
|
||||
return this._readyPromises[serviceId];},_deriveActiveApnIdFromItems:function asc_fromItems(serviceId,apnType){return this._apnItems(serviceId,apnType).then(function(apnItems){if(apnItems.length){return apnItems.sort(ApnUtils.sortByCategory)[0].id;}else{return null;}});},_deriveActiveApnIdFromSettings:function asc_deriveFromSettings(serviceId,apnType){return Promise.all([this._apnItems(serviceId,apnType),this._apnSettings.get(serviceId,apnType)]).then(function(results){var apnItems=results[0];var apnInUse=results[1];if(apnInUse){var matchedApnItem=apnItems.find(function(apnItem){return ApnUtils.isMatchedApn(apnItem.apn,apnInUse);});if(matchedApnItem){return matchedApnItem.id;}else{var category=(apnInUse.carrier==='_custom_')?ApnItem.APN_CATEGORY.CUSTOM:ApnItem.APN_CATEGORY.PRESET;return this.addApn(serviceId,apnInUse,category);}}else{return null;}}.bind(this));},_getApnAppliedType:function asc_getApnAppliedType(serviceId,apnId){return this._apnSelections.get(serviceId).then(function(apnSelection){return APN_TYPES.find((apnType)=>apnSelection[apnType]===apnId);}.bind(this));},_storeApnSettingByType:function asc_storeApnByType(serviceId,apnType){return this._apnSelections.get(serviceId).then(function(apnSelection){var apnList=this._apnList(serviceId);var apnId=apnSelection[apnType];if(apnId){return apnList.item(apnId);}}.bind(this)).then(function(apnItem){if(apnItem){return this._apnSettings.update(serviceId,apnType,apnItem.apn);}else{return this._apnSettings.update(serviceId,apnType,null);}}.bind(this));},_apnList:function asc_apnList(serviceId){var apnList=this._apnLists[serviceId];if(!apnList){this._apnLists[serviceId]=apnList=ApnList(APN_LIST_KEY+serviceId);}
|
||||
return apnList;},_apnItems:function asc_apnItems(serviceId,apnType){return this._ready(serviceId).then(()=>{return this._apnList(serviceId).items();}).then((apnItems)=>{return apnItems&&apnItems.filter(ApnUtils.apnTypeFilter.bind(null,apnType))||[];});},_restoreApnItemsOfCategory:function asc_restoreApnItems(apnList,apnsForRestoring,category){return apnList.items().then(function(apnItems){apnItems=apnItems||[];var promises=[];apnItems.filter(function(apnItem){if(apnItem.category===category){return true;}}).forEach(function(apnItem){promises.push(apnList.remove(apnItem.id));});return Promise.all(promises);}).then(function(){var promises=[];apnsForRestoring.forEach(function(apns){promises.push(apnList.add(apns,category));});return Promise.all(promises);});},restore:function asc_restore(serviceId,mode){var mobileConnection=navigator.mozMobileConnections[serviceId];var networkType=mobileConnection.data.type;var apnList=this._apnList(serviceId);var that=this;return Promise.all([ApnUtils.getOperatorCode(serviceId,'mcc'),ApnUtils.getOperatorCode(serviceId,'mnc')]).then(function(values){var mcc=values[0];var mnc=values[1];return Promise.all([ApnUtils.getEuApns(),ApnUtils.getDefaultApns(mcc,mnc,networkType),ApnUtils.getCpApns(mcc,mnc,networkType)]);}).then(function(results){var euApns=ApnUtils.separateApnsByType(results[0]);var presetApns=ApnUtils.separateApnsByType(Array.prototype.concat.apply([],results.slice(1)));return that._restoreApnItemsOfCategory(apnList,euApns,ApnItem.APN_CATEGORY.EU).then(function(){return that._restoreApnItemsOfCategory(apnList,presetApns,ApnItem.APN_CATEGORY.PRESET);});}).then(function(){return that._apnSelections.clear(serviceId);}).then(function(){if(mode!==RESTORE_MODE.ONLY_APN_ITEMS){return that._apnSettings.restore(serviceId);}});},queryApns:function asc_queryApns(serviceId,apnType){return this.getActiveApnId(serviceId,apnType).then(function(activeApnId){return this._apnItems(serviceId,apnType).then(function(items){items.forEach(function(apnItem){apnItem.active=(apnItem.id===activeApnId);});return items;});}.bind(this));},addApn:function asc_addApn(serviceId,apn,category){return this._ready(serviceId).then(()=>{return this._apnList(serviceId).add(apn,category||ApnItem.APN_CATEGORY.CUSTOM);});},removeApn:function asc_removeApn(serviceId,id){return this._ready(serviceId).then(()=>{return this._apnList(serviceId).remove(id);}).then(()=>{return this._getApnAppliedType(serviceId,id);}).then((matchedApnType)=>{if(matchedApnType){return this._deriveActiveApnIdFromItems(serviceId,matchedApnType).then((activeApnId)=>{return this.setActiveApnId(serviceId,matchedApnType,activeApnId);});}});},updateApn:function asc_updateApn(serviceId,id,apn){return this._ready(serviceId).then(()=>{return this._apnList(serviceId).update(id,apn);}).then(()=>{return this._getApnAppliedType(serviceId,id);}).then((matchedApnType)=>{if(matchedApnType){return this._storeApnSettingByType(serviceId,matchedApnType);}});},getActiveApnId:function asc_getActiveApnId(serviceId,apnType){var that=this;return this._ready(serviceId).then(function(){return that._apnSelections.get(serviceId);}).then(function(apnSelection){return apnSelection&&apnSelection[apnType];}).then(function(activeApnId){if(activeApnId){return activeApnId;}else{return that._deriveActiveApnIdFromSettings(serviceId,apnType).then(function(apnId){that.setActiveApnId(serviceId,apnType,apnId);return apnId;});}}).then(function(activeApnId){if(activeApnId){return activeApnId;}else{return that._deriveActiveApnIdFromItems(serviceId,apnType).then(function(apnId){that.setActiveApnId(serviceId,apnType,apnId);return apnId;});}});},setActiveApnId:function asc_setActiveApnId(serviceId,apnType,id){return this._ready(serviceId).then(()=>{return this._apnSelections.get(serviceId);}).then((apnSelection)=>{if(apnSelection[apnType]!==id){apnSelection[apnType]=id;return this._storeApnSettingByType(serviceId,apnType);}});}};return new ApnSettingsManager();});
|
||||
Loading…
Reference in a new issue