Files
Iris/src/js/helpers.js

822 lines
18 KiB
JavaScript
Raw Normal View History

2016-10-25 17:01:37 +13:00
export let isTouchDevice = function(){
return 'ontouchstart' in document.documentElement
}
2017-11-23 17:43:29 +13:00
/**
* Storage handler
* All localStorage tasks are handled below. This means we can detect for localStorage issues in one place
**/
var storage = (function() {
var uid = new Date;
var storage;
var result;
try {
(storage = window.localStorage).setItem(uid, uid);
result = storage.getItem(uid) == uid;
storage.removeItem(uid);
return result && storage;
} catch (exception) {}
}());
2017-11-24 09:02:42 +13:00
/**
* Get a storage value
*
* @param key = string
* @param default_value = mixed (optional, if localStorage key doesn't exist, return this)
**/
2017-11-23 17:43:29 +13:00
export let getStorage = function(key, default_value = {}){
if (storage){
2017-11-23 21:17:27 +13:00
var value = storage.getItem(key);
2017-11-23 17:43:29 +13:00
if (value){
return JSON.parse(value);
} else {
return default_value;
}
} else {
console.warn("localStorage not available. Using default value for '"+key+"'.");
2017-11-23 17:43:29 +13:00
return default_value;
}
}
2017-11-24 09:02:42 +13:00
/**
* Set a storage value
*
* @param key = string
* @param value = object
* @param replace = boolean (optional, completely replace our local value rather than merging it)
**/
export let setStorage = function(key, value, replace = false){
2017-11-23 17:43:29 +13:00
if (storage){
var stored_value = storage.getItem(key);
2017-11-24 09:02:42 +13:00
// We have nothing to merge with, or we want to completely replace previous value
if (!stored_value || replace){
var new_value = value;
// Merge new value with existing
} else {
2017-11-23 17:43:29 +13:00
var new_value = Object.assign(
{},
JSON.parse(stored_value),
value
);
}
storage.setItem(key, JSON.stringify(new_value));
2017-11-24 09:02:42 +13:00
return;
2017-11-23 17:43:29 +13:00
} else {
console.warn("localStorage not available. '"+key+"'' will not perist when you close your browser.");
2017-11-24 09:02:42 +13:00
return;
2017-11-23 17:43:29 +13:00
}
}
/**
* Image sizing
* We digest all our known image source formats into a universal small,medium,large,huge object
*
* @param images = array
* @return obj
**/
2017-10-10 13:05:28 +13:00
export let sizedImages = function(images){
2016-10-25 17:01:37 +13:00
var sizes = {
small: false,
medium: false,
large: false,
huge: false
}
if (images.length <= 0) return sizes;
for (var i = 0; i < images.length; i++){
let image = images[i]
2016-10-25 17:01:37 +13:00
2017-01-25 08:55:18 +13:00
// Mopidy image object
if (image.__model__ && image.__model__ == 'Image'){
if (image.width < 400){
sizes.small = image.url;
}else if (image.width < 800){
sizes.medium = image.url;
}else if (image.width < 1000){
sizes.large = image.url;
} else {
sizes.huge = image.url;
}
2017-01-25 08:55:18 +13:00
// Mopidy image string
} else if (typeof(image) == 'string'){
sizes.small = image
2016-11-01 16:41:04 +13:00
// spotify-styled images
} else if (image.width !== undefined){
if (image.width < 400){
sizes.small = image.url;
}else if (image.width < 800){
sizes.medium = image.url;
}else if (image.width < 1000){
sizes.large = image.url;
} else {
sizes.huge = image.url;
2016-10-25 17:01:37 +13:00
}
2016-11-07 21:21:15 +13:00
// lastfm-styled images
} else if (image.size !== undefined){
2017-10-10 13:05:28 +13:00
switch(image.size){
2016-11-07 21:21:15 +13:00
case 'mega':
case 'extralarge':
case 'large':
sizes.medium = image['#text']
break;
case 'medium':
2016-11-07 21:21:15 +13:00
case 'small':
sizes.small = image['#text']
break;
2016-11-07 21:21:15 +13:00
}
2016-11-01 16:41:04 +13:00
}
2016-10-25 17:01:37 +13:00
}
if (!sizes.small){
if (sizes.medium) sizes.small = sizes.medium
else if (sizes.large) sizes.small = sizes.large
else if (sizes.huge) sizes.small = sizes.huge
else sizes.small = null
}
2017-04-04 16:51:22 +12:00
if (!sizes.medium){
if (sizes.large) sizes.medium = sizes.large
else if (sizes.huge) sizes.medium = sizes.huge
2016-11-25 16:08:19 +13:00
else sizes.medium = sizes.small
}
2017-04-04 16:51:22 +12:00
if (!sizes.large) sizes.large = sizes.medium;
if (!sizes.huge) sizes.huge = sizes.large;
2016-11-07 21:21:15 +13:00
2016-10-25 17:01:37 +13:00
return sizes;
2016-10-27 21:20:05 +13:00
}
/**
* Digest an array of Mopidy image objects into a universal format. We also re-write
* image URLs to be absolute to the mopidy server (required for proxy setups).
*
* @param mopidy = obj (mopidy store object)
* @param images = array
* @return array
**/
export let digestMopidyImages = function(mopidy, images){
var digested = [];
for (var i = 0; i < images.length; i++){
// Image object (ie from images.get)
if (typeof images[i] === 'object'){
// Accommodate backends that provide URIs vs URLs
var url = images[i].url
if (!url && images[i].uri){
url = images[i].uri
}
// Amend our URL
images[i].url = url
// Replace local images to point directly to our Mopidy server
2017-11-14 20:57:02 +13:00
if (url && url.startsWith('/images/')){
url = '//'+mopidy.host+':'+mopidy.port+url
}
// String-based image
} else if (typeof images[i] === 'string'){
// Replace local images to point directly to our Mopidy server
if (images[i].startsWith('/images/')){
images[i] = '//'+mopidy.host+':'+mopidy.port+images[i]
}
}
digested.push(images[i])
}
return digested
}
export let generateGuid = function(type = 'numeric'){
// numeric
if (type == 'numeric'){
var date = new Date().valueOf().toString();
var random_number = Math.floor((Math.random() * 100)).toString();
return parseInt(date+random_number);
} else {
var format = 'xxxxxxxxxx';
return format.replace(/[xy]/g, function(c){
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
return v.toString(16);
});
}
2016-10-28 10:48:55 +13:00
}
2017-10-10 13:05:28 +13:00
export let getCurrentPusherConnection = function(connections, connectionid){
2016-10-28 10:48:55 +13:00
function isCurrentConnection(connection){
return connection.connectionid == newProps.pusher.connectionid;
}
var currentConnection = newProps.pusher.connections.find(isCurrentConnection);
2017-10-10 13:05:28 +13:00
if (!currentConnection ) return false;
2016-10-28 10:48:55 +13:00
return currentConnection;
2016-11-01 11:49:04 +13:00
}
/**
* Get a track's icon
* @param track object
* @return string
**/
export let getTrackIcon = function(current_track = false, core = false){
if (!core) return false
if (!current_track) return false
if (typeof(current_track.uri) == 'undefined') return false
if (typeof(core.tracks[current_track.uri]) === 'undefined') return false
var track = core.tracks[current_track.uri]
if (!track.images) return false
return sizedImages(track.images).small
}
/**
* Format our album objects into a universal format
*
* @param album obj
* @return album obj
**/
export let formatAlbum = function(album){
if (album.release_date !== undefined){
album.date = album.release_date;
}
return album;
}
/**
2017-10-18 17:03:59 +13:00
* Format tracks into our universal format
*
* @param tracks = object or array of objects
2017-10-18 17:03:59 +13:00
* @return array
**/
2017-10-18 17:03:59 +13:00
export let formatTracks = function(tracks){
2017-10-18 20:32:30 +13:00
if (!tracks || tracks === undefined){
return null;
}
// Handle single records
2017-10-18 17:03:59 +13:00
var singular = false;
if (tracks.constructor !== Array){
tracks = [tracks];
singular = true;
}
var formatted = [];
for (var i = 0; i < tracks.length; i++){
// Nested track object (eg in spotify playlist)
if (tracks[i].track && isObject(tracks[i].track)){
2017-10-18 17:03:59 +13:00
var track = Object.assign({}, tracks[i].track);
// Copy supporting values
if (tracks[i].added_by){
track.added_by = tracks[i].added_by;
}
if (tracks[i].added_at){
track.added_at = tracks[i].added_at;
}
if (tracks[i].tlid){
track.tlid = tracks[i].tlid;
}
} else {
var track = Object.assign({}, tracks[i]);
}
if (track.duration_ms){
track.duration = track.duration_ms;
} else if (track.length){
track.duration = track.length;
}
if (track.track_no){
track.track_number = track.track_no;
} else if (track.track_number){
track.track_number = track.track_number;
2017-10-18 17:03:59 +13:00
}
if (track.disc_no){
track.disc_number = track.disc_no;
}
if (track.release_date){
track.date = track.release_date;
}
// Copy images from albums (if applicable)
if (track.album && track.album.images){
if (!track.images || track.images.length > 0){
track.images = track.album.images;
}
}
2017-10-18 17:03:59 +13:00
formatted.push(track);
}
2017-10-18 17:03:59 +13:00
if (singular){
return formatted[0];
} else {
return formatted;
}
}
2016-11-01 11:49:04 +13:00
/**
* Figure out a URI's source namespace
* @param uri = string
**/
export let uriSource = function(uri){
if (!uri){
return false;
}
2016-11-01 11:49:04 +13:00
var exploded = uri.split(':');
return exploded[0]
}
export let sourceIcon = function(uri,source = null){
if (uri) source = uriSource(uri)
2017-10-10 13:05:28 +13:00
switch(source){
2017-08-30 20:54:54 +12:00
2016-11-06 18:29:49 +13:00
case 'local':
case 'm3u':
return 'folder'
2017-08-30 20:54:54 +12:00
case 'gmusic':
return 'google'
2017-08-30 20:54:54 +12:00
case 'podcast':
case 'podcast+file':
case 'podcast+http':
case 'podcast+https':
case 'podcast+itunes':
return 'podcast'
case 'tunein':
case 'somafm':
case 'dirble':
return 'microphone'
2016-11-06 18:29:49 +13:00
default:
return source
}
}
2016-11-01 11:49:04 +13:00
/**
* Get an element from a URI
* @param element = string, the element we wish to extract
* @param uri = string
**/
2018-01-05 21:31:59 +13:00
export let getFromUri = function(element, uri = ""){
2016-11-07 21:21:15 +13:00
var exploded = uri.split(':');
2017-02-26 18:33:20 +13:00
var namespace = exploded[0]
switch (element){
case 'mbid':
var index = exploded.indexOf('mbid')
2017-10-10 13:05:28 +13:00
if (index > -1 ) return exploded[index+1]
2017-02-26 18:33:20 +13:00
break
case 'artistid':
if (exploded[1] == 'artist'){
return exploded[2]
}
break
case 'albumid':
if (exploded[1] == 'album'){
return exploded[2]
}
break
case 'playlistid':
if (exploded[1] == 'playlist'){
return exploded[2]
} else if (exploded[1] == 'user' && exploded[3] == 'playlist'){
return exploded[4]
}
break
case 'playlistowner':
if (exploded[1] == 'user' && exploded[3] == 'playlist'){
return exploded[2]
}
break
2017-02-26 18:33:20 +13:00
case 'trackid':
if (exploded[1] == 'track'){
return exploded[2]
}
break
case 'userid':
if (exploded[1] == 'user'){
return exploded[2]
}
break
2017-05-12 05:22:57 +12:00
case 'genreid':
if (exploded[1] == 'genre'){
return exploded[2]
}
break
case 'seeds':
if (exploded[1] == 'discover'){
return exploded[2]
}
break
2018-01-05 21:31:59 +13:00
case 'searchtype':
2018-01-05 21:31:59 +13:00
if (exploded[0] == "search"){
return exploded[1];
2018-01-05 21:31:59 +13:00
}
break
case 'searchterm':
if (exploded[0] == "search"){
return exploded[2];
}
break
2016-11-07 21:21:15 +13:00
}
2017-02-26 18:33:20 +13:00
return null
2016-11-01 11:49:04 +13:00
}
/**
* Identify what kind of asset a URI is (playlist, album, etc)
* @param uri = string
* @return string
**/
2017-10-10 13:05:28 +13:00
export let uriType = function(uri){
2017-02-26 18:41:21 +13:00
var exploded = uri.split(':')
if (exploded[0] == 'm3u'){
return 'playlist'
}
if (exploded[0] == 'iris'){
switch (exploded[1]){
case 'search':
case 'discover':
case 'browse':
return exploded[1];
break;
}
}
2017-02-26 18:41:21 +13:00
switch (exploded[1]){
case 'track':
case 'artist':
case 'album':
case 'playlist':
2017-05-12 05:22:57 +12:00
case 'genre':
2017-02-26 18:41:21 +13:00
return exploded[1]
break
2016-11-01 11:49:04 +13:00
2017-02-26 18:41:21 +13:00
case 'user':
if (exploded.length > 3 && exploded[3] == 'playlist'){
return 'playlist'
}
return exploded[1]
break
2016-11-01 11:49:04 +13:00
}
return null;
}
/**
* Convert a raw URI into a object index-friendly format. Primarily used for loading local playlists
* @param $uri = string
* @return string
**/
export let indexFriendlyUri = function (uri){
var output = encodeURI(uri)
output = output.replace("'",'%27')
return output
}
/**
* Digest an array of objects and pull into simple array of one property
*
* @param property = string
* @param items = Array
* @return Array
**/
export let arrayOf = function(property, items){
let array = []
for (let i = 0; i < items.length; i++){
array.push(items[i][property])
}
return array
}
/**
* Merge duplicated items in an array
*
* @param list Array the unclean array
* @param key string = the unique key (id, uri, tlid, etc)
**/
export let mergeDuplicates = function(list, key){
var clean_list = [];
var keyed_list = {};
2017-10-10 13:05:28 +13:00
for(var i in list){
var item = list[i]
2017-10-10 13:05:28 +13:00
if (item[key] in keyed_list){
item = Object.assign({}, keyed_list[item[key]], item)
}
keyed_list[item[key]] = item;
}
2017-10-10 13:05:28 +13:00
for(i in keyed_list){
clean_list.push(keyed_list[i]);
}
return clean_list;
}
2017-01-16 18:31:06 +13:00
/**
* Remove duplicate items in a simple array
*
* @param list Array the unclean array
**/
export let removeDuplicates = function(array){
var unique = [];
for (var i in array){
2017-10-10 13:05:28 +13:00
if (unique.indexOf(array[i]) <= -1){
2017-01-16 18:31:06 +13:00
unique.push(array[i])
}
}
return unique;
}
2017-08-14 20:02:41 +12:00
/**
* Apply a partial text search on an array of objects
*
* @param field = string (the field we're to search)
* @param value = string (the value to find)
* @param array = array of objects to search
* @param singular = boolean (just return the first result)
2017-08-14 20:02:41 +12:00
* @return array
**/
export let applyFilter = function(field, value, array, singular = false){
var results = [];
2017-08-14 20:02:41 +12:00
for (var i = 0; i < array.length; i++){
if (array[i][field] && String(array[i][field]).toLowerCase().includes(String(value).toLowerCase())){
if (singular){
return array[i];
} else {
results.push(array[i]);
}
2017-08-14 20:02:41 +12:00
}
}
return results;
2017-08-14 20:02:41 +12:00
}
/**
* Convert a list of indexes to a useable range
* We ignore stragglers, and only attend to the first 'bunch' of consecutive indexes
*
* @param indexes array of int
**/
export let createRange = function (indexes){
// sort our indexes smallest to largest
function sortAsc(a,b){
return a - b
}
indexes.sort(sortAsc);
// iterate indexes to build the first 'bunch'
var first_bunch = []
var previous_index = false
2017-10-10 13:05:28 +13:00
for(var i = 0; i < indexes.length; i++){
if (!previous_index || previous_index == indexes[i]-1){
first_bunch.push(indexes[i])
previous_index = indexes[i]
}
// TODO: break when we find an integer step for better performance
}
return {
start: first_bunch[0],
length: first_bunch.length
}
2016-12-09 18:47:10 +13:00
}
/**
* Sort an array of objects
* @param array = array to sort
* @param property = string to sort by
* @param reverse = boolean
* @param sort_map = array of value ordering (rather than alphabetical, numerical, etc)
2016-12-09 18:47:10 +13:00
* @return array
**/
export let sortItems = function (array, property, reverse = false, sort_map = null){
2016-12-14 08:51:11 +13:00
2017-10-10 13:05:28 +13:00
function compare(a,b){
2016-12-09 18:47:10 +13:00
var a_value = a;
var a_property_split = property.split('.');
2017-10-10 13:05:28 +13:00
for (var i = 0; i < a_property_split.length; i++){
if (typeof(a_value[a_property_split[i]]) === 'undefined'){
a_value = false;
break;
} else {
a_value = a_value[a_property_split[i]];
2016-12-14 08:51:11 +13:00
}
}
2016-12-09 18:47:10 +13:00
var b_value = b;
var b_property_split = property.split('.');
for (var i = 0; i < b_property_split.length; i++){
2017-10-10 13:05:28 +13:00
if (typeof(b_value[b_property_split[i]]) === 'undefined'){
b_value = false;
break;
} else {
b_value = b_value[b_property_split[i]];
2016-12-14 08:51:11 +13:00
}
}
2016-12-09 18:47:10 +13:00
// Sorting by URI as a reference for sorting by uri source (first component of URI)
if (property == 'uri'){
a_value = uriSource(a_value);
b_value = uriSource(b_value);
}
// Map sorting
// Use the index of the string as a sorting mechanism
if (sort_map){
var a_index = sort_map.indexOf(a_value+':');
var b_index = sort_map.indexOf(b_value+':');
if (a_index < b_index) return 1;
if (a_index > b_index) return -1;
// Boolean sorting
} else if (typeof(a_value) === 'boolean'){
if (a_value && !b_value) return -1;
if (!a_value && b_value) return 1;
2016-12-14 08:51:11 +13:00
return 0
2016-12-09 18:47:10 +13:00
// Alphabetic sorting
} else if (typeof(a_value) === 'string'){
if (!a_value || !b_value ) return 0;
if (a_value.toLowerCase() > b_value.toLowerCase()) return 1;
if (a_value.toLowerCase() < b_value.toLowerCase()) return -1;
return 0
2016-12-09 18:47:10 +13:00
// Numeric sorting
} else {
if (parseInt(a_value) > parseInt(b_value)) return 1;
if (parseInt(a_value) < parseInt(b_value)) return -1;
2016-12-14 08:51:11 +13:00
return 0
2016-12-09 18:47:10 +13:00
}
}
var sorted = Object.assign([], array.sort(compare));
if (reverse){
sorted.reverse();
}
return sorted;
}
2018-06-26 08:51:11 +12:00
/**
* Shuffle items in place
*
* @param Array items
* @return Array
**/
export let shuffle = function(array){
var j, x, i;
for (i = array.length - 1; i > 0; i--) {
j = Math.floor(Math.random() * (i + 1));
x = array[i];
array[i] = array[j];
array[j] = x;
}
return array;
}
/**
* Figure out if a value is a number
* @param value = mixed
* @return boolean
**/
export let isNumeric = function (value){
return !isNaN(parseFloat(value)) && isFinite(value)
}
/**
* Figure out if a value is an object
* @param value = mixed
* @return boolean
**/
export let isObject = function(value){
return value instanceof Object && value.constructor === Object;
2016-12-15 12:54:41 +13:00
}
2017-05-14 07:16:11 +12:00
/**
* Detect if an item is in the loading queue. We simply loop all load items to
* see if any items contain our searched key.
*
* TODO: Explore performance of this
2017-06-12 21:10:04 +12:00
* TODO: Allow wildcards
2017-05-14 07:16:11 +12:00
*
* @param load_queue = obj (passed from store)
* @param key = string (the string to lookup)
* @return boolean
**/
2017-05-22 04:19:44 +12:00
export let isLoading = function(load_queue = [], keys = []){
// Loop all of our load queue items
2017-10-10 13:05:28 +13:00
for (var load_queue_key in load_queue){
2017-05-22 04:19:44 +12:00
// Make sure it's not a root object method
2017-05-14 07:16:11 +12:00
if (load_queue.hasOwnProperty(load_queue_key)){
2017-05-22 04:19:44 +12:00
// Loop all the keys we're looking for
for (var i = 0; i < keys.length; i++){
2017-06-14 20:21:02 +12:00
if (load_queue[load_queue_key].includes(keys[i])){
2017-05-22 04:19:44 +12:00
return true
}
2017-05-14 07:16:11 +12:00
}
}
}
return false
}
/**
* Get indexed record(s) by URI from our asset index
*
* @param store = obj
* @param uris = mixed (array or string)
* @return array
**/
export let getIndexedRecords = function(index, uris){
var records = []
// Wrap in array, if we've only got one URI
if (!uris instanceof Array){
uris = [uris]
}
for (var i = 0; i < uris.length; i++){
if (index.hasOwnProperty(uris[i])){
records.push(index[uris[i]])
}
}
return records
}
/**
* Uppercase-ify the first character of a string
*
* @param string = string
* @return string
**/
export let titleCase = function(string){
return string.charAt(0).toUpperCase() + string.slice(1)
2018-01-23 11:34:03 +13:00
}