Tracking Development with Git now

This commit is contained in:
Paoda
2019-02-08 19:34:04 -06:00
commit 3aedb93bbe
40 changed files with 8813 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
import React from 'react';
import noalbumart from '../assets/img/noalbumart.png';
import Emitter from '../melodii/Events';
export default class AlbumArt extends React.Component {
constructor() {
super();
this.state = {albumArt: noalbumart};
this.handleEvents();
}
shouldComponentUpdate(nextprops, nextState) {
return this.state.albumArt !== nextState.albumArt;
}
render() {
console.log("Album Art Updated");
return (
<div id='albumContainer'>
<img alt='Album-Art' src={this.state.albumArt} id='albumImg'></img>
</div>
);
}
/**
* @listens Song#updateAlbumArt Updates Album Art
*/
handleEvents() {
Emitter.on('updateAlbumArt', (blob, err) => {
if (err) throw err;
this.setState({albumArt: blob});
});
}
}

236
src/components/Body.js Normal file
View File

@@ -0,0 +1,236 @@
import React from "react";
import Table from "./Body/Table";
import Song from "../melodii/Song";
import Filepath from "../melodii/Filepath";
import Misc from "./MiscMethods";
import Emitter from "../melodii/Events";
import Modal from "./Modal";
const Settings = window.require("electron-settings");
export default class Body extends React.Component {
constructor() {
super();
this.state = {
table: null,
msg: ""
};
}
/** @listens Table#newTable loads new Table*/
handleEvents() {
Emitter.on("newTable", (obj, err) => {
if (err) throw err;
this.setState({ table: obj });
});
}
componentWillMount() {
this.initialize();
}
shouldComponentUpdate(nextProps, nextState) {
return this.state.table !== nextState.table;
}
render() {
return (
<div className="wrapper">
<div id="searchBar">
<input
type="text"
placeholder="Search..."
onKeyUp={this.checkKey.bind(this)}
tabIndex="0"
/>
<input
type="button"
onClick={this.openSettings.bind(this)}
value="Settings"
/>
<span id="bad-search-syntax" />
</div>
<Table table={this.state.table} />
</div>
);
}
/** Initializes Body Element (Therefore Table too) @async @return {void}*/
async initialize() {
this.handleEvents.bind(this);
this.handleEvents();
let template = {
thead: {
tr: ["Artist", "Title", "Album", "Year", "Genre", "Time"] //contains <th> strings
},
tbody: []
};
if (!Settings.has("tableJSON")) {
let tableJSON = await this.generate(template);
this.setState({
table: tableJSON
});
let timestamp = Date.now();
Settings.set("tableJSON", {
data: tableJSON,
timestamp: timestamp
});
let date = new Date(timestamp);
console.log(
"Table data created at: " +
date.toDateString() +
" at " +
date.toTimeString()
);
} else {
console.log("Data Loaded from Persistent Storage Space");
this.setState({
table: Settings.get("tableJSON").data
});
let date = new Date(Settings.get("tableJSON").timestamp);
console.log(
"Table data created on: " +
date.toDateString() +
" at " +
date.toTimeString()
);
}
}
/**
* Generates Table From Scratch
* @param {Array<String>} template
* @return {Promise<}
* @async
*/
async generate(template) {
let filepath = new Filepath("C:\\Users\\Paoda\\Downloads");
let list = await filepath.getValidFiles();
console.log("Found " + list.length + "valid files.");
return new Promise(async (res, rej) => {
let temp = await this.generateBody(
template,
list,
0,
list.length - 1
);
res(temp);
});
}
/**
* Generates Body of Table from Scratch
* @param {Object} tableArg Table Element
* @param {Array<String>} arr Array of Valid Song Files
* @param {Number} start of Array
* @param {Number} end of Array
* @return {Object} Table Object with Body Completely parsed.
* @async
*/
async generateBody(tableArg, arr, start, end) {
let table = tableArg;
let t1 = performance.now();
let dom = document.getElementById("bad-search-syntax");
for (let i = 0; i <= end; i++) {
let song = new Song(arr[i]);
song = await Song.getMetadata(song);
table.tbody.push(Misc.formatMetadata(song, song.metadata));
dom.innerHTML = "Creating Table Data: " + ~~((i / end) * 100) + "%";
}
let t2 = performance.now();
console.log(
"Time Taken (Table Data Creation): " +
Math.floor(t2 - t1) / 1000 +
"s"
);
return new Promise((res, rej) => {
res(table);
});
}
/**
* Handles Key Presses
* @param {KeyboardEvent} e
*/
checkKey(e) {
if (e.keyCode === 13) this.search(e.target.value);
}
/**
* Searches for Elements using a the provided String Parameter as an argument.
* - Directly Manipulates the State of Body once it's done so it returns void
* @param {String} string
*/
search(string) {
const table = Settings.get("tableJSON").data;
console.log("Search Text: " + string);
if (string === "") {
this.setState({ table: table });
} else if (string.includes(":")) {
let type = string.match(/^(.*):/)[0];
type = type.substr(0, type.length - 1);
let term = string.match(/:( ?.*)$/)[0];
if (term[1] === " ") term = term.substr(2, term.length);
else term = term.substr(1, term.length);
type = type.toLowerCase();
let temp = {
tbody: null,
thead: table.thead
};
if (type === "title") {
term = term.toLowerCase();
temp.tbody = table.tbody.filter(obj =>
obj.title.toLowerCase().includes(term)
);
} else if (type === "artist") {
term = term.toLowerCase();
temp.tbody = table.tbody.filter(obj =>
obj.artist.toLowerCase().includes(term)
);
} else if (type === "album") {
term = term.toLowerCase();
temp.tbody = table.tbody.filter(obj =>
obj.album.toLowerCase().includes(term)
);
} else if (type === "genre") {
term = term.toLowerCase();
temp.tbody = table.tbody.filter(obj =>
obj.genre.toLowerCase().includes(term)
);
} else if (type === "year") {
term = parseInt(term, 10);
temp.tbody = table.tbody.filter(obj => obj.year === term);
} else {
// type == time
term = term.toLowerCase();
temp.tbody = table.tbody.filter(obj =>
obj.time.toLowerCase().includes(term)
);
}
this.setState({ table: temp });
let error = document.getElementById("bad-search-syntax");
if (error.innerHTML !== "") error.innerHTML = "";
console.log("Search found: " + temp.tbody.length + " Songs");
} else {
document.getElementById("bad-search-syntax").innerHTML =
"Invalid Syntax!";
}
}
openSettings() {
const settings = document.querySelector('.settings-window');
Emitter.emit('loadModal');
}
}

