full release edit capabilities oh my goodness gracious
Signed-off-by: ari melody <ari@arimelody.me>
This commit is contained in:
parent
34cddcfdb2
commit
604e2a4a7c
25 changed files with 1043 additions and 202 deletions
|
@ -86,7 +86,7 @@ a img {
|
|||
}
|
||||
|
||||
.card {
|
||||
margin-bottom: 2em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
/**
|
||||
* Creates a "magic" reorderable list from `container`.
|
||||
* This function is absolute magic and I love it
|
||||
*
|
||||
* Example:
|
||||
* ```html
|
||||
* <ul id="list">
|
||||
* <li>Item 1</li>
|
||||
* <li>Item 2</li>
|
||||
* <li>Item 3</li>
|
||||
* </ul>
|
||||
* ```
|
||||
* ```js
|
||||
* // javascript
|
||||
* makeMagicList(document.getElementById("list"), "li");
|
||||
* ```
|
||||
*
|
||||
* @param {HTMLElement} container The parent container to use as a list.
|
||||
* @param {string} itemSelector The selector name of list item elements.
|
||||
* @param {Function} callback A function to call after each reordering.
|
||||
*/
|
||||
export function makeMagicList(container, itemSelector, callback) {
|
||||
if (!container)
|
||||
throw new Error("container not provided");
|
||||
if (!itemSelector)
|
||||
throw new Error("itemSelector not provided");
|
||||
|
||||
container.querySelectorAll(itemSelector).forEach(item => {
|
||||
item.draggable = true;
|
||||
item.addEventListener("dragstart", () => { item.classList.add("moving") });
|
||||
item.addEventListener("dragend", () => { item.classList.remove("moving") });
|
||||
item.querySelectorAll("input").forEach(el => {
|
||||
el.addEventListener("mousedown", () => { item.draggable = false });
|
||||
el.addEventListener("mouseup", () => { item.draggable = true });
|
||||
el.addEventListener("dragstart", e => { e.stopPropagation() });
|
||||
});
|
||||
});
|
||||
|
||||
var lastCursorY;
|
||||
container.addEventListener("dragover", event => {
|
||||
const dragging = container.querySelector(itemSelector + ".moving");
|
||||
if (!dragging) return;
|
||||
|
||||
let cursorY = event.touches ? event.touches[0].clientY : event.clientY;
|
||||
|
||||
// don't bother processing if we haven't moved
|
||||
if (lastCursorY === cursorY) return
|
||||
lastCursorY = cursorY;
|
||||
|
||||
// get the element positioned ahead of the cursor
|
||||
const notMoving = [...container.querySelectorAll(itemSelector + ":not(.moving)")];
|
||||
const afterElement = notMoving.reduce((previous, current) => {
|
||||
const box = current.getBoundingClientRect();
|
||||
const offset = cursorY - box.top - box.height / 2;
|
||||
if (offset < 0 && offset > previous.offset)
|
||||
return { offset: offset, element: current };
|
||||
return previous;
|
||||
}, { offset: Number.NEGATIVE_INFINITY }).element;
|
||||
|
||||
if (afterElement) {
|
||||
container.insertBefore(dragging, afterElement);
|
||||
} else {
|
||||
container.appendChild(dragging);
|
||||
}
|
||||
|
||||
if (callback) callback();
|
||||
});
|
||||
}
|
|
@ -1,3 +1,9 @@
|
|||
input[type="text"] {
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
#release {
|
||||
margin-bottom: 1em;
|
||||
padding: 1.5em;
|
||||
|
@ -12,10 +18,6 @@
|
|||
|
||||
.release-artwork {
|
||||
width: 200px;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.release-artwork img {
|
||||
|
@ -28,6 +30,7 @@
|
|||
}
|
||||
|
||||
.release-info {
|
||||
width: 0;
|
||||
margin: 0;
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
|
@ -38,6 +41,28 @@
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
#title {
|
||||
width: 100%;
|
||||
margin: 0 -.2em;
|
||||
padding: 0 .2em;
|
||||
font-weight: bold;
|
||||
border-radius: 4px;
|
||||
border: 1px solid transparent;
|
||||
background: transparent;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#title:hover {
|
||||
background: #ffffff;
|
||||
border-color: #80808080;
|
||||
}
|
||||
|
||||
#title:active,
|
||||
#title:focus {
|
||||
background: #ffffff;
|
||||
border-color: #808080;
|
||||
}
|
||||
|
||||
.release-title small {
|
||||
opacity: .75;
|
||||
}
|
||||
|
@ -71,6 +96,7 @@
|
|||
border: none;
|
||||
background: none;
|
||||
outline: none;
|
||||
resize: vertical;
|
||||
}
|
||||
.release-info table td:has(select),
|
||||
.release-info table td:has(input),
|
||||
|
@ -126,6 +152,10 @@ button[disabled] {
|
|||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
a.delete {
|
||||
color: #d22828;
|
||||
}
|
||||
|
||||
.release-actions {
|
||||
margin-top: auto;
|
||||
display: flex;
|
||||
|
@ -134,90 +164,6 @@ button[disabled] {
|
|||
justify-content: right;
|
||||
}
|
||||
|
||||
.card.credits .credit {
|
||||
margin-bottom: .5em;
|
||||
padding: .5em;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 1em;
|
||||
|
||||
border-radius: .5em;
|
||||
background: #f8f8f8f8;
|
||||
border: 1px solid #808080;
|
||||
}
|
||||
|
||||
.card.credits .credit .artist-avatar {
|
||||
border-radius: .5em;
|
||||
}
|
||||
|
||||
.card.credits .credit .artist-name {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.card.credits .credit .artist-role small {
|
||||
font-size: inherit;
|
||||
opacity: .66;
|
||||
}
|
||||
|
||||
.track {
|
||||
margin-bottom: 1em;
|
||||
padding: 1em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .5em;
|
||||
|
||||
border-radius: .5em;
|
||||
background: #f8f8f8f8;
|
||||
border: 1px solid #808080;
|
||||
}
|
||||
|
||||
.card h2.track-title {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.card-title a.button {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.track-id {
|
||||
width: fit-content;
|
||||
font-family: "Monaspace Argon", monospace;
|
||||
font-size: .8em;
|
||||
font-style: italic;
|
||||
line-height: 1em;
|
||||
user-select: all;
|
||||
-webkit-user-select: all;
|
||||
}
|
||||
|
||||
.track-album {
|
||||
margin-left: auto;
|
||||
font-style: italic;
|
||||
font-size: .75em;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.track-album.empty {
|
||||
color: #ff2020;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.track-description {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.track-lyrics {
|
||||
max-height: 10em;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.track .empty {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
dialog {
|
||||
width: min(720px, calc(100% - 2em));
|
||||
padding: 2em;
|
||||
|
@ -245,13 +191,15 @@ dialog div.dialog-actions {
|
|||
gap: .5em;
|
||||
}
|
||||
|
||||
dialog#editcredits ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
.card-title a.button {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
dialog#editcredits .credit>div {
|
||||
/*
|
||||
* RELEASE CREDITS
|
||||
*/
|
||||
|
||||
.card.credits .credit {
|
||||
margin-bottom: .5em;
|
||||
padding: .5em;
|
||||
display: flex;
|
||||
|
@ -264,24 +212,70 @@ dialog#editcredits .credit>div {
|
|||
border: 1px solid #808080;
|
||||
}
|
||||
|
||||
dialog#editcredits .credit p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
dialog#editcredits .credit .artist-avatar {
|
||||
.card.credits .credit .artist-avatar {
|
||||
border-radius: .5em;
|
||||
}
|
||||
|
||||
dialog#editcredits .credit .credit-info {
|
||||
.card.credits .credit .artist-name {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.card.credits .credit .artist-role small {
|
||||
font-size: inherit;
|
||||
opacity: .66;
|
||||
}
|
||||
|
||||
#editcredits ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
#editcredits .credit>div {
|
||||
margin-bottom: .5em;
|
||||
padding: .5em;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 1em;
|
||||
|
||||
border-radius: .5em;
|
||||
background: #f8f8f8f8;
|
||||
border: 1px solid #808080;
|
||||
}
|
||||
|
||||
#editcredits .credit {
|
||||
transition: transform .2s ease-out, opacity .2s;
|
||||
}
|
||||
|
||||
#editcredits .credit.moving {
|
||||
transform: scale(1.05);
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
#editcredits .credit p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#editcredits .credit .artist-avatar {
|
||||
border-radius: .5em;
|
||||
}
|
||||
|
||||
#editcredits .credit .credit-info {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
dialog#editcredits .credit .credit-info .credit-attribute {
|
||||
#editcredits .credit .credit-info .credit-attribute {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
dialog#editcredits .credit .credit-info .credit-attribute input[type="text"] {
|
||||
#editcredits .credit .credit-info .credit-attribute label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#editcredits .credit .credit-info .credit-attribute input[type="text"] {
|
||||
margin-left: .25em;
|
||||
padding: .2em .4em;
|
||||
flex-grow: 1;
|
||||
|
@ -291,15 +285,255 @@ dialog#editcredits .credit .credit-info .credit-attribute input[type="text"] {
|
|||
color: inherit;
|
||||
}
|
||||
|
||||
dialog#editcredits .credit .artist-name {
|
||||
#editcredits .credit .artist-name {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
dialog#editcredits .credit .artist-role small {
|
||||
#editcredits .credit .artist-role small {
|
||||
font-size: inherit;
|
||||
opacity: .66;
|
||||
}
|
||||
|
||||
dialog#editcredits .credit button.delete {
|
||||
#editcredits .credit button.delete {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
#addcredit ul {
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
background: #f8f8f8;
|
||||
}
|
||||
|
||||
#addcredit ul li.new-artist {
|
||||
padding: .5em;
|
||||
display: flex;
|
||||
gap: .5em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#addcredit ul li.new-artist:nth-child(even) {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
#addcredit ul li.new-artist:hover {
|
||||
background: #e0e0e0;
|
||||
}
|
||||
|
||||
#addcredit .new-artist .artist-id {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
/*
|
||||
* RELEASE LINKS
|
||||
*/
|
||||
|
||||
.card.links {
|
||||
display: flex;
|
||||
gap: .2em;
|
||||
}
|
||||
|
||||
.card.links a.button[data-name="spotify"] {
|
||||
background-color: #8cff83
|
||||
}
|
||||
|
||||
.card.links a.button[data-name="applemusic"] {
|
||||
background-color: #8cd9ff
|
||||
}
|
||||
|
||||
.card.links a.button[data-name="soundcloud"] {
|
||||
background-color: #fdaa6d
|
||||
}
|
||||
|
||||
.card.links a.button[data-name="youtube"] {
|
||||
background-color: #ff6e6e
|
||||
}
|
||||
|
||||
#editlinks table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#editlinks tr {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#editlinks th {
|
||||
padding: 0 .1em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#editlinks tr:nth-child(odd) {
|
||||
background: #f8f8f8;
|
||||
}
|
||||
|
||||
#editlinks tr th,
|
||||
#editlinks tr td {
|
||||
height: 2em;
|
||||
}
|
||||
|
||||
#editlinks tr td {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#editlinks tr.link {
|
||||
transition: transform .2s ease-out, opacity .2s;
|
||||
}
|
||||
|
||||
#editlinks tr.link.moving {
|
||||
transform: scale(1.05);
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
#editlinks tr .grabber {
|
||||
width: 2em;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
#editlinks tr .grabber img {
|
||||
width: 1em;
|
||||
pointer-events: none;
|
||||
}
|
||||
#editlinks tr .link-name {
|
||||
width: 8em;
|
||||
}
|
||||
#editlinks tr .link-url {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
#editlinks td a.delete {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
padding: 0 .5em;
|
||||
}
|
||||
|
||||
#editlinks td input[type="text"] {
|
||||
width: calc(100% - .6em);
|
||||
height: 100%;
|
||||
padding: 0 .3em;
|
||||
border: none;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
}
|
||||
#editlinks td input[type="text"]:hover {
|
||||
background: #0001;
|
||||
}
|
||||
#editlinks td input[type="text"]:focus {
|
||||
outline: 1px solid #808080;
|
||||
}
|
||||
|
||||
/*
|
||||
* RELEASE TRACKS
|
||||
*/
|
||||
|
||||
.card.tracks .track {
|
||||
margin-bottom: 1em;
|
||||
padding: 1em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .5em;
|
||||
|
||||
border-radius: .5em;
|
||||
background: #f8f8f8f8;
|
||||
border: 1px solid #808080;
|
||||
}
|
||||
|
||||
.card.tracks h2.track-title {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
gap: .5em;
|
||||
}
|
||||
|
||||
.card.tracks h2.track-title .track-number {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.card.tracks .track-album {
|
||||
margin-left: auto;
|
||||
font-style: italic;
|
||||
font-size: .75em;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.card.tracks .track-album.empty {
|
||||
color: #ff2020;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.card.tracks .track-description {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.card.tracks .track-lyrics {
|
||||
max-height: 10em;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.card.tracks .track .empty {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
#edittracks ul {
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
#edittracks .track {
|
||||
transition: transform .2s ease-out, opacity .2s;
|
||||
}
|
||||
|
||||
#edittracks .track.moving {
|
||||
transform: scale(1.05);
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
#edittracks .track div {
|
||||
padding: .5em;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#edittracks .track div:active {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
#edittracks .track:nth-child(even) {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
#edittracks .track-number {
|
||||
min-width: 1em;
|
||||
display: inline-block;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
#edittracks .track-name {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#addtrack ul {
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
background: #f8f8f8;
|
||||
}
|
||||
|
||||
#addtrack ul li.new-track {
|
||||
padding: .5em;
|
||||
display: flex;
|
||||
gap: .5em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#addtrack ul li.new-track:nth-child(even) {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
#addtrack ul li.new-track:hover {
|
||||
background: #e0e0e0;
|
||||
}
|
|
@ -1,7 +1,9 @@
|
|||
import Stateful from "/script/silver.min.js"
|
||||
|
||||
const releaseID = document.getElementById("release").dataset.id;
|
||||
const artwork_input = document.getElementById("artwork");
|
||||
const title_input = document.getElementById("title");
|
||||
const artwork_img = document.getElementById("artwork");
|
||||
const artwork_input = document.getElementById("artwork-file");
|
||||
const type_input = document.getElementById("type");
|
||||
const desc_input = document.getElementById("description");
|
||||
const date_input = document.getElementById("release-date");
|
||||
|
@ -10,20 +12,22 @@ const buylink_input = document.getElementById("buylink");
|
|||
const vis_input = document.getElementById("visibility");
|
||||
const save_btn = document.getElementById("save");
|
||||
|
||||
let token = atob(localStorage.getItem("arime-token"));
|
||||
var artwork_data = artwork_img.attributes.src.value;
|
||||
|
||||
let edited = new Stateful(false);
|
||||
var token = atob(localStorage.getItem("arime-token"));
|
||||
|
||||
let release_data = update_data(undefined);
|
||||
var edited = new Stateful(false);
|
||||
|
||||
var release_data = update_data(undefined);
|
||||
|
||||
function update_data(old) {
|
||||
let release_data = {
|
||||
var release_data = {
|
||||
visible: vis_input.value === "true",
|
||||
title: undefined,
|
||||
title: title_input.value,
|
||||
description: desc_input.value,
|
||||
type: type_input.value,
|
||||
releaseDate: date_input.value,
|
||||
artwork: artwork_input.attributes.src.value,
|
||||
artwork: artwork_data,
|
||||
buyname: buyname_input.value,
|
||||
buylink: buylink_input.value,
|
||||
};
|
||||
|
@ -38,8 +42,6 @@ function update_data(old) {
|
|||
function save_release() {
|
||||
console.table(release_data);
|
||||
|
||||
edited.set(false);
|
||||
|
||||
(async () => {
|
||||
const res = await fetch(
|
||||
"/api/v1/music/" + releaseID, {
|
||||
|
@ -61,15 +63,29 @@ function save_release() {
|
|||
location = location;
|
||||
})();
|
||||
}
|
||||
window.save_release = save_release;
|
||||
|
||||
edited.onUpdate(edited => {
|
||||
save_btn.disabled = !edited;
|
||||
})
|
||||
|
||||
artwork_input.addEventListener("click", () => {
|
||||
title_input.addEventListener("change", () => {
|
||||
release_data = update_data(release_data);
|
||||
});
|
||||
artwork_img.addEventListener("click", () => {
|
||||
artwork_input.addEventListener("change", () => {
|
||||
if (artwork_input.files.length > 0) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = e => {
|
||||
const data = e.target.result;
|
||||
artwork_img.src = data;
|
||||
artwork_data = data;
|
||||
release_data = update_data(release_data);
|
||||
};
|
||||
reader.readAsDataURL(artwork_input.files[0]);
|
||||
}
|
||||
});
|
||||
artwork_input.click();
|
||||
});
|
||||
type_input.addEventListener("change", () => {
|
||||
release_data = update_data(release_data);
|
||||
});
|
||||
|
|
24
admin/static/index.js
Normal file
24
admin/static/index.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
const newReleaseBtn = document.getElementById("create-release");
|
||||
|
||||
newReleaseBtn.addEventListener("click", event => {
|
||||
event.preventDefault();
|
||||
const id = prompt("Enter an ID for this release:");
|
||||
if (id == null || id == "") return;
|
||||
|
||||
fetch("/api/v1/music", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({id})
|
||||
}).then(res => {
|
||||
if (res.ok) location = "/admin/release/" + id;
|
||||
else {
|
||||
res.text().then(err => {
|
||||
alert("Request failed: " + err);
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
}).catch(err => {
|
||||
alert("Failed to create release. Check the console for details.");
|
||||
console.error(err);
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue