refactored MiscMethods.js, now Misc.js

This commit is contained in:
Paoda 2019-03-22 20:19:48 -07:00
parent 1291646115
commit dd58f5512f
4 changed files with 359 additions and 361 deletions

View File

@ -1,7 +1,7 @@
import React from "react";
import Song from "../../melodii/Song";
import MusicPlayer from "../../melodii/MusicPlayer";
import Misc, { createID } from "../MiscMethods";
import { createID, sortTable, TableText, formatMetadata } from "../Misc";
import Emitter from "../../melodii/Events";
import Filepath from "../../melodii/Filepath";
import Settings from 'electron-settings';
@ -91,7 +91,7 @@ export default class Table extends React.Component {
*/
handleSort(table, term) {
const temp = table;
Emitter.emit("newTable", Misc.sortTable(temp, term));
Emitter.emit("newTable", sortTable(temp, term));
}
/**
@ -113,22 +113,22 @@ export default class Table extends React.Component {
onKeyDown={this.handleKeyDown.bind(this)}
tabIndex="0">
<td id="text">
{Misc.truncateText(obj.artist, maxWidth, "Roboto")}
{TableText.truncateText(obj.artist, maxWidth, "Roboto")}
</td>
<td id="text">
{Misc.truncateText(obj.title, maxWidth, "Roboto")}
{TableText.truncateText(obj.title, maxWidth, "Roboto")}
</td>
<td id="text">
{Misc.truncateText(obj.album, maxWidth, "Roboto")}
{TableText.truncateText(obj.album, maxWidth, "Roboto")}
</td>
<td id="number">
{Misc.truncateText(obj.year, maxWidth, "Roboto")}
{TableText.truncateText(obj.year, maxWidth, "Roboto")}
</td>
<td id="text">
{Misc.truncateText(obj.genre, maxWidth, "Roboto")}
{TableText.truncateText(obj.genre, maxWidth, "Roboto")}
</td>
<td id="number">
{Misc.truncateText(obj.time, maxWidth, "Roboto")}
{TableText.truncateText(obj.time, maxWidth, "Roboto")}
</td>
</tr>
));
@ -222,7 +222,7 @@ export async function generate(path, template) {
let song = new Song(arr[i]);
song= await Song.getMetadata(song);
table.tbody.push(Misc.formatMetadata(song, song.metadata));
table.tbody.push(formatMetadata(song, song.metadata));
dom.innerHTML = "Creating Table Data: " + ~~((i / end) * 100) + "%";
}

347
src/components/Misc.js Normal file
View File

@ -0,0 +1,347 @@
import Song from "../melodii/Song";
const usedTableIDs = [];
/**
* Finds the ode of a Set of umbers
* @param {Array<Number>} arr
* @return {Number} The Mode
*/
export function mode(arr) {
//https://codereview.stackexchange.com/a/68431
return arr.reduce(
function(current, item) {
var val = (current.numMapping[item] =
(current.numMapping[item] || 0) + 1);
if (val > current.greatestFreq) {
current.greatestFreq = val;
current.mode = item;
}
return current;
},
{ mode: null, greatestFreq: -Infinity, numMapping: {} },
arr
).mode;
}
/**
* Finds the Median of a Set of Numbers
* @param {Array<Number>} arr
* @return {Number} The Median
*/
export function median(arr) {
arr.sort((a, b) => {
return a - b;
});
let half = ~~(arr.length / 2);
if (arr.length % 2) return arr[half];
else return (arr[half - 1] + arr[half]) / 2.0;
}
/**
* Finds the Average of a Set of Numbers
* @param {Array<Number>} arr
* @return {Number} The Average
*/
export function average(arr) {
let total = 0;
for (let i = 0; i < arr.length; i++) {
total += arr[i];
}
return total / arr.length;
}
export class TableText {
/**
* Stack Overflow: https://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393
*
* This Method finds how much hortizontal space text takes up (UTF8 Compliant) using the HTML5 Canvas
*
* @param {String} text Text to Measure
* @param {String} font Font of Text
* @param {*} cnvs Cached Canvas (if it exists)
* @return {Number} Width of String of Text
* @static
*/
static measureText(text, font, cnvs) {
// let canvas =
// self.canvas || (self.canvas = document.createElement("canvas"));
// let ctx = canvas.getContext("2d");
let ctx;
let canvas = cnvs;
if (canvas) ctx = canvas.getContext("2d");
else {
canvas = document.createElement("canvas");
ctx = canvas.getContext("2d");
}
ctx.font = font;
let metrics = ctx.measureText(text);
return metrics.width;
}
/**
* This Method Truncates Text given the amount of available horizontal space and the font of the text desired so that
* All the text fits onto one line. If truncated the String ends up looking like thi...
*
*
* @param {String} text Text to be Truncated
* @param {Number} maxWidth How much Horizontal Space is available to be used up by text.
* @param {String} font Name of Font
* @return {String} Truncated Text
* @static
*/
static truncateText(text, maxWidth, font) {
let canvas = document.createElement("canvas");
let width = TableText.measureText(text, font, canvas);
if (width > maxWidth) {
//text needs truncating...
let charWidths = [];
let ellipsisWidth = TableText.measureText("...", font, canvas);
//get Average width of every char in string
for (let char in text)
if (typeof char === "string")
charWidths.push(TableText.measureText(char, font));
// let charWidth = this.median(charWidths);
let charWidth = average(charWidths);
// let charWidth = this.mode(charWidths);
//Find out how many of these characters fit in max Width;
let maxChars = (maxWidth - ellipsisWidth) / charWidth;
let truncated = "";
try {
truncated = text.substr(0, maxChars);
} catch (e) {
// console.warn('\n' + e + ' ASSUMPTION: Melodii width shrunk to extremely small proportions');
// console.warn('Text: "' + text + '"\nMaximum Width: ' + maxWidth + 'px.\nMaximum Space for Characters: ' + maxChars + 'px.');
}
return truncated + "...";
} else return text;
}
}
/**
* Creates a unique ID that is not UUID compliant.
* - used to distinguish table objects from one another.
* @param {Number} length
* @return {String}
*/
export function createID(length) {
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
let id;
do {
id = "";
for (let i = 0; i < length; i++) id += chars[randInt(0, chars.length)];
} while(usedTableIDs.includes(id));
usedTableIDs.push(id);
return id;
function randInt(min, max) {
return ~~(Math.random() * (max - min) + min);
}
}
/**
* This function takes a Song and the Metadata of said Song and formats it so that it can be easlily processed by Table Generation.
* @param {Song} song
* @param {Object} metadata
* @return {Object} The Formateed Metadata
* @static
*/
export function formatMetadata(song, metadata) {
let format = metadata.format;
let common = metadata.common;
let min = ~~((format.duration % 3600) / 60);
let sec = ~~(format.duration % 60);
if (sec < 10) sec = "0" + sec;
let time = min + ":" + sec;
return {
location: song.location,
time: time,
artist: common.artist || "",
title: common.title || "",
album: common.album || "",
year: common.year || "",
genre: common.genre ? common.genre.toString() : "",
inSeconds: format.duration
};
}
/**
* Sorts a Table based on a Term Given to the Method
*
* @param {Object} table Table Object
* @param {*} term Sort Term
* @return {Object} Processed Table Object
* @static
*
*/
export function sortTable(table, term) {
term = term.toLowerCase();
let tbody = table.tbody.slice();
let res = {
thead: {
tr: table.thead.tr.slice()
},
tbody: null
};
if (term === "title") {
tbody.sort((a, b) => {
// turns [" Uptown Funk"] into ["Uptown Funk"]
let fixedStr = removeLeadingWhitespaces(
a.title,
b.title
);
a.title = fixedStr[0];
b.title = fixedStr[1];
if (a.title < b.title) return -1;
else if (a.title > b.title) return 1;
else return 0;
});
} else if (term === "artist") {
tbody.sort((a, b) => {
let fixedStr = removeLeadingWhitespaces(
a.artist,
b.artist
);
a.artist = fixedStr[0];
b.artist = fixedStr[1];
if (a.artist < b.artist) return -1;
else if (a.artist > b.artist) return 1;
else return 0;
});
} else if (term === "album") {
tbody.sort((a, b) => {
let fixedStr = removeLeadingWhitespaces(
a.album,
b.album
);
a.album = fixedStr[0];
b.album = fixedStr[1];
if (a.album < b.album) return -1;
else if (a.album > b.album) return 1;
else return 0;
});
} else if (term === "genre") {
tbody.sort((a, b) => {
let fixedStr = removeLeadingWhitespaces(
a.genre,
b.genre
);
a.genre = fixedStr[0];
b.genre = fixedStr[1];
if (a.genre < b.genre) return -1;
else if (a.genre > b.genre) return 1;
else return 0;
});
} else if (term === "year") {
tbody.sort((a, b) => {
return a.year - b.year;
});
} else if (term === "time") {
//term == time convert the time into seconds
//The messy else if + else is becomes a.inSeconds || b.inSeconds can be undefined.
tbody.sort((a, b) => {
if (a.inSeconds && b.inSeconds) {
return a.inSeconds - b.inSeconds;
} else if (a.inSeconds || b.inSeconds) {
if (a.inSeconds) {
let parsedB = b.time.split(":");
if (parsedB[0][0] === "0") parsedB[0] = parsedB[0][1];
if (parsedB[1][0] === "0") parsedB[1] = parsedB[1][1];
let totalB =
parseInt(parsedB[0], 10) * 60 +
parseInt(parsedB[1], 10);
b.inSeconds = totalB;
return a.inSeconds - totalB;
} else {
let parsedA = a.time.split(":");
if (parsedA[0][0] === "0") parsedA[0] = parsedA[0][1];
if (parsedA[1][0] === "0") parsedA[1] = parsedA[1][1];
let totalA =
parseInt(parsedA[0], 10) * 60 +
parseInt(parsedA[1], 10);
a.inSeconds = totalA;
return totalA - b.inSeconds;
}
} else {
let parsedA = a.time.split(":");
if (parsedA[0][0] === "0") parsedA[0] = parsedA[0][1];
if (parsedA[1][0] === "0") parsedA[1] = parsedA[1][1];
let parsedB = b.time.split(":");
if (parsedB[0][0] === "0") parsedB[0] = parsedB[0][1];
if (parsedB[1][0] === "0") parsedB[1] = parsedB[1][1];
let totalA =
parseInt(parsedA[0], 10) * 60 +
parseInt(parsedA[1], 10);
let totalB =
parseInt(parsedB[0], 10) * 60 +
parseInt(parsedB[1], 10);
a.inSeconds = totalA;
b.inSeconds = totalB;
return totalA - totalB;
}
});
}
if (table.tbody === tbody) console.warn("Music has not been sorted");
else console.log("Music has been sorted");
res.tbody = tbody;
return res;
/**
* Removes Leading Whitespaces from 2 Strings
* - Used Exclusively in sortTable()
* - Used so that Both Strings can properly be compared
* @param {String} string1
* @param {String} string2
* @return {Array<String>} Truncated Strings
* @static
*/
function removeLeadingWhitespaces(string1, string2) {
const regex = /^\s+/i;
let stringA = string1;
let stringB = string2;
if (regex.test(stringA)) {
let spaces = regex.exec(stringA)[0];
stringA = stringA.substr(spaces.length, stringA.length);
}
if (regex.test(stringB)) {
let spaces = regex.exec(stringB)[0];
stringB = stringB.substr(spaces.length, stringB.length);
}
return [stringA, stringB];
}
}

View File

@ -1,349 +0,0 @@
import Song from "../melodii/Song";
const usedTableIDs = [];
/**
* - Every Method in this file must be Static and _probably_ Synchronous
* - The Methods contained in this class must only be methods that don't really fit anywhere else
* - Any Functions that require the use of Fs are not allowd in this Class.
*/
export default class MiscMethods {
/**
* Finds the Mode of a Set of Numbers
* @param {Array<Number>} arr
* @return {Number} The Mode
* @static
*/
static mode(arr) {
//https://codereview.stackexchange.com/a/68431
return arr.reduce(
function(current, item) {
var val = (current.numMapping[item] =
(current.numMapping[item] || 0) + 1);
if (val > current.greatestFreq) {
current.greatestFreq = val;
current.mode = item;
}
return current;
},
{ mode: null, greatestFreq: -Infinity, numMapping: {} },
arr
).mode;
}
/**
* Finds the Median of a Set of Numbers
* @param {Array<Number>} arr
* @return {Number} The Median
* @static
*/
static median(arr) {
arr.sort((a, b) => {
return a - b;
});
let half = ~~(arr.length / 2);
if (arr.length % 2) return arr[half];
else return (arr[half - 1] + arr[half]) / 2.0;
}
/**
* Finds the Average of a Set of Numbers
* @param {Array<Number>} arr
* @return {Number} The Average
* @static
*/
static average(arr) {
let total = 0;
for (let i = 0; i < arr.length; i++) {
total += arr[i];
}
return total / arr.length;
}
/**
* Stack Overflow: https://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393
*
* This Method finds how much hortizontal space text takes up (UTF8 Compliant) using the HTML5 Canvas
*
* @param {String} text Text to Measure
* @param {String} font Font of Text
* @param {*} cnvs Cached Canvas (if it exists)
* @return {Number} Width of String of Text
* @static
*/
static measureText(text, font, cnvs) {
// let canvas =
// self.canvas || (self.canvas = document.createElement("canvas"));
// let ctx = canvas.getContext("2d");
let ctx;
let canvas = cnvs;
if (canvas) ctx = canvas.getContext("2d");
else {
canvas = document.createElement("canvas");
ctx = canvas.getContext("2d");
}
ctx.font = font;
let metrics = ctx.measureText(text);
return metrics.width;
}
/**
* This Method Truncates Text given the amount of available horizontal space and the font of the text desired so that
* All the text fits onto one line. If truncated the String ends up looking like thi...
*
*
* @param {String} text Text to be Truncated
* @param {Number} maxWidth How much Horizontal Space is available to be used up by text.
* @param {String} font Name of Font
* @return {String} Truncated Text
* @static
*/
static truncateText(text, maxWidth, font) {
let canvas = document.createElement("canvas");
let width = MiscMethods.measureText(text, font, canvas);
if (width > maxWidth) {
//text needs truncating...
let charWidths = [];
let ellipsisWidth = MiscMethods.measureText("...", font, canvas);
//get Average width of every char in string
for (let char in text)
if (typeof char === "string")
charWidths.push(MiscMethods.measureText(char, font));
// let charWidth = this.median(charWidths);
let charWidth = MiscMethods.average(charWidths);
// let charWidth = this.mode(charWidths);
//Find out how many of these characters fit in max Width;
let maxChars = (maxWidth - ellipsisWidth) / charWidth;
let truncated = "";
try {
truncated = text.substr(0, maxChars);
} catch (e) {
// console.warn('\n' + e + ' ASSUMPTION: Melodii width shrunk to extremely small proportions');
// console.warn('Text: "' + text + '"\nMaximum Width: ' + maxWidth + 'px.\nMaximum Space for Characters: ' + maxChars + 'px.');
}
return truncated + "...";
} else return text;
}
/**
* This function takes a Song and the Metadata of said Song and formats it so that it can be easlily processed by Table Generation.
* @param {Song} song
* @param {Object} metadata
* @return {Object} The Formateed Metadata
* @static
*/
static formatMetadata(song, metadata) {
let format = metadata.format;
let common = metadata.common;
let min = ~~((format.duration % 3600) / 60);
let sec = ~~(format.duration % 60);
if (sec < 10) sec = "0" + sec;
let time = min + ":" + sec;
return {
location: song.location,
time: time,
artist: common.artist || "",
title: common.title || "",
album: common.album || "",
year: common.year || "",
genre: common.genre ? common.genre.toString() : "",
inSeconds: format.duration
};
}
/**
* Sorts a Table based on a Term Given to the Method
*
* @param {Object} table Table Object
* @param {*} term Sort Term
* @return {Object} Processed Table Object
* @static
*
*/
static sortTable(table, term) {
term = term.toLowerCase();
let tbody = table.tbody.slice();
let res = {
thead: {
tr: table.thead.tr.slice()
},
tbody: null
};
if (term === "title") {
tbody.sort((a, b) => {
// turns [" Uptown Funk"] into ["Uptown Funk"]
let fixedStr = MiscMethods.removeLeadingWhitespaces(
a.title,
b.title
);
a.title = fixedStr[0];
b.title = fixedStr[1];
if (a.title < b.title) return -1;
else if (a.title > b.title) return 1;
else return 0;
});
} else if (term === "artist") {
tbody.sort((a, b) => {
let fixedStr = MiscMethods.removeLeadingWhitespaces(
a.artist,
b.artist
);
a.artist = fixedStr[0];
b.artist = fixedStr[1];
if (a.artist < b.artist) return -1;
else if (a.artist > b.artist) return 1;
else return 0;
});
} else if (term === "album") {
tbody.sort((a, b) => {
let fixedStr = MiscMethods.removeLeadingWhitespaces(
a.album,
b.album
);
a.album = fixedStr[0];
b.album = fixedStr[1];
if (a.album < b.album) return -1;
else if (a.album > b.album) return 1;
else return 0;
});
} else if (term === "genre") {
tbody.sort((a, b) => {
let fixedStr = MiscMethods.removeLeadingWhitespaces(
a.genre,
b.genre
);
a.genre = fixedStr[0];
b.genre = fixedStr[1];
if (a.genre < b.genre) return -1;
else if (a.genre > b.genre) return 1;
else return 0;
});
} else if (term === "year") {
tbody.sort((a, b) => {
return a.year - b.year;
});
} else if (term === "time") {
//term == time convert the time into seconds
//The messy else if + else is becomes a.inSeconds || b.inSeconds can be undefined.
tbody.sort((a, b) => {
if (a.inSeconds && b.inSeconds) {
return a.inSeconds - b.inSeconds;
} else if (a.inSeconds || b.inSeconds) {
if (a.inSeconds) {
let parsedB = b.time.split(":");
if (parsedB[0][0] === "0") parsedB[0] = parsedB[0][1];
if (parsedB[1][0] === "0") parsedB[1] = parsedB[1][1];
let totalB =
parseInt(parsedB[0], 10) * 60 +
parseInt(parsedB[1], 10);
b.inSeconds = totalB;
return a.inSeconds - totalB;
} else {
let parsedA = a.time.split(":");
if (parsedA[0][0] === "0") parsedA[0] = parsedA[0][1];
if (parsedA[1][0] === "0") parsedA[1] = parsedA[1][1];
let totalA =
parseInt(parsedA[0], 10) * 60 +
parseInt(parsedA[1], 10);
a.inSeconds = totalA;
return totalA - b.inSeconds;
}
} else {
let parsedA = a.time.split(":");
if (parsedA[0][0] === "0") parsedA[0] = parsedA[0][1];
if (parsedA[1][0] === "0") parsedA[1] = parsedA[1][1];
let parsedB = b.time.split(":");
if (parsedB[0][0] === "0") parsedB[0] = parsedB[0][1];
if (parsedB[1][0] === "0") parsedB[1] = parsedB[1][1];
let totalA =
parseInt(parsedA[0], 10) * 60 +
parseInt(parsedA[1], 10);
let totalB =
parseInt(parsedB[0], 10) * 60 +
parseInt(parsedB[1], 10);
a.inSeconds = totalA;
b.inSeconds = totalB;
return totalA - totalB;
}
});
}
if (table.tbody === tbody) console.warn("Music has not been sorted");
else console.log("Music has been sorted");
res.tbody = tbody;
return res;
}
/**
* Removes Leading Whitespaces from 2 Strings
* - Used Exclusively in MiscMethods.sortTable
* - Used so that Both Strings can properly be compared
* @param {String} string1
* @param {String} string2
* @return {Array<String>} Truncated Strings
* @static
*/
static removeLeadingWhitespaces(string1, string2) {
const regex = /^\s+/i;
let stringA = string1;
let stringB = string2;
if (regex.test(stringA)) {
let spaces = regex.exec(stringA)[0];
stringA = stringA.substr(spaces.length, stringA.length);
}
if (regex.test(stringB)) {
let spaces = regex.exec(stringB)[0];
stringB = stringB.substr(spaces.length, stringB.length);
}
return [stringA, stringB];
}
}
/**
* Creates a unique ID that is not UUID compliant.
* - used to distinguish table objects from one another.
* @param {Number} length
* @return {String}
*/
export function createID(length) {
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
let id;
do {
id = "";
for (let i = 0; i < length; i++) id += chars[randInt(0, chars.length)];
} while(usedTableIDs.includes(id));
usedTableIDs.push(id);
return id;
function randInt(min, max) {
return ~~(Math.random() * (max - min) + min);
}
}

View File

@ -1,5 +1,5 @@
import Filepath from "./Filepath";
import Misc, { createID } from "../components/MiscMethods";
import { createID, formatMetadata } from "../components/Misc";
import Song from "./Song";
export default class Playlist {
@ -61,7 +61,7 @@ export default class Playlist {
for (let i = 0; i < filepaths.length - 1; i++) {
let song = new Song(filepaths[i]);
song = await Song.getMetadata(song);
content.push(Misc.formatMetadata(song, song.metadata));
content.push(formatMetadata(song, song.metadata));
}
res(content);
});