user_accolor=profile_data["accolor"],
playlists=plist_data,
songs=songs,
- song_list=render_template("song-list.html", songs=songs))
+ song_list=render_template("song-list.html", songs=songs, is_profile_song_list=True))
@app.post("/edit-profile")
def edit_profile():
flash_and_log(f"Created playlist {name}", "success")
return redirect(request.referrer)
-@app.post("/delete-playlist/<int:playlistid>")
+@app.get("/delete-playlist/<int:playlistid>")
def delete_playlist(playlistid):
if not "userid" in session:
abort(401)
# Make sure playlist exists
- plist_data = query_db("select * from playlists where playlistid = ?", args=[playlistid])
+ plist_data = query_db("select * from playlists where playlistid = ?", args=[playlistid], one=True)
if not plist_data:
abort(404)
get_db().commit()
flash_and_log(f"Deleted playlist {plist_data['name']}", "success")
- return redirect(request.referrer)
+ return redirect(f"/users/{session['username']}")
@app.post("/append-to-playlist")
def append_to_playlist():
songid = request.form["songid"]
# Make sure song exists
- song_data = query_db("select * from songs where songid = ?", args=[songid])
+ song_data = query_db("select * from songs where songid = ?", args=[songid], one=True)
if not song_data:
abort(404)
query_db("update playlists set updated = ? where playlistid = ?", args=[timestamp, playlistid])
get_db().commit()
- return {"status": "ok"}
+ flash_and_log(f"Added '{song_data['title']}' to {plist_data['name']}", "success")
+
+ return redirect(request.referrer)
@app.post("/edit-playlist/<int:playlistid>")
def edit_playlist_post(playlistid):
abort(401)
# Make sure playlist exists
- plist_data = query_db("select * from playlists where playlistid = ?", args=[playlistid])
+ plist_data = query_db("select * from playlists where playlistid = ?", args=[playlistid], one=True)
if not plist_data:
abort(404)
if session["userid"] != plist_data["userid"]:
abort(401)
- private = request.form["type"] == "private"
-
# Make sure all songs are valid
- songids = []
- for field, value in request.form.items():
- if field.startswith("position-"):
- try:
- position = int(field[len("position-"):])
- songid = int(value)
- except ValueError:
- abort(400)
-
- song_data = query_db("select * from songs where songid = ?", args=[songid])
- if not song_data:
- abort(400)
-
- # Song is valid, add to list
- songids.append((position, songid))
+ try:
+ songids = [int(s) for s in request.form["songids"].split(",")]
+ except ValueError:
+ # Invalid songid(s)
+ abort(400)
+
+ for songid in songids:
+ song_data = query_db("select * from songs where songid = ?", args=[songid])
+ if not song_data:
+ abort(400)
# All songs valid - delete old songs
query_db("delete from playlist_songs where playlistid = ?", args=[playlistid])
# Re-add songs with new positions
- for position, songid in songids:
+ for position, songid in enumerate(songids):
query_db("insert into playlist_songs (playlistid, position, songid) values (?, ?, ?)", args=[playlistid, position, songid])
+ # Update private, name
+ private = int(request.form["type"] == "private")
+ name = request.form["name"]
+ query_db("update playlists set private = ?, name = ? where playlistid = ?", [private, name, playlistid])
+
get_db().commit()
flash_and_log("Playlist updated", "success")
return render_template(
"playlist.html",
name=plist_data["name"],
+ playlistid=plist_data["playlistid"],
+ userid=plist_data["userid"],
username=plist_data["username"],
+ songs=songs,
song_list=render_template("song-list.html", songs=songs))
def flash_and_log(msg, category=None):
db = getattr(g, '_database', None)
if db is None:
db = g._database = sqlite3.connect(DATA_DIR / "database.db")
+ db.cursor().execute("PRAGMA foreign_keys = ON")
# Get current version
user_version = query_db("pragma user_version", one=True)[0]
name TEXT NOT NULL,
private INTEGER NOT NULL,
- FOREIGN KEY(userid) REFERENCES users(userid)
+ FOREIGN KEY(userid) REFERENCES users(userid) ON DELETE CASCADE
);
CREATE INDEX playlists_by_userid ON playlists(userid);
songid INTEGER NOT NULL,
PRIMARY KEY(playlistid, position),
- FOREIGN KEY(playlistid) REFERENCES playlists(playlistid),
- FOREIGN KEY(songid) REFERENCES songs(songid)
+ FOREIGN KEY(playlistid) REFERENCES playlists(playlistid) ON DELETE CASCADE,
+ FOREIGN KEY(songid) REFERENCES songs(songid) ON DELETE CASCADE
);
CREATE INDEX playlist_songs_by_playlist ON playlist_songs(playlistid);
border: 0px;
border-radius: 5px;
padding: 8px;
- margin: 10px;
+}
+
+select {
+ border: none;
+ background-color: var(--purple);
+ color: var(--yellow);
+ border-radius: 5px;
+ font-size: 16px;
+ padding: 8px;
}
div.main {
border: solid 2px var(--purple);
}
+.profile-bio {
+ margin: 10px 0px;
+}
+
.profile-action {
margin-bottom: 20px;
}
margin: 10px;
}
+.playlist-list-entry {
+ box-shadow: 0px 0px 5px 0px;
+ border-radius: 10px;
+ padding: 10px;
+ margin: 10px 0px;
+}
+
+.draggable-song {
+ box-shadow: 0px 0px 5px 0px;
+ border-radius: 10px;
+ padding: 5px 10px;
+ margin: 10px 0px;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: 10px;
+}
+
/* Coloris Color Picker */
.clr-field button {
{% block title %}{{ name }}{% endblock %}
-{% block body %}
+{% block body -%}
<h1>{{ name }}</h1>
<p>Playlist by <a href="/users/{{ username }}" class="profile-link">{{ username }}</a></p>
+{% if session["userid"] == userid -%}
+<p class="playlist-actions">
+<button class="button" onclick="showPlaylistEditor()">Edit</button>
+<button class="button" onclick="deletePlaylist()">Delete</button>
+</p>
+
+<script>
+function showPlaylistEditor() {
+ document.querySelector(".song-list").hidden = true;
+ document.querySelector(".playlist-actions").hidden = true;
+ document.querySelector(".playlist-editor").hidden = false;
+}
+
+function hidePlaylistEditor() {
+ document.querySelector(".song-list").hidden = false;
+ document.querySelector(".playlist-actions").hidden = false;
+ document.querySelector(".playlist-editor").hidden = true;
+}
+
+function deletePlaylist() {
+ if (confirm("Are you sure you want to delete this playlist?")) {
+ window.location.href = "/delete-playlist/{{ playlistid }}";
+ }
+}
+</script>
+
+{%- endif %}
+
{{ song_list|safe }}
-{% endblock %}
+{% if session["userid"] == userid -%}
+<!-- Drag-and-drop playlist editor -->
+<div class="playlist-editor" hidden>
+ <h2>Edit Playlist</h2>
+ <form action="/edit-playlist/{{ playlistid }}" method="post" onsubmit="updateSongIds(event)">
+ <label for="name">Playlist Name</label><br>
+ <input name="name" type="text" maxlength="100" value="{{ name }}"/><br>
+
+ <label for="type">Playlist Type:</label>
+ <input name="type" type="radio" value="private" checked />
+ <label for="private">Private</label>
+ <input name="type" type="radio" value="public" />
+ <label for="public">Public</label><br>
+
+ <input id="playlist-songids-input" type="hidden" name="songids" value="-1" /> <!-- Populated by script on submit -->
+
+ <p>Drag and drop songs to reorder them, or use the trash icon to remove them from the playlist.</p>
+
+ <div class="edit-list">
+ {%- for song in songs %}
+ <div class="draggable-song" draggable="true" ondragstart="onSongDragStart(event)" ondragover="onSongDragOver(event)" ondrop="onSongDrop(event)">
+ <span class="songid" hidden>{{ song.songid }}</span>
+ <span class="song-title">{{ song.title }}</span> -
+ <span class="song-artist">{{ song.username }}</span>
+ <span style="width: 100%"></span>
+ <button onclick="removeSong(event)" class="song-list-button" title="Remove" style="margin-right: 0px">
+ <img class="lsp_btn_delete02" alt="Delete">
+ </button>
+ </div>
+ {%- endfor %}
+
+ <!-- dummy song to move to end -->
+ <div class="draggable-song" ondragover="onSongDragOver(event)" ondrop="onSongDrop(event)">
+
+ </div>
+ </div>
+
+ <a href="javascript:hidePlaylistEditor()">Cancel</a>
+ <input type="submit" value="Save" style="margin: 10px;"/>
+ </form>
+
+ <script>
+ function updateSongIds(event) {
+ var form = event.currentTarget;
+ var editList = form.querySelector(".edit-list");
+ var songids = [];
+ for (const entry of editList.children) {
+ var songidSpan = entry.querySelector(".songid");
+ if (songidSpan) {
+ console.log(songidSpan.textContent);
+ songids.push(songidSpan.textContent);
+ }
+ }
+ songids = songids.join(",");
+ console.log(songids);
+
+ var songidsInput = form.querySelector("#playlist-songids-input");
+ songidsInput.value = songids;
+ }
+ function onSongDragStart(event) {
+ var list = event.currentTarget.closest(".edit-list");
+ var index = [...list.children].indexOf(event.currentTarget);
+ event.dataTransfer.setData("text", index.toString());
+ event.dataTransfer.effectAllowed = "move";
+ }
+ function onSongDragOver(event) {
+ event.preventDefault();
+ event.dataTransfer.dropEffect = "move";
+ var list = event.currentTarget.closest(".edit-list");
+ for (const child of list.children) {
+ child.style.borderTop = "";
+ child.style.borderBottom = "";
+ }
+ if (event.currentTarget.previousElementSibling) {
+ event.currentTarget.previousElementSibling.style.borderBottom = "3px solid var(--purple)";
+ }
+ event.currentTarget.style.borderTop = "3px solid var(--purple)";
+ }
+ function onSongDrop(event) {
+ event.preventDefault();
+ const data = event.dataTransfer.getData("text");
+ var sourceIndex = parseInt(data);
+ var list = event.currentTarget.closest(".edit-list");
+ for (const child of list.children) {
+ child.style.borderTop = "";
+ child.style.borderBottom = "";
+ }
+ var sourceElement = list.children[sourceIndex];
+ if (sourceElement !== event.currentTarget) {
+ sourceElement.remove();
+ list.insertBefore(sourceElement, event.currentTarget);
+ }
+ }
+ </script>
+</div>
+{%- endif %}
+
+{%- endblock %}
<label for="public">Public</label><br>
<a href="javascript:hideAddPlaylist();">Cancel</a>
- <input type="submit" value="Create Playlist" />
+ <input type="submit" value="Create Playlist" style="margin: 10px;"/>
</form>
<script>
function showAddPlaylist() {
<div class="song-buttons">
<!-- Owner-Specific Buttons (Edit/Delete) -->
- {% if session["userid"] == song.userid %}
+ {% if session["userid"] == song.userid and is_profile_song_list %}
<a href="/edit-song?songid={{ song.songid }}" class="song-list-button">
<img class="lsp_btn_edit02" alt="Edit">
</a>
{% if current_user_playlists %}
<!-- Add to Playlist Buttons -->
<div class="song-playlist-controls">
- <button type="button" class="button" onclick="return showPlaylistSelector(event, {{ song.songid }})">Add to Playlist</button>
+ <form action="/append-to-playlist" method="post">
+ <input type="hidden" name="songid" value="{{ song.songid }}" id="playlist-selector-songid"/>
+ <select name="playlistid">
+ <option value="-1">Add to Playlist...</option>
+ {% for plist in current_user_playlists -%}
+ <option value="{{ plist.playlistid }}" onclick="this.closest('form').submit()">{{ plist['name'] }}</option>
+ {%- endfor %}
+ </select>
+ </form>
</div>
{% endif %}
</div>
</div>
{% endfor %}
-
- {% if current_user_playlists -%}
- <!-- Playlist selector, shown when Add to Playlist is clicked -->
- <div class="playlist-selector" hidden>
- <form action="/append-to-playlist" method="post">
- <input type="hidden" name="songid" value="-1" id="playlist-selector-songid"/>
- <select name="playlistid">
- {% for plist in current_user_playlists -%}
- <option value="{{ plist.playlistid }}">{{ plist['name'] }}</option>
- {%- endfor %}
- </select>
- <input type="submit" value="submit" />
- </form>
- </div>
- {%- endif %}
</div>
<script>
function showDetails(event) {
var songElement = event.target.closest(".song");
-
- for (const child of songElement.children) {
- if (child.classList.contains("song-details")) {
- if (child.hidden) {
- // Show details
- child.hidden = false;
- event.target.alt = "Hide Details";
- event.target.className = "lsp_btn_hide02";
- event.target.src = customImage(document.getElementById("lsp_btn_hide02"));
- }
- else {
- // Hide details
- child.hidden = true;
- event.target.alt = "Show Details";
- event.target.className = "lsp_btn_show02";
- event.target.src = customImage(document.getElementById("lsp_btn_show02"));
- }
- }
+ var songDetails = songElement.querySelector(".song-details");
+ if (songDetails.hidden) {
+ // Show details
+ songDetails.hidden = false;
+ event.target.alt = "Hide Details";
+ event.target.className = "lsp_btn_hide02";
+ event.target.src = customImage(document.getElementById("lsp_btn_hide02"));
+ }
+ else {
+ // Hide details
+ songDetails.hidden = true;
+ event.target.alt = "Show Details";
+ event.target.className = "lsp_btn_show02";
+ event.target.src = customImage(document.getElementById("lsp_btn_show02"));
}
return false;
}
-
-{% if current_user_playlists %}
-var m_addToPlaylistSongid = null;
-function showPlaylistSelector(event, songid) {
- m_addToPlaylistSongid = songid;
- var songList = event.target.closest(".song-list");
- var playlistSelector = songList.querySelector(".playlist-selector");
- playlistSelector.hidden = false;
- var songidInput = playlistSelector.querySelector("#playlist-selector-songid")
- songidInput.value = songid
- return false;
-}
-{% endif %}
</script>