diff --git a/README.md b/README.md index 3ed9a821..5bed95e6 100644 --- a/README.md +++ b/README.md @@ -1,82 +1,18 @@ -# MP3 DOM +Name - Dayan Badalbaev. +Welcome to my MP3 player! + +explanatin about what's going on in this repo. +I started to work on this project at the beggining of the first assinment of DOM. +I wanted to continue it in my own way, this is why its not following the given tamplate. +Also I splited the function between the js files including my logic, while im designing my style as a programmer. +Functions that work with the Player object are in `player.js` with the object. +Function that works with the DOM and the event listeners and so on are in `index.js`. +Hope you will appreciate my way and decisions as a programer. + +The site supports- + - Show all the songs. + - Show all the playlists. + - Add a new song. + - remove a song. + - play a song (with a color ficher). -The users of your MP3 player complained about it being inconvenient for regular (non-programmer) people. Time to build a GUI! - -Your task is to create a webpage that conveniently displays the songs and playlists in a player object. The player object will have the same structure as in your previous assignment. - -## General Instructions - -1. Fork this repo into your account. -2. Clone the forked repo to your computer. -3. Create a new git branch for your work. -4. Complete the requirements. -5. Submit your work. -6. May the odds be ever in your favor! - -## New Requirements! -- There is now a section for adding new songs to the player. Make it work! -- Add a play button to every song. Clicking it will play that song. -- Add a remove button to every song. Clicking it will remove the song from the playlist. -- There should be only one event listener on the entire songs list, that handles all play and remove events of songs. -- You are given new template files to use: `index.new.html` and `index.new.js`. - -## Webpage Requirements - -Your webpage should contain 2 lists: - -- A list of the `songs` in the player, sorted by their titles -- A list of the `playlists` in the player, sorted by their names - -### Songs - -Each song list item shall display the following details: - -- song `title` -- `album` name -- `artist` name -- an image with the album's cover art (`coverArt`) -- song `duration` (in `mm:ss` format, of course) - -One song can be played at a time. There should be some indication of the currently playing song (the specific indication is up to you). Clicking on a song will change the indication of the currently playing song. We have already provided code that handles the click event for you. - -### Playlists - -Every playlist list item should display the following information: - -- playlist `name` -- the number of songs in the playlist -- the total duration of the playlist - -## Bonus Requirements - -- After a song begins to play, it automatically switches to the next one when the song duration has passed. -- The color of the durations of songs should reflect their length. A duration of less than 2 min will show green, and will be gradually redder until it is completely red for durations that are above 7 min. -- When a song is removed, all playlists in the page will also be updated. -- When adding a new song, the songs list will remain sorted by title. -- Anything else you can think of... - -## Technical Instructions - -You are provided with a template for your project: - -- an HTML file (`index.html`) -- a linked, empty CSS file (`style.css`) -- a linked JS script with a sample `player` object (`player.js`) -- a linked JS script with a template for your code (`index.js`) -- an `images` folder with the webpage icon and song cover art - -The HTML defines the basic structure of the page. There are 2 container elements - one for the songs and one for the playlists. You may add more structural elements to the HTML (headings etc.), but the songs and playlists themselves must be generated using JS, based on the `player` object. - -## Submission - -1. On GitHub, open a pull request from your branch to the main branch. -2. **Do not merge the pull request!** -3. Add the user `Cyber4sPopo` as collaborator to your repo. -4. Submit a link to the pull request in Google Classroom. - -## Additional Remarks - -- **Avoid duplication!** Use JS functions and CSS classes wisely. -- Maintain high coding standards. Keep your code readable, indented properly, commented and indicative. -- Maintain a proper git workflow. Commit often, push often, write informative commit messages. -- You are free to style your webpage however you like. Make it BEAUTIFUL! diff --git a/index.html b/index.html index c55fb482..5f895ef7 100644 --- a/index.html +++ b/index.html @@ -9,11 +9,36 @@ -
- -
-
- +
+ +
+
+ +
+
+ +
+
diff --git a/index.new.html b/index.new.html index eba39f17..ed4b9cae 100644 --- a/index.new.html +++ b/index.new.html @@ -24,7 +24,7 @@

