Преглед на файлове

Added manageSongs, with filters. Fixed some issues with genre editing.

KrisVos130 преди 9 години
родител
ревизия
c213e1e1ab

+ 242 - 2
app/client/scripts/events.js

@@ -441,6 +441,8 @@ Template.queues.events({
         $("#dislikes").val(this.dislikes).change();
         $("#duration").val(this.duration).change();
         $("#skip-duration").val(this.skipDuration).change();
+        $("#genres").val(this.genres).change();
+        $("#genres").material_select();
         $("#previewPlayerContainer").addClass("hide-preview");
         Session.set("image_url", this.img);
         Session.set("editing", true);
@@ -636,7 +638,7 @@ Template.queues.events({
         newSong.duration = Number($("#duration").val());
         newSong.skipDuration = $("#skip-duration").val();
         newSong.requestedBy = Session.get("song").requestedBy;
-        newSong.genres = Session.get("song").genres;
+        newSong.genres = $("#genres").val();
         if(newSong.skipDuration === undefined){
             newSong.skipDuration = 0;
         }
@@ -866,7 +868,245 @@ Template.manageStation.events({
         newSong.duration = Number($("#duration").val());
         newSong.skipDuration = $("#skip-duration").val();
         newSong.requestedBy = Session.get("song").requestedBy;
-        newSong.genres = Session.get("song").genres;
+        newSong.genres = $("#genres").val();
+        Meteor.call("updatePlaylistSong", newSong.mid, newSong, function(err, res) {
+            console.log(err, res);
+            if (err) {
+                var $toastContent = $('<span><strong>Song not saved.</strong> ' + err.reason + '</span>');
+                Materialize.toast($toastContent, 8000);
+            } else {
+                var $toastContent = $('<span><strong>Song saved!</strong> No errors were found.</span>');
+                Materialize.toast($toastContent, 4000);
+                Session.set("song", newSong);
+            }
+        });
+    }
+});
+
+Template.manageSongs.events({
+    /* TODO Add undo delete button */
+    "change #show_genres_cb": function() {
+        var selected = $("#show_genres_cb").is(":checked");
+        Session.set("showGenres", selected);
+    },
+    "change #show_no_genres_cb": function() {
+        var selected = $("#show_no_genres_cb").is(":checked");
+        Session.set("showNoGenres", selected);
+    },
+    "input #id": function() {
+        $("#previewPlayerContainer").addClass("hide-preview");
+    },
+    "input #img": function() {
+        var url = $("#img").val();
+        Session.set("image_url", url);
+    },
+    "click .preview-button": function(e){
+        Session.set("song", this);
+        $("#previewModal").openModal();
+    },
+    "click #previewImageButton": function() {
+        $("#preview-image").attr("src", Session.get("song").img);
+    },
+    "click .edit-song-button": function(e){
+        Session.set("song", this);
+        Session.set("genre", $(e.target).data("genre"));
+        $("#mid").val(this.mid).change();
+        $("#artist").val(this.artist).change();
+        $("#title").val(this.title).change();
+        $("#img").val(this.img).change();
+        $("#id").val(this.id).change();
+        $("#likes").val(this.likes).change();
+        $("#dislikes").val(this.dislikes).change();
+        $("#duration").val(this.duration).change();
+        $("#skip-duration").val(this.skipDuration).change();
+        $("#genres").val(this.genres).change();
+        $("#genres").material_select();
+        $("#previewPlayerContainer").addClass("hide-preview");
+        Session.set("image_url", this.img);
+        Session.set("editing", true);
+        $("#editModal").openModal({
+            complete : function() {
+                Session.set("editing", false);
+                if (YTPlayer !== undefined && YTPlayer.stopVideo !== undefined) {
+                    YTPlayer.stopVideo();
+                }
+            }
+        });
+    },
+    "click .remove-song-button": function(e){
+        var genre = $(e.target).data("genre") || $(e.target).parent().data("genre");
+        Meteor.call("removeSongFromPlaylist", genre, this.mid);
+    },
+    "click #play": function() {
+        var duration = Session.get("song").duration;
+        var d = moment.duration(parseInt(duration), 'seconds');
+        $("#time-total").text(d.minutes() + ":" + ("0" + d.seconds()).slice(-2));
+        $("#previewPlayerContainer").removeClass("hide-preview");
+        var song = Session.get("song");
+        var id = song.id;
+        var volume = localStorage.getItem("volume") || 20;
+        if (song.duration !== 0) {
+            $("#play").attr("disabled", true);
+            $("#stop").attr("disabled", false);
+            $("#pause").attr("disabled", false);
+            $("#forward").attr("disabled", false);
+            if (YTPlayer === undefined) {
+                YTPlayer = new YT.Player("previewPlayer", {
+                    height: 540,
+                    width: 568,
+                    videoId: id,
+                    playerVars: {autoplay: 1, controls: 0, iv_load_policy: 3, showinfo: 0, fs: 0},
+                    events: {
+                        'onReady': function(event) {
+                            event.target.seekTo(Number(song.skipDuration));
+                            event.target.playVideo();
+                            event.target.setVolume(volume);
+                        },
+                        'onStateChange': function(event){
+                            if (event.data == YT.PlayerState.PAUSED) {
+                                if (seekerBarInterval !== undefined) {
+                                    Meteor.clearInterval(seekerBarInterval);
+                                    seekerBarInterval = undefined;
+                                }
+                            }
+                            if (event.data == YT.PlayerState.UNSTARTED) {
+                                if (seekerBarInterval !== undefined) {
+                                    Meteor.clearInterval(seekerBarInterval);
+                                    seekerBarInterval = undefined;
+                                }
+                                $(".seeker-bar").css({width: "0"});
+                                $("#time-elapsed").text("0:00");
+                                $("#previewPlayerContainer").addClass("hide-preview");
+                            }
+                            if (event.data == YT.PlayerState.PLAYING) {
+                                seekerBarInterval = Meteor.setInterval(function() {
+                                    var duration = Session.get("song").duration;
+                                    var timeElapsed = YTPlayer.getCurrentTime();
+                                    var skipDuration = Session.get("song").skipDuration;
+
+                                    if (duration <= (timeElapsed - skipDuration)) {
+                                        YTPlayer.stopVideo();
+                                        $("#play").attr("disabled", false);
+                                        $("#stop").attr("disabled", true);
+                                        $("#pause").attr("disabled", true);
+                                        $("#forward").attr("disabled", true);
+                                        $("#previewPlayerContainer").addClass("hide-preview");
+                                        $(".seeker-bar").css({width: "0"});
+                                        $("#time-elapsed").text("0:00");
+                                        Meteor.clearInterval(seekerBarInterval);
+                                    } else {
+                                        var percentComplete = (timeElapsed - skipDuration) / duration * 100;
+                                        $(".seeker-bar").css({width: percentComplete + "%"});
+                                        var d = moment.duration(timeElapsed - skipDuration, 'seconds');
+                                        $("#time-elapsed").text(d.minutes() + ":" + ("0" + d.seconds()).slice(-2));
+                                    }
+                                }, 100);
+                                $("#play").attr("disabled", true);
+                                $("#stop").attr("disabled", false);
+                                $("#pause").attr("disabled", false);
+                                $("#forward").attr("disabled", false);
+                            } else {
+                                $("#play").attr("disabled", false);
+                                $("#stop").attr("disabled", true);
+                                $("#pause").attr("disabled", true);
+                                $("#forward").attr("disabled", true);
+                            }
+                        }
+                    }
+                });
+            } else {
+                if (YTPlayer.getPlayerState() === 2) {
+                    YTPlayer.playVideo();
+                } else {
+                    console.log(id, song.skipDuration, song.duration);
+                    YTPlayer.loadVideoById(id);
+                    YTPlayer.seekTo(Number(song.skipDuration));
+                }
+            }
+            $("#previewPlayerContainer").removeClass("hide-preview");
+        }
+    },
+    "click #stop": function() {
+        $("#play").attr("disabled", false);
+        $("#stop").attr("disabled", true);
+        $("#pause").attr("disabled", true);
+        $("#forward").attr("disabled", true);
+        if (previewEndSongTimeout !== undefined) {
+            Meteor.clearTimeout(previewEndSongTimeout);
+        }
+        if (YTPlayer !== undefined && YTPlayer.stopVideo !== undefined) {
+            YTPlayer.stopVideo();
+        }
+    },
+    "click #pause": function() {
+        $("#play").attr("disabled", false);
+        $("#stop").attr("disabled", false);
+        $("#pause").attr("disabled", true);
+        $("#forward").attr("disabled", true);
+        if (previewEndSongTimeout !== undefined) {
+            Meteor.clearTimeout(previewEndSongTimeout);
+        }
+        if (YTPlayer !== undefined && YTPlayer.pauseVideo !== undefined) {
+            YTPlayer.pauseVideo();
+        }
+    },
+    "click #forward": function() {
+        var error = false;
+        if (YTPlayer !== undefined) {
+            var duration = Number(Session.get("song").duration) | 0;
+            var skipDuration = Number(Session.get("song").skipDuration) | 0;
+            if (YTPlayer.getDuration() < duration + skipDuration) {
+                var $toastContent = $('<span><strong>Error.</strong> The song duration is longer than the length of the video.</span>');
+                Materialize.toast($toastContent, 8000);
+                error = true;
+            } else {
+                YTPlayer.seekTo(skipDuration + duration - 10);
+            }
+        }
+        if (!error) {
+            if (previewEndSongTimeout !== undefined) {
+                Meteor.clearTimeout(previewEndSongTimeout);
+            }
+            previewEndSongTimeout = Meteor.setTimeout(function() {
+                if (YTPlayer !== undefined) {
+                    YTPlayer.stopVideo();
+                }
+                $("#play").attr("disabled", false);
+                $("#stop").attr("disabled", true);
+                $("#pause").attr("disabled", true);
+                $("#forward").attr("disabled", true);
+                $("#previewPlayerContainer").addClass("hide-preview");
+            }, 10000);
+        }
+    },
+    "click #get-spotify-info": function() {
+        var search = $("#title").val();
+        var artistName = $("#artist").val();
+        getSpotifyInfo(search, function(data) {
+            for(var i in data){
+                for(var j in data[i].items){
+                    if(search.indexOf(data[i].items[j].name) !== -1 && artistName.indexOf(data[i].items[j].artists[0].name) !== -1){
+                        $("#img").val(data[i].items[j].album.images[2].url).change();
+                        $("#duration").val(data[i].items[j].duration_ms / 1000).change();
+                        return;
+                    }
+                }
+            }
+        }, artistName);
+    },
+    "click #save-song-button": function() {
+        var newSong = {};
+        newSong.mid = $("#mid").val();
+        newSong.id = $("#id").val();
+        newSong.likes = Number($("#likes").val());
+        newSong.dislikes = Number($("#dislikes").val());
+        newSong.title = $("#title").val();
+        newSong.artist = $("#artist").val();
+        newSong.img = $("#img").val();
+        newSong.duration = Number($("#duration").val());
+        newSong.skipDuration = $("#skip-duration").val();
+        newSong.requestedBy = Session.get("song").requestedBy;
+        newSong.genres = $("#genres").val() || [];
         Meteor.call("updatePlaylistSong", newSong.mid, newSong, function(err, res) {
             console.log(err, res);
             if (err) {

+ 17 - 2
app/client/scripts/helpers.js

@@ -210,6 +210,23 @@ Template.news.helpers({
     }
 });
 
+Template.manageSongs.helpers({
+    songs: function () {
+        var noGenres = Session.get("showNoGenres");
+        var genres = Session.get("showGenres");
+        if (noGenres === true && genres === true) {
+            return Songs.find();
+        } else if (noGenres === true && genres === false) {
+            return Songs.find({genres: []});
+        } else {
+            return Songs.find({$where : "this.genres.length > 0"});
+        }
+    },
+    song_image: function() {
+        return Session.get("image_url");
+    }
+});
+
 Template.manageStation.helpers({
     songs: function () {
         var parts = location.href.split('/');
@@ -221,8 +238,6 @@ Template.manageStation.helpers({
         var songs = [];
         if (playlist !== undefined && playlist.songs !== undefined) {
             playlist.songs.forEach(function(songMid) {
-                console.log(songMid);
-                console.log(Songs.findOne({mid: songMid}));
                 songs.push(Songs.findOne({mid: songMid}));
             });
         }

+ 18 - 0
app/client/scripts/onCreated.js

@@ -117,6 +117,24 @@ Template.manageStation.onCreated(function() {
     });
 });
 
+Template.manageSongs.onCreated(function() {
+    Session.set("showNoGenres", false);
+    Session.set("showGenres", true);
+    var tag = document.createElement("script");
+    tag.src = "https://www.youtube.com/iframe_api";
+    var firstScriptTag = document.getElementsByTagName('script')[0];
+    firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
+    YTPlayer = undefined;
+    $(document).keydown(function(evt){
+        if (evt.keyCode==83 && (evt.ctrlKey)){
+            evt.preventDefault();
+            if (Session.get("editing") === true) {
+                $("#save-song-button").click();
+            }
+        }
+    });
+});
+
 Template.register.onCreated(function() {
     Accounts.onLoginFailure(function() {
         var errAlert = $('<div style="margin-bottom: 0" class="alert alert-danger" role="alert"><strong>Oh Snap!</strong> Something went wrong when trying to register with GitHub. Maybe an account with that username already exists?</div>');

+ 25 - 0
app/client/scripts/onRendered.js

@@ -50,6 +50,31 @@ Template.manageStation.onRendered(function() {
     });
 });
 
+Template.manageSongs.onRendered(function() {
+    $("#previewModal").on("hidden.bs.modal", function() {
+        if (previewEndSongTimeout !== undefined) {
+            Meteor.clearTimeout(previewEndSongTimeout);
+        }
+        $("#play").attr("disabled", false);
+        $("#stop").attr("disabled", true);
+        if (YTPlayer !== undefined) {
+            $("#previewPlayer").hide();
+            YTPlayer.seekTo(0);
+            YTPlayer.stopVideo();
+        }
+    });
+    $(document).ready(function() {
+        var volume = localStorage.getItem("volume") || 20;
+        $("#volume_slider").val(volume).on("input", function() {
+            volume = Number($("#volume_slider").val());
+            localStorage.setItem("volume", volume);
+            if (YTPlayer !== undefined) {
+                YTPlayer.setVolume(volume);
+            }
+        });
+    });
+});
+
 Template.news.onRendered(function() {
     if (rTimeInterval !== undefined) {
         Meteor.clearInterval(rTimeInterval)

+ 14 - 0
app/client/scripts/routes.js

@@ -109,6 +109,20 @@ Router.route("/admin", {
     }
 });
 
+Router.route("/admin/songs", {
+    waitOn: function() {
+        return [Meteor.subscribe("isModerator", Meteor.userId()), Meteor.subscribe("isAdmin", Meteor.userId())];
+    },
+    action: function() {
+        var user = Meteor.users.findOne({});
+        if (user !== undefined && user.profile !== undefined && (user.profile.rank === "admin" || user.profile.rank === "moderator")) {
+            this.render("manageSongs");
+        } else {
+            this.redirect("/");
+        }
+    }
+});
+
 Router.route("/admin/queues", {
     waitOn: function() {
         return [Meteor.subscribe("isModerator", Meteor.userId()), Meteor.subscribe("isAdmin", Meteor.userId())];

+ 180 - 0
app/client/templates/manageSongs.html

@@ -0,0 +1,180 @@
+<template name="manageSongs">
+    {{> alerts}}
+    <div class="landing">
+        {{> header}}
+        <div class="row">
+            <div class="col m8 s8 l8 offset-l2 offset-m1 offset-s2 admin-playlist-panel card-panel teal accent-3">
+                <div class="card-content white-text">
+                    <h3 class="text-center">Filters</h3>
+                    <p>
+                        <input checked="checked" type="checkbox" id="show_genres_cb" />
+                        <label for="show_genres_cb" class="white-text">Show songs with genres</label>
+                    </p>
+                    <p>
+                        <input type="checkbox" id="show_no_genres_cb" />
+                        <label for="show_no_genres_cb" class="white-text">Show songs without a genre</label>
+                    </p>
+                </div>
+            </div>
+            <div class="col m8 s8 l8 offset-l2 offset-m1 offset-s2 admin-playlist-panel card-panel teal accent-3">
+                <div class="card-content white-text">
+                    <h3 class="text-center">Songs</h3>
+                    <table class="bordered">
+                        <thead>
+                        <tr>
+                            <th>Title</th>
+                            <th>Artist(s)</th>
+                            <th>Id</th>
+                            <th>Mid</th>
+                            <th>Genres</th>
+                            <th>Likes</th>
+                            <th>Dislikes</th>
+                            <th class="table-right-th">Edit</th>
+                            <th class="table-right-th">Remove</th>
+                        </tr>
+                        </thead>
+                        <tbody>
+                        {{#each songs}}
+                            <tr>
+                                <th align="left" scope="row">{{title}}</th>
+                                <td align="left">{{artist}}</td>
+                                <td align="left">{{id}}</td>
+                                <td align="left">{{mid}}</td>
+                                <td align="left">{{genres}}</td>
+                                <td align="left">{{likes}}</td>
+                                <td align="left">{{dislikes}}</td>
+                                <td class="table-right-td">
+                                    <button class="btn edit-song-button"
+                                            data-toggle="modal" data-target="#editModal">Edit
+                                    </button>
+                                </td>
+                                <td class="table-right-td">
+                                    <button class="btn red remove-song-button"><i
+                                            class="material-icons">remove_circle</i></button>
+                                </td>
+                            </tr>
+                        {{/each}}
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div id="editModal" class="modal modal-fixed-footer">
+        <div class="modal-content musare white-text">
+            <div class="row">
+                <h4 class="center-align">Video Preview</h4>
+                <div class="video-container" id="previewPlayerContainer">
+                    <div width="960" height="540" id="previewPlayer"></div>
+                </div>
+                <div class="seeker-bar-container col l10 m10 s10 white" id="preview-progress">
+                    <div class="seeker-bar teal" style="width: 0%"></div>
+                </div>
+            <span class="col l2 m2 s2 center" id="preview-time">
+                <span id="time-elapsed">0:00</span> / <span id="time-total">0:00</span>
+            </span>
+                <button id="play" title="Play video" class="btn green col m1 s1 l1 offset-l3 offset-m3 offset-s3">
+                    <i class="material-icons">play_arrow</i>
+                </button>
+                <button id="stop" title="Stop video" class="btn red col m1 s1 l1" disabled>
+                    <i class="material-icons">stop</i>
+                </button>
+                <button id="pause" title="Pause video" class="btn orange col m1 s1 l1" disabled>
+                    <i class="material-icons">pause</i>
+                </button>
+                <button id="forward" title="Go to the last 10 seconds of the video" class="btn blue col m1 s1 l1"
+                        disabled>
+                    <i class="material-icons">fast_forward</i>
+                </button>
+                <form class="col m2 s2 l2" action="#">
+                    <p class="range-field" style="margin-top: 0">
+                        <input type="range" id="volume_slider" min="0" max="100"/>
+                    </p>
+                </form>
+            </div>
+            <div class="row">
+                <h4 class="center-align">Image Preview</h4>
+                <img id="song-preview"
+                     onerror="this.src='http://static.boredpanda.com/blog/wp-content/uploads/2014/04/amazing-fox-photos-182.jpg'"
+                     class="center-block" src="{{song_image}}"/>
+            </div>
+            <div class="row">
+                <h4 class="center-align">Edit Info</h4>
+                <div class="input-field col l8 m8 s12 offset-l2 offset-m2">
+                    <select multiple id="genres">
+                        <option value="" disabled selected>Select Genre(s):</option>
+                        <option value="edm">EDM</option>
+                        <option value="chill">Chill</option>
+                    </select>
+                    <label class="white-text">Genre(s)</label>
+                </div>
+                <div class="input-field col l8 m8 s12 offset-l2 offset-m2">
+                    <i class="material-icons prefix">vpn_key</i>
+                    <label for="mid" class="white-text">Song MID</label>
+                    <input class="validate" name="mid" id="mid" type="text" pattern=".{6}"/>
+                </div>
+                <div class="input-field col l8 m8 s12 offset-l2 offset-m2">
+                    <i class="material-icons prefix">vpn_key</i>
+                    <label for="mid" class="white-text">Song ID</label>
+                    <input class="validate" name="id" id="id" type="text" pattern=".{11}"/>
+                </div>
+                <div class="input-field col l8 m8 s12 offset-l2 offset-m2">
+                    <i class="material-icons prefix">vpn_key</i>
+                    <label for="mid" class="white-text">Song Genres</label>
+                    <input class="validate" name="genres" id="genres" type="text" disabled/>
+                </div>
+                <div class="input-field col l8 m8 s12 offset-l2 offset-m2">
+                    <i class="material-icons prefix">person</i>
+                    <label for="id" class="white-text">Song Artist</label>
+                    <input class="validate" name="artist" id="artist" aria-required="true" type="text"/>
+                </div>
+                <div class="input-field col l8 m8 s12 offset-l2 offset-m2">
+                    <i class="material-icons prefix">subject</i>
+                    <label for="title" class="white-text">Song Title</label>
+                    <input class="validate required" name="title" id="title" type="text"/>
+                </div>
+                <div class="input-field col l8 m8 s12 offset-l2 offset-m2">
+                    <i class="material-icons prefix">timelapse</i>
+                    <label for="title" class="white-text">Song Duration</label>
+                    <input class="validate" name="duration" id="duration" type="number" step="any" min="0"/>
+                </div>
+                <div class="input-field col l8 m8 s12 offset-l2 offset-m2">
+                    <i class="material-icons prefix">timer_off</i>
+                    <label for="skip-duration" class="white-text">Skip Duration</label>
+                    <input class="validate" id="skip-duration" type="number" step="any" min="0"/>
+                </div>
+                <div class="input-field col l8 m8 s12 offset-l2 offset-m2">
+                    <i class="material-icons prefix">thumb_up</i>
+                    <label for="likes" class="white-text">Likes</label>
+                    <input disabled id="likes" type="number"/>
+                </div>
+                <div class="input-field col l8 m8 s12 offset-l2 offset-m2">
+                    <i class="material-icons prefix">thumb_down</i>
+                    <label for="dislikes" class="white-text">Dislikes</label>
+                    <input disabled id="dislikes" type="number"/>
+                </div>
+                <div class="input-field col l8 m8 s12 offset-l2 offset-m2">
+                    <i class="material-icons prefix">image</i>
+                    <label for="img" class="white-text">Song Image</label>
+                    <input class="validate" name="img" id="img" type="url"/>
+                </div>
+            </div>
+            <div class="row">
+                <button type="button" id="get-spotify-info"
+                        class="btn btn-large col l6 m6 s10 offset-l3 offset-m3 offset-s1 waves-effect waves-light">Get
+                    Spotify Data
+                </button>
+                <button type="button" id="save-song-button"
+                        class="btn btn-large col l6 m6 s10 offset-l3 offset-m3 offset-s1 waves-effect waves-light">Save
+                    Changes
+                </button>
+            </div>
+            <script>
+                $('#genres').material_select();
+            </script>
+        </div>
+        <div class="modal-footer musare white-text">
+            <a href="#!" class="modal-action modal-close waves-effect waves-light btn-flat white">X</a>
+        </div>
+    </div>
+</template>

+ 2 - 1
app/server/server.js

@@ -1052,7 +1052,8 @@ Meteor.methods({
                 "img": newSong.img,
                 "duration": newSong.duration,
                 "skipDuration": newSong.skipDuration,
-                "approvedBy": Meteor.userId()
+                "approvedBy": Meteor.userId(),
+                "genres": newSong.genres
         }}, function(err) {
                 console.log(err);
                 if (err) {