View File

@@ -0,0 +1,179 @@
import React from "react";
import Song from "../../melodii/Song";
import MusicPlayer from "../../melodii/MusicPlayer";
import Misc from "../MiscMethods";
import Emitter from "../../melodii/Events";
/** @type {HTMLElement} */
var active = document.createElement("tr");
active.classList.toggle("active");
const mp = new MusicPlayer();
var JSXcache;
/** The React Component Responsible for Rendering the Song Table */
export default class Table extends React.Component {
/** Throttles the interval of which the class re-renders when resizing.
* @param {Object} props */
constructor(props) {
super(props);
let self = this;
let throttle;
window.onresize = e => {
window.clearTimeout(throttle);
throttle = setTimeout(() => {
self.forceUpdate();
}, 250);
};
}
/**
* Method responsible for Initializing Table.headJSX and Table.bodyJSX, generating the Table JSX.
* @param {Object} table Obejct of Elements, Was once JSON (usually)
*/
initialize(table) {
this.headJSX = this.parseHead(table);
this.bodyJSX = this.parseBody(table);
}
render() {
if (this.props.table) {
if (this.props.table !== JSXcache) {
this.initialize(this.props.table);
console.log("Table Rendered from Scratch");
JSXcache = this.props.table;
return (
<table id="songTable">
<thead>
<tr>{this.headJSX}</tr>
</thead>
<tbody>{this.bodyJSX}</tbody>
</table>
);
} else {
return (
<table id="songTable">
<thead>
<tr>{this.headJSX}</tr>
</thead>
<tbody>{this.bodyJSX}</tbody>
</table>
);
}
} else {
return <table id="songTable" />;
}
}
/**
* Parses the Head Portion of the Table
* @param {Object} table
* @return {Array<HTMLTableHeaderCellElement>}
*/
parseHead(table) {
let arr = table.thead.tr;
return arr.map(string => (
<th
key={string}
onClick={this.handleSort.bind(this, table, string)}>
{string}
</th>
));
}
/**
* Handles Sorting the table by a Album, Title, Year etc..
* @fires Table#newTable Event to Generate a new Table
* @param {Object} table
* @param {String} term
*/
handleSort(table, term) {
const temp = table;
Emitter.emit("newTable", Misc.sortTable(temp, term));
}
/**
* Parses the Body Portion of the Table Object
* @param {Object} table
* @return {Array<HTMLTableRowElement>}
*/
parseBody(table) {
let arr = table.tbody;
let clientWidth = document.documentElement.clientWidth;
let innerWidth = window.innerWidth || 0;
let maxWidth = Math.max(clientWidth, innerWidth) / 6;
let temp = arr.map(obj => (
<tr
key={obj.location}
data-filepath={obj.location}
onClick={this.handleClick.bind(this)}
onKeyDown={this.handleKeyDown.bind(this)}
tabIndex="0">
<td id="text">
{Misc.truncateText(obj.artist, maxWidth, "Roboto")}
</td>
<td id="text">
{Misc.truncateText(obj.title, maxWidth, "Roboto")}
</td>
<td id="text">
{Misc.truncateText(obj.album, maxWidth, "Roboto")}
</td>
<td id="number">
{Misc.truncateText(obj.year, maxWidth, "Roboto")}
</td>
<td id="text">
{Misc.truncateText(obj.genre, maxWidth, "Roboto")}
</td>
<td id="number">
{Misc.truncateText(obj.time, maxWidth, "Roboto")}
</td>
</tr>
));
return temp;
}
/**
* Handles a Click on the Table
* @param {MouseEvent} e
* @return {void}
* @async
*/
async handleClick(e) {
if (active !== e.currentTarget) {
active.classList.toggle("active");
e.currentTarget.classList.toggle("active");
active = e.currentTarget;
} else {
let filepath = e.currentTarget.dataset.filepath;
let song = new Song(filepath);
mp.load(song);
mp.play();
song = await Song.getMetadata(song);
Song.setAlbumArt(song.metadata);
}
}
/**
* Handles a KeyDown Event
* @param {KeyboardEvent} e
* @async
*/
async handleKeyDown(e) {
// console.log("Focus:" + e.currentTarget.dataset.filepath + " Key: " + e.key);
if (e.keyCode === 13 && e.currentTarget === active) {
//Active and Presses Enter
let filepath = e.currentTarget.dataset.filepath;
let song = new Song(filepath);
mp.load(song);
mp.play();
song = await Song.getMetadata(song);
Song.setAlbumArt(song.metadata);
}
}
}