Add a new song

Songs

- +

Playlists

- +
diff --git a/scripts/index.js b/scripts/index.js index 6842c794..3b443c17 100644 --- a/scripts/index.js +++ b/scripts/index.js @@ -1,30 +1,309 @@ /** - * Plays a song from the player. - * Playing a song means changing the visual indication of the currently playing song. - * - * @param {String} songId - the ID of the song to play + * @todo + * * add song to playlist + * * add a new plalist + * * remove a song from playlist + * * remove a playlist */ -function playSong(songId) { - // Your code here + +/** + * removes all the elements from main content block + */ +function resetWindow(){ + + let el = document.getElementById('main-content'); + while(el.firstChild){ + el.removeChild(el.firstChild); + } + + el.appendChild(createElement("div",[],[],{'id':'songs' , 'class':'main-content'})) + el.appendChild(createElement("div",[],[],{'id':'playlists', 'class':'main-content'})) +} + +/** + * called when a remove button has been clicked. + * removes the song, removes it from the playlists(if exists). + * if the song is currently playing, stops it. + * + * @param {number} id - the id of clicked song + */ +function removeSongHandler(id){ + removeSong(id); + + //if the song is currently playing: + const playNowDiv = document.getElementById("playNow").children[1]; + if(playNowDiv){ + if(playNowDiv.id === "song" + id){ + document.getElementById("playNow").removeChild(playNowDiv); + } + } + + songsHandler(); +} + +/** + * called when 'add song' clicked. + * opens a form to add a song. + */ +function addSongHandler(){ + resetWindow(); + let children = [] + let text; + let keyInput; + let td, td1; + + let head = createElement("h1"); + head.textContent = "Add Song" + + //the loop generates the form inside a table by the keys. + const keys = ["name", "album", "artist", "duration", "image"] + for(const key of keys){ + text = createElement("text"); + text.textContent = "Song's " + key; + td = createElement("td",[text]); + keyInput = createElement("input",[],[],{"id":key, "type":"text", "name":key, "placeholder":key, "required":"required"}); + td1 = createElement("td",[keyInput]) + children.push(createElement("tr", [td,td1],[],{})) + } + + //generates the id as an optional input in the form + text = createElement("text"); + text.textContent = "ID (optional)" + td = createElement("td",[text]) + let id = createElement("input", [],[],{"type":"text", "id":"id", "placeholder":"ID"}) + td1 = createElement("td",[id]) + children.push(createElement("tr", [td,td1],[],{})) + + // create a submit button + let s = createElement("input",[],[],{"type":"submit", "value":"Add"}) + s.textContent = "Add" + s.addEventListener("click",eventLesitener) + td = createElement("tr",[s]) + children.push(td); + + let table = createElement("table",children) + let form = createElement("form",[head, table],["main-content"]) + // Create a form dynamically + form.setAttribute("method", "post"); + document.getElementById("main-content").appendChild(form) +} + +/** + * called when add-song form is submited. + * checks some of the values and cretes new song. + */ +function formSubmit(){ + const name = document.getElementById("name").value; + const album = document.getElementById("album").value; + const artist = document.getElementById("artist").value; + const duration = document.getElementById("duration").value; + const img = document.getElementById("image").value; + const id = document.getElementById("id").value; + + if(!name || !album || !artist || !duration){ + addSongHandler(); + return; + } + + if(duration.length != 5){ + alert("Unvalide Duration!") + addSongHandler(); + return; + } + if((duration[0] < '0' || duration[0] > '9') || + (duration[1] < '0' || duration[1] > '9') || + (duration[3] < '0' || duration[3] >= '6') || + (duration[4] < '0' || duration[4] > '9') || + duration[2] != ":"){ + alert("Unvalide Duration!") + addSongHandler(); + return; + } + + addSong(name, album, artist, duration, id, img); + + songsHandler(); } /** * Creates a song DOM element based on a song object. + * @param {object} song - gets a song + * @returns {HTMLElement} div - includes all the song's details */ -function createSongElement({ id, title, album, artist, duration, coverArt }) { + function createSongElement({ id, title, album, artist, duration, coverArt }) { const children = [] - const classes = [] - const attrs = { onclick: `playSong(${id})` } - return createElement("div", children, classes, attrs) + const classes = ["song"] + const attrs = {"id":"song" + id} + + const topic = createElement("h1"); + topic.textContent = "song #" + id + "\n"; + children.push(topic) + + const info = createElement("p"); + info.textContent = "name: " + title + "\nalbum: " + album + "\nartist: " + artist + "\nduration: " + convertDuration(duration); + children.push(info) + + if(coverArt){ + const img = createElement("img"); + img.src = coverArt; + children.push(img) + } + + const el = createElement("div", children, classes, attrs) + return el +} + +/** + * @returns {array} songsElems - array of html elements that represents all the songs + */ +function getSongsElems(){ + let songsElems = []; + for(const song of player.songs){ + const songDiv = createSongElement(song) + + songsElems.push(songDiv); + } + return songsElems; +} + +/** + * called when 'songs' button (nav-bar) clicked + * resets the main content and shows the songs + */ +function songsHandler(){ + resetWindow(); + let songsElems = getSongsElems(); + for(const songEl of songsElems){ + const id = parseInt(songEl.id.slice(4, songEl.id.length)); + const playButt = createElement("button",[],[],{"value":"Play"}); + playButt.textContent = "Play"; + playButt.addEventListener("click",eventLesitener) + + const removeButt = createElement("button",[],[],{"value":"Remove"}); + removeButt.textContent= "Remove"; + removeButt.addEventListener("click",eventLesitener); + + let br = createElement("br"); + songEl.appendChild(br); + songEl.appendChild(playButt); + br = createElement("br"); + songEl.appendChild(br); + songEl.appendChild(removeButt); + document.getElementById("songs").appendChild(songEl) + } +} + +function eventLesitener(event){ + const callerVal = event.target.value; + let id = event.target.parentElement.id; + id = parseInt(id.slice(4,id.length)); + if(callerVal === "Play"){ + playSong(id); + } + else if(callerVal === "Remove"){ + removeSongHandler(id); + } + else if(callerVal === "Add"){ + formSubmit(); + } + else if(callerVal === "Stop"){ + clearPlayingNow(); + } +} + +/** + * called when 'playlists' button (nav-bar) clicked + * resets the main content and shows the playLists + */ +function playlistsHandler(){ + resetWindow() + for(const playlist of player.playlists){ + document.getElementById("playlists").appendChild(createPlaylistElement(playlist)) + } +} + +function addSongToPlaylist(){ + resetWindow(); + + + + let checkbox, id; + let songsElems = getSongsElems(); + for(let songElem of songsElems){ + id = songElem.id.slice(4, songElem.id.length) + checkbox = createElement("input", [songElem], [], {"id":"cb" + id, "type":"checkbox", "name":"cb" + id}) + console.log(checkbox); + } +} + +/** + * clears the playing-now div (at the bottom of the nav-bar) + */ +function clearPlayingNow(){ + let currDir = document.getElementById("playNow") + while(currDir.firstChild){ + currDir.removeChild(currDir.firstChild); + } + + const plyNow = createElement("header"); + plyNow.textContent = "Playing Now"; + currDir.appendChild(plyNow); +} + +/** + * Plays a song from the player. + * Playing a song means showing the song in the 'playing now' section (at the bottom of the nav-bar) + * @param {String} songId - the ID of the song to play + */ +function playSong(songId) { + clearPlayingNow() + let song = getEl(player.songs, songId); + let currDir = document.getElementById("playNow") + + if(song.duration <= 120){ + currDir.style.background = "rgb(0,255,0)" + } + else if(song.duration >= 420){ + currDir.style.background = "rgb(255,0,0)" + } + else{ + const green = (420 - song.duration)*256/300 + const red = 256-green; + currDir.style.background = "rgb(" + red +"," + green + ",0)" + } + + currDir.appendChild(createSongElement(song)) + + const br = createElement("br") + currDir.childNodes[1].appendChild(br); + + const stopButt = createElement("button",[],[],{"value":"Stop"}); + stopButt.addEventListener("click",eventLesitener) + stopButt.textContent = "Stop Playing"; + currDir.childNodes[1].appendChild(stopButt); } /** * Creates a playlist DOM element based on a playlist object. + * @param {object} playlist + * @returns {HTMLElement} div - a div that includes playlist's details and song */ function createPlaylistElement({ id, name, songs }) { const children = [] - const classes = [] + const classes = ["playlist"] const attrs = {} + + const topic = createElement("header"); + topic.textContent = "playlist #" + id + " - " + name; + children.push(topic) + + const info = createElement("p"); + info.textContent = "songs -"; + children.push(info) + for(let i = 1; i <= songs.length; i++){//pushes all the song in a playlist as an element + children.push(createSongElement(getEl(player.songs,songs[i-1]))); + } + return createElement("div", children, classes, attrs) } @@ -39,9 +318,24 @@ function createPlaylistElement({ id, name, songs }) { * Each child can be a DOM element, or a string (if you just want a text element). * @param {Array} classes - the class list of the new element * @param {Object} attributes - the attributes for the new element + * + * @returns {HTMLElement} newEl - the element that has been created */ function createElement(tagName, children = [], classes = [], attributes = {}) { - // Your code here + let newEl = document.createElement(tagName); + for(const child of children){ + newEl.appendChild(child); + } + for(const cls of classes){ + newEl.classList.add(cls); + } + for(const atr in attributes){ + newEl.setAttribute(atr,attributes[atr]) + } + return newEl } // You can write more code below this line +player.songs.sort((s1,s2) => s1.title.localeCompare(s2.title)); +player.playlists.sort((pl1,pl2) => pl1.name.localeCompare(pl2.name)); +songsHandler() diff --git a/scripts/player.js b/scripts/player.js index 5a85f825..a75b374d 100644 --- a/scripts/player.js +++ b/scripts/player.js @@ -62,3 +62,127 @@ const player = { { id: 5, name: "Israeli", songs: [4, 5] }, ], } + +/** + * gets specific object in an array by id. + * throws exception if not found. + * @param {array} arr - songs array or playlist array to seek in. + * @param {number} id - the wanted id of the object + * @returns {object} - the object in the array that has the wanted id. + */ +function getEl(arr, id){ + for(let el of arr){ + if(el.id === id){ + return el; + } + } + throw("couldn't find element where id=" + id); +} + +/** + * gets an array of objects and id(optional) + * if id is given - checks if it aviable id (if its not already used) + * if id isnt given - generate new id + * @param {array} arr + * @param {number} id + * @returns {number} id + */ +function generateID(arr, id){ + let ids = [];//an array that includes all the ids of the objects in arr + for(let cell of arr){ + ids.push(cell.id); + } + + if(!id){//if id is undifined (optional!) => creates new one + let i = 1; + while(ids.includes(i)){ + i++; + } + id = i; + } + else{//if id has been given, checks if it already used. + if(ids.includes(id)){ + throw(id + " id already exist!"); + } + } + return id; +} + +/** + * return string('mm:ss') if number was given + * return number(seconds) if string was given + * @param {string|number} duration (string('mm:ss') / number(seconds)) + * @returns {string|number} duration + */ +function convertDuration(duration) { + if(typeof(duration) === 'number'){ + let min = Math.floor(duration / 60); + let sec = duration % 60; + if (min < 10) { + min = "0" + String(min); + } + if (sec < 10) { + sec = "0" + String(sec); + } + return min + ':' + sec; + } + else{//if its a string + return parseInt(duration.slice(3)) + parseInt(duration.slice(0,2)) * 60; + } +} + +/** + * creates new song, add it to songs array. + * @param {string} title - song's title. + * @param {string} album - song's album. + * @param {string} artist - song's artist. + * @param {string} duration - song's duration. patern - "MM:SS" + * @param {number} id - optional. the id of the song. if not given, generates new one. (should be uniqe.) + * @param {string} coverArt - path to image of the song.(local or from the network). + * @returns {number} id - the id of the new object + */ +function addSong(title, album, artist, duration = "00:00", id, coverArt) { + id = generateID(player.songs, id); + duration = convertDuration(duration); + let newSong={id, title, album, artist, duration, coverArt} + player.songs.push(newSong); + player.songs.sort((s1,s2) => s1.title.localeCompare(s2.title)); + return id; +} + +/** + * removes a song by the given id. + * if the song doesn't exist - throws exception + * @param {number} id + */ +function removeSong(id) { + //parameters - id (type number) + //removes the song with the wanted id(from songs array and from all the playlists) + let song2delete = getEl(player.songs,id); + const index = player.songs.indexOf(song2delete); + player.songs.splice(index,1); //deletes the wanted song from the songs array. + + //searches and deletes the id of the song from all the playlists + for(const pl of player.playlists){ + let songs = pl.songs; + const i = songs.indexOf(id); + if(i > -1){ + songs.splice(i,1); + } + if(songs.length === 0){ + removePlaylist(pl.id); + } + } +} + +/** + * removes a playlist by an id + * @param {number} id + */ +function removePlaylist(id) { + //parameters - id(type number) + //removes the playlist with the wanted id + let pl = getEl(player.playlists, id); + const index = player.playlists.indexOf(pl); + player.playlists.splice(index,1); + } diff --git a/style.css b/style.css index f4645fe9..e97b6694 100644 --- a/style.css +++ b/style.css @@ -1 +1,83 @@ -/* Your code here */ +@import url('https://fonts.googleapis.com/css?family=Josefin+Sans&display=swap'); + +*{ + margin:0; + padding:0; + box-sizing: border-box; + list-style:none; + text-decoration: none; + font-family: 'Josefin Sans', sans-serif ; +} + +body{ + background: #f3f5f9; +} + +.wrapper{ + display:flex; + position:relative; +} + +.wrapper .navbardiv{ + position:fixed; + width:200px; + height: 100%; + background: #4b4276; +} +td { white-space:pre-wrap; word-wrap:break-word } + +nav ul li{ + padding:15px; + border-bottom: 1px solid rgba(0,0,0,0.05); + border-top: 1px solid rgba(225,225,225,0.05); +} + +.nav-link{ + color:#bdb8d7; + display: block; +} + +nav ul li:hover{ + background: #594f8d; +} + +/*the navigator's text turns white if we 'touch' it*/ +nav ul li:hover button{ + color: #fff; +} + +.wrapper .main-content{ + width:100%; + margin-left: 230px; +} + +header{ + font-size: 28; + padding: 20px; + background: #fff; + color:#717171; + border-bottom: 1px solid #e0e4e8; + border-top: 1px solid #e0e4e8; +} + +li ul li{ + size:70%; +} + +img{ + width:150px; + /* margin-left: 20px; + margin-bottom:-25px; */ +} +p{ + white-space: pre-line; +} + +.playlist>p{ + font-size: 25px; +} + +.playlist h1{ + font-size: 20px; +} +