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
|
@ -12,8 +12,7 @@
|
|||
hx-swap="beforeend"
|
||||
>
|
||||
<img src="{{$Artist.GetAvatar}}" alt="" width="16" loading="lazy" class="artist-avatar">
|
||||
<span class="artist-name">{{$Artist.Name}}</span>
|
||||
<span class="artist-id">({{$Artist.ID}})</span>
|
||||
<span class="artist-name">{{$Artist.Name}} <span class="artist-id">({{$Artist.ID}})</span></span>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
<input type="checkbox" name="primary" {{if .Primary}}checked{{end}}>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="delete">Delete</button>
|
||||
<a class="delete">Delete</a>
|
||||
</div>
|
||||
</li>
|
||||
{{end}}
|
||||
|
@ -40,6 +40,8 @@
|
|||
</form>
|
||||
|
||||
<script type="module">
|
||||
import { makeMagicList } from "/admin/static/admin.js";
|
||||
|
||||
(() => {
|
||||
const container = document.getElementById("editcredits");
|
||||
const form = document.querySelector("#editcredits form");
|
||||
|
@ -47,39 +49,21 @@
|
|||
const addCreditBtn = document.getElementById("add-credit");
|
||||
const discardBtn = form.querySelector("button#discard");
|
||||
|
||||
function creditFromElement(el) {
|
||||
const artistID = el.dataset.artist;
|
||||
const roleInput = el.querySelector(`input[name="role"]`)
|
||||
const primaryInput = el.querySelector(`input[name="primary"]`)
|
||||
const deleteBtn = el.querySelector("button.delete");
|
||||
|
||||
let credit = {
|
||||
"artist": artistID,
|
||||
"role": roleInput.value,
|
||||
"primary": primaryInput.checked,
|
||||
};
|
||||
|
||||
roleInput.addEventListener("change", () => {
|
||||
credit.role = roleInput.value;
|
||||
});
|
||||
primaryInput.addEventListener("change", () => {
|
||||
credit.primary = primaryInput.checked;
|
||||
});
|
||||
deleteBtn.addEventListener("click", e => {
|
||||
if (!confirm("Are you sure you want to delete " + artistID + "'s credit?")) return;
|
||||
el.remove();
|
||||
credits = credits.filter(credit => credit.artist != artistID);
|
||||
});
|
||||
|
||||
return credit;
|
||||
}
|
||||
|
||||
let credits = [...form.querySelectorAll(".credit")].map(el => creditFromElement(el));
|
||||
makeMagicList(creditList, ".credit");
|
||||
|
||||
creditList.addEventListener("htmx:afterSwap", e => {
|
||||
const el = creditList.children[creditList.children.length - 1];
|
||||
const credit = creditFromElement(el);
|
||||
credits.push(credit);
|
||||
|
||||
const artistID = el.dataset.artist;
|
||||
const deleteBtn = el.querySelector("a.delete");
|
||||
|
||||
deleteBtn.addEventListener("click", e => {
|
||||
if (!confirm("Are you sure you want to delete " + artistID + "'s credit?")) return;
|
||||
el.remove();
|
||||
});
|
||||
|
||||
el.addEventListener("dragstart", () => { el.classList.add("moving") });
|
||||
el.addEventListener("dragend", () => { el.classList.remove("moving") });
|
||||
});
|
||||
|
||||
container.showModal();
|
||||
|
@ -89,12 +73,18 @@
|
|||
});
|
||||
|
||||
form.addEventListener("submit", e => {
|
||||
const credits = [...creditList.querySelectorAll(".credit")].map(el => {
|
||||
return {
|
||||
"artist": el.dataset.artist,
|
||||
"role": el.querySelector(`input[name="role"]`).value,
|
||||
"primary": el.querySelector(`input[name="primary"]`).checked,
|
||||
};
|
||||
});
|
||||
|
||||
e.preventDefault();
|
||||
fetch(form.action, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(credits)
|
||||
}).then(res => {
|
||||
if (res.ok) location = location;
|
||||
|
@ -105,7 +95,7 @@
|
|||
});
|
||||
}
|
||||
}).catch(err => {
|
||||
alert("Failed to update credits. Check the console for details");
|
||||
alert("Failed to update credits. Check the console for details.");
|
||||
console.error(err);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -12,6 +12,6 @@
|
|||
<input type="checkbox" name="primary">
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="delete">Delete</button>
|
||||
<a class="delete">Delete</a>
|
||||
</div>
|
||||
</li>
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
<dialog id="editlinks">
|
||||
<header>
|
||||
<h2>Editing: Links</h2>
|
||||
<button id="add-link" class="button new">Add</button>
|
||||
</header>
|
||||
|
||||
<form action="/api/v1/music/{{.ID}}/links">
|
||||
<table>
|
||||
<tr>
|
||||
<th class="grabber"></th>
|
||||
<th class="link-name">Name</th>
|
||||
<th class="link-url">URL</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
{{range .Links}}
|
||||
<tr class="link">
|
||||
<td class="grabber"><img src="/img/list-grabber.svg"/></td>
|
||||
<td class="link-name">
|
||||
<input type="text" name="name" value="{{.Name}}">
|
||||
</td>
|
||||
<td class="link-url">
|
||||
<input type="text" name="url" value="{{.URL}}">
|
||||
</td>
|
||||
<td>
|
||||
<a class="delete">Delete</a>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</table>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<button id="discard" type="button">Discard</button>
|
||||
<button id="save" type="submit" class="save">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script type="module">
|
||||
import { makeMagicList } from "/admin/static/admin.js";
|
||||
(() => {
|
||||
const container = document.getElementById("editlinks");
|
||||
const form = document.querySelector("#editlinks form");
|
||||
const linkTable = form.querySelector("table tbody");
|
||||
const addLinkBtn = document.getElementById("add-link");
|
||||
const discardBtn = form.querySelector("button#discard");
|
||||
|
||||
makeMagicList(linkTable, "tr.link");
|
||||
|
||||
function rigLinkItem(el) {
|
||||
const nameInput = el.querySelector(`input[name="name"]`)
|
||||
const deleteBtn = el.querySelector("a.delete");
|
||||
|
||||
deleteBtn.addEventListener("click", e => {
|
||||
e.preventDefault();
|
||||
if (nameInput.value != "" &&
|
||||
!confirm("Are you sure you want to delete \"" + nameInput.value + "\"?"))
|
||||
return;
|
||||
el.remove();
|
||||
});
|
||||
}
|
||||
|
||||
[...linkTable.querySelectorAll("tr.link")].map(rigLinkItem);
|
||||
|
||||
addLinkBtn.addEventListener("click", e => {
|
||||
e.preventDefault();
|
||||
const row = document.createElement("tr");
|
||||
row.className = "link";
|
||||
|
||||
const grabberCell = document.createElement("td");
|
||||
grabberCell.className = "grabber";
|
||||
const grabberImg = document.createElement("img");
|
||||
grabberImg.src = "/img/list-grabber.svg";
|
||||
grabberCell.appendChild(grabberImg);
|
||||
row.appendChild(grabberCell);
|
||||
|
||||
const nameCell = document.createElement("td");
|
||||
nameCell.className = "link-name";
|
||||
const nameInput = document.createElement("input");
|
||||
nameInput.type = "text";
|
||||
nameInput.name = "name";
|
||||
nameCell.appendChild(nameInput);
|
||||
row.appendChild(nameCell);
|
||||
|
||||
const urlCell = document.createElement("td");
|
||||
urlCell.className = "link-url";
|
||||
const urlInput = document.createElement("input");
|
||||
urlInput.type = "text";
|
||||
urlInput.name = "url";
|
||||
urlCell.appendChild(urlInput);
|
||||
row.appendChild(urlCell);
|
||||
|
||||
const deleteCell = document.createElement("td");
|
||||
const deleteBtn = document.createElement("a");
|
||||
deleteBtn.className = "delete";
|
||||
deleteBtn.innerText = "Delete";
|
||||
deleteCell.appendChild(deleteBtn);
|
||||
row.appendChild(deleteCell);
|
||||
|
||||
linkTable.appendChild(row);
|
||||
|
||||
row.draggable = true;
|
||||
row.addEventListener("dragstart", () => { row.classList.add("moving") });
|
||||
row.addEventListener("dragend", () => { row.classList.remove("moving") });
|
||||
row.querySelectorAll("input").forEach(el => {
|
||||
el.addEventListener("mousedown", () => { row.draggable = false });
|
||||
el.addEventListener("mouseup", () => { row.draggable = true });
|
||||
el.addEventListener("dragstart", e => { e.stopPropagation() });
|
||||
});
|
||||
|
||||
deleteBtn.addEventListener("click", e => {
|
||||
e.preventDefault();
|
||||
if (nameInput.value != "" && !confirm("Are you sure you want to delete \"" + nameInput.value + "\"?")) return;
|
||||
row.remove();
|
||||
});
|
||||
});
|
||||
|
||||
container.showModal();
|
||||
|
||||
container.addEventListener("close", () => {
|
||||
container.remove();
|
||||
});
|
||||
|
||||
form.addEventListener("submit", e => {
|
||||
var links = [];
|
||||
[...linkTable.querySelectorAll("tr.link")].map(el => {
|
||||
const name = el.querySelector(`input[name="name"]`).value;
|
||||
const url = el.querySelector(`input[name="url"]`).value;
|
||||
if (name == "" || url == "") return;
|
||||
links.push({
|
||||
"name": name,
|
||||
"url": url,
|
||||
});
|
||||
})
|
||||
|
||||
e.preventDefault();
|
||||
fetch(form.action, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(links)
|
||||
}).then(res => {
|
||||
if (res.ok) location = location;
|
||||
else {
|
||||
res.text().then(err => {
|
||||
alert(err);
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
}).catch(err => {
|
||||
alert("Failed to update links. Check the console for details.");
|
||||
console.error(err);
|
||||
});
|
||||
});
|
||||
|
||||
discardBtn.addEventListener("click", e => {
|
||||
e.preventDefault();
|
||||
container.close();
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</dialog>
|
|
@ -0,0 +1,47 @@
|
|||
<dialog id="addtrack">
|
||||
<header>
|
||||
<h2>Add track</h2>
|
||||
</header>
|
||||
|
||||
<ul>
|
||||
{{range $Track := .Tracks}}
|
||||
</li>
|
||||
<li class="new-track"
|
||||
data-id="{{$Track.ID}}"
|
||||
hx-get="/admin/release/{{$.ReleaseID}}/newtrack/{{$Track.ID}}"
|
||||
hx-target="#edittracks ul"
|
||||
hx-swap="beforeend"
|
||||
>
|
||||
{{.Title}}
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
|
||||
{{if not .Tracks}}
|
||||
<p class="empty">There are no more tracks to add.</p>
|
||||
{{end}}
|
||||
|
||||
<div class="dialog-actions">
|
||||
<button id="cancel" type="button">Cancel</button>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
(() => {
|
||||
const newTrackModal = document.getElementById("addtrack")
|
||||
const editTracksModal = document.getElementById("edittracks")
|
||||
const cancelBtn = newTrackModal.querySelector("#cancel");
|
||||
|
||||
editTracksModal.addEventListener("htmx:afterSwap", () => {
|
||||
newTrackModal.close();
|
||||
newTrackModal.remove();
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener("click", () => {
|
||||
newTrackModal.close();
|
||||
newTrackModal.remove();
|
||||
});
|
||||
|
||||
newTrackModal.showModal();
|
||||
})();
|
||||
</script>
|
||||
</dialog>
|
|
@ -0,0 +1,112 @@
|
|||
<dialog id="edittracks">
|
||||
<header>
|
||||
<h2>Editing: Tracks</h2>
|
||||
<a id="add-track"
|
||||
class="button new"
|
||||
href="/admin/release/{{.ID}}/addtrack"
|
||||
hx-get="/admin/release/{{.ID}}/addtrack"
|
||||
hx-target="body"
|
||||
hx-swap="beforeend"
|
||||
>Add</a>
|
||||
</header>
|
||||
|
||||
<form action="/api/v1/music/{{.ID}}/tracks">
|
||||
<ul>
|
||||
{{range .Tracks}}
|
||||
<li class="track" data-track="{{.ID}}" data-title="{{.Title}}" data-number="{{.Number}}" draggable="true">
|
||||
<div>
|
||||
<p class="track-name">
|
||||
<span class="track-number">{{.Number}}</span>
|
||||
{{.Title}}
|
||||
</p>
|
||||
<a class="delete">Delete</a>
|
||||
</div>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<button id="discard" type="button">Discard</button>
|
||||
<button id="save" type="submit" class="save">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script type="module">
|
||||
import { makeMagicList } from "/admin/static/admin.js";
|
||||
(() => {
|
||||
const container = document.getElementById("edittracks");
|
||||
const form = document.querySelector("#edittracks form");
|
||||
const trackList = form.querySelector("ul");
|
||||
const addTrackBtn = document.getElementById("add-track");
|
||||
const discardBtn = form.querySelector("button#discard");
|
||||
|
||||
makeMagicList(trackList, ".track", refreshTrackNumbers);
|
||||
|
||||
function rigTrackItem(trackItem) {
|
||||
const trackID = trackItem.dataset.track;
|
||||
const trackTitle = trackItem.dataset.title;
|
||||
const deleteBtn = trackItem.querySelector("a.delete");
|
||||
|
||||
deleteBtn.addEventListener("click", e => {
|
||||
e.preventDefault();
|
||||
if (!confirm("Are you sure you want to remove " + trackTitle + "?")) return;
|
||||
trackItem.remove();
|
||||
refreshTrackNumbers();
|
||||
});
|
||||
}
|
||||
|
||||
function refreshTrackNumbers() {
|
||||
trackList.querySelectorAll("li").forEach((trackItem, i) => {
|
||||
trackItem.querySelector(".track-number").innerText = i + 1;
|
||||
});
|
||||
}
|
||||
|
||||
trackList.addEventListener("htmx:afterSwap", e => {
|
||||
const trackItem = trackList.children[trackList.children.length - 1];
|
||||
trackList.appendChild(trackItem);
|
||||
trackItem.addEventListener("dragstart", () => { trackItem.classList.add("moving") });
|
||||
trackItem.addEventListener("dragend", () => { trackItem.classList.remove("moving") });
|
||||
rigTrackItem(trackItem);
|
||||
refreshTrackNumbers();
|
||||
});
|
||||
|
||||
trackList.querySelectorAll("li").forEach(trackItem => {
|
||||
rigTrackItem(trackItem);
|
||||
});
|
||||
|
||||
container.showModal();
|
||||
|
||||
container.addEventListener("close", () => {
|
||||
container.remove();
|
||||
});
|
||||
|
||||
form.addEventListener("submit", e => {
|
||||
e.preventDefault();
|
||||
|
||||
let tracks = [...trackList.querySelectorAll(".track")].map(trackItem => trackItem.dataset.track);
|
||||
|
||||
fetch(form.action, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(tracks)
|
||||
}).then(res => {
|
||||
if (res.ok) location = location;
|
||||
else {
|
||||
res.text().then(err => {
|
||||
alert(err);
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
}).catch(err => {
|
||||
alert("Failed to update tracks. Check the console for details.");
|
||||
console.error(err);
|
||||
});
|
||||
});
|
||||
|
||||
discardBtn.addEventListener("click", e => {
|
||||
e.preventDefault();
|
||||
container.close();
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</dialog>
|
|
@ -0,0 +1,9 @@
|
|||
<li class="track" data-track="{{.ID}}" data-title="{{.Title}}" data-number="{{.Number}}" draggable="true">
|
||||
<div>
|
||||
<p class="track-name">
|
||||
<span class="track-number">{{.Number}}</span>
|
||||
{{.Title}}
|
||||
</p>
|
||||
<a class="delete">Delete</a>
|
||||
</div>
|
||||
</li>
|
Loading…
Add table
Add a link
Reference in a new issue