22
src/components/Footer.js Normal file
View File

@@ -0,0 +1,22 @@
import React from 'react';
import SongInfo from './Footer/SongInfo';
import PlayPause from './Footer/PlayPause';
import Volume from './Footer/Volume';
export default class Footer extends React.Component {
render() {
return (
<footer>
<div className='mediaControls'>
{/* <SkipBkwd /> */}
<PlayPause />
{/* <SkipFwd /> */}
<Volume />
</div>
<div className='songInfo'>
<SongInfo title={this.props.title} artist={this.props.artist} album={this.props.album} />
</div>
</footer>
);
}
}

View File

@@ -0,0 +1,16 @@
import React from 'react';
// import '@fortawesome/fontawesome-free/css/all.css';
import { library } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faVolumeUp } from '@fortawesome/free-solid-svg-icons';
library.add(faVolumeUp)
export default class Mute extends React.Component {
render() {
return (
// <i className= 'fa fa-volume-up' id='muteIcon'></i>
<FontAwesomeIcon icon="faVolumeUp" id='muteIcon' />
);
}
}

View File

@@ -0,0 +1,52 @@
import React from "react";
import Emitter from "../../melodii/Events";
import MusicPlayer from "../../melodii/MusicPlayer";
// import "@fortawesome/fontawesome-free/css/all.css";
import { library } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faPause, faPlay } from '@fortawesome/free-solid-svg-icons'
library.add(faPause);
library.add(faPlay);
const mp = new MusicPlayer();
export default class PlayPause extends React.Component {
/** @listens */
constructor(props) {
super(props);
Emitter.on("toggle", bool => this.handleEvent(bool));
this.state = { icon: "pause" };
}
handleClick() {
//Updates Icon and controls the Audio came from clicking on button
if (this.state.icon === "pause") {
//set to play
mp.pause();
this.setState({ icon: "play" });
} else {
//set to pause
mp.play();
this.setState({ icon: "pause" });
}
}
/**
* Updates Icon From Event
* @param {Boolean} bool
*/
handleEvent(bool) {
if (!bool) this.setState({ icon: "play" });
else this.setState({ icon: "pause" });
}
render() {
return (
<FontAwesomeIcon icon={this.state.icon} id="playPause" onClick={this.handleClick.bind(this)} />
);
}
}

View File

@@ -0,0 +1,9 @@
import React from 'react';
export default class SongInfo extends React.Component {
render() {
return (
<span>{this.props.title} - {this.props.artist} | {this.props.album}</span>
);
}
}

View File

@@ -0,0 +1,48 @@
import React from "react";
import MusicPlayer from "../../melodii/MusicPlayer";
const Settings = window.require("electron-settings");
const mp = new MusicPlayer();
export default class Volume extends React.Component {
constructor() {
super();
this.state = {
input: Settings.get("Volume") || 50,
max: 50
};
}
render() {
return (
<input
style={{
backgroundSize:
(this.state.input * 100) / this.state.max + "% 100%"
}}
value={this.state.input}
id="volBar"
type="range"
max={this.state.max}
onChange={this.setVolume.bind(this)}
onMouseUp={this.setLastVolume.bind(this)}
/>
);
}
/**
* Sets Volume
* @param {Event} e
*/
setVolume(e) {
this.setState({
input: e.target.value
});
mp.setVolume(e.target.value / 50);
}
/**
* Saves the Previous Volume
* @param {Event} e
*/
setLastVolume(e) {
Settings.set("Volume", e.target.value);
}
}

20
src/components/Header.js Normal file
View File

@@ -0,0 +1,20 @@
import React from 'react';
import Minimize from './Header/Minimize';
import Quit from './Header/Quit';
export default class Header extends React.Component {
shouldComponentUpdate() {
return false;
}
render() {
return (
<header>
<span>Melodii Music Player</span>
<div id='headerIcons'>
<Minimize />
<Quit />
</div>
</header>
);
}
}

View File

@@ -0,0 +1,21 @@
import React from 'react';
import Buttons from '../../melodii/Buttons';
// import '@fortawesome/fontawesome-free/css/all.css';
import { library } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faWindowMinimize } from '@fortawesome/free-solid-svg-icons'
library.add(faWindowMinimize)
export default class Minimize extends React.Component {
render() {
return (
<FontAwesomeIcon icon="window-minimize" onClick={this.minimize} />
// <i onClick={this.minimize} className= 'fa fa-window-minimize'></i>
);
}
minimize() {
Buttons.minimize();
}
}

View File

@@ -0,0 +1,21 @@
import React from 'react';
import Buttons from '../../melodii/Buttons';
// import '@fortawesome/fontawesome-free/css/all.css';
import { library } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faTimes } from '@fortawesome/free-solid-svg-icons'
library.add(faTimes);
export default class Quit extends React.Component {
render() {
return (
// <i onClick={this.quit} className= 'fa fa-times'></i>
<FontAwesomeIcon icon="times" onClick={this.quit} />
);
}
quit() {
Buttons.quit();
}
}

View File

@@ -0,0 +1,324 @@
import Song from "../melodii/Song";
/**
* - 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];
}
}

44
src/components/Modal.js Normal file
View File

@@ -0,0 +1,44 @@
import React from 'react';
import Emitter from '../melodii/Events';
export default class Modal extends React.Component {
constructor(props) {
super(props);
this.state = {
style: { display: "none" }
}
Emitter.on('loadModal', () => {
this.setState({
style: { display: "flex" }
});
});
}
render() {
console.log("Modal Created");
return(
<div
className="modal"
onClick={this.handleClick.bind(this)}
style={this.state.style}
>
{this.props.content}
</div>
)
}
shouldComponentUpdate(nProps, nState) {
return (
this.props.content !== nProps.content ||
this.state.display !== nState.style.display)
}
handleClick() {
console.log("Dismissing Modal.");
this.setState({
style: { display: "none" }
});
}
}

View File

@@ -0,0 +1,14 @@
import React from 'react';
import Modal from '../Modal';
const Settings = window.require("electron-settings");
export default class SettingsManager extends Modal {
render() {
const JSX = <div className='settings-window'>Hello</div>
return(
<Modal content={JSX} />
);
}
}

54
src/components/SeekBar.js Normal file
View File

@@ -0,0 +1,54 @@
import React from "react";
import MusicPlayer from "../melodii/MusicPlayer";
var mp = new MusicPlayer();
export default class SeekBar extends React.Component {
constructor(props) {
super(props);
this.isPlayingOnMouseDown = false;
this.onChangeUsed = false;
}
/** @param {Event} e */
handleChange(e) {
mp.seek(+e.target.value);
this.onChangeUsed = true;
}
/** @param {KeyboardEvent} e */
handleMouseDown(e) {
this.isPlayingOnMouseDown = !mp.isPaused;
mp.pause();
}
/** @param {MouseEvent} e */
handleMouseUp(e) {
if (!this.onChangeUsed) {
mp.seek(+e.target.value);
}
if (this.isPlayingOnMouseDown) mp.play();
}
render() {
return (
<div className="seekBar">
<input
type="range"
style={{
backgroundSize:
((this.props.currentTime || 0) * 100) /
(this.props.duration || 0) +
"% 100%"
}}
value={this.props.currentTime || 0}
max={this.props.duration || 0}
id="seekRange"
className="melodiiSlider"
onChange={this.handleChange.bind(this)}
onMouseDown={this.handleMouseDown.bind(this)}
onMouseUp={this.handleMouseUp.bind(this)}
/>
</div>
);
}
}