diff --git a/src/css/main.css b/src/css/main.css
index 0c8b64e..3da5f60 100644
--- a/src/css/main.css
+++ b/src/css/main.css
@@ -51,6 +51,16 @@ output {
display: none;
}
+/* Bag Info */
+
+.bag-info :invalid {
+ border-color: red;
+}
+
+.bag-info td:last-child {
+ width: 50px;
+}
+
/* File Selection */
.dropzone {
diff --git a/src/index.html b/src/index.html
index 0a447c9..98bad07 100644
--- a/src/index.html
+++ b/src/index.html
@@ -131,7 +131,102 @@
Create a bag
- Bag Info coming soon
+
diff --git a/src/js/BagInfo.js b/src/js/BagInfo.js
new file mode 100644
index 0000000..09b44ea
--- /dev/null
+++ b/src/js/BagInfo.js
@@ -0,0 +1,67 @@
+/*
+ UI for editing bag-info.txt values
+*/
+
+export default class BagInfo {
+ constructor(container) {
+ this.container = container;
+
+ this.elementBody = container.querySelector("tbody");
+
+ this.elementTemplate = container.querySelector(
+ "template#bag-info-element-template"
+ );
+
+ this.addElement(
+ "Bagging-Date",
+ new Date().toISOString().substring(0, 10)
+ );
+
+ container
+ .querySelector(".add-element .dropdown-menu")
+ .addEventListener("click", evt => {
+ if ("label" in evt.target.dataset) {
+ this.addElement(evt.target.dataset.label);
+ return false;
+ }
+ });
+
+ this.elementBody.addEventListener("click", evt => {
+ if (evt.target.classList.contains("delete-row")) {
+ evt.target.parentNode.parentNode.remove();
+ }
+ });
+ }
+
+ addElement(label, value) {
+ label = (label || "").trim();
+ value = (value || "").trim();
+
+ let entryTemplate = document.importNode(
+ this.elementTemplate.content,
+ true
+ );
+
+ entryTemplate.querySelector(".bag-info-label").value = label;
+ entryTemplate.querySelector(".bag-info-value").value = value;
+
+ this.elementBody.appendChild(entryTemplate);
+ }
+
+ getValues() {
+ // This is not a Map because bag-info.txt allows multiple values for the same label and we must preserve ordering:
+ let info = [];
+ this.container.querySelectorAll("tbody tr").forEach(row => {
+ let labelInput = row.querySelector(
+ "input.bag-info-label:not(:invalid)"
+ );
+ let valueInput = row.querySelector(
+ "input.bag-info-value:not(:invalid)"
+ );
+ if (labelInput && valueInput) {
+ info.push([labelInput.value, valueInput.value]);
+ }
+ });
+ return info;
+ }
+}
diff --git a/src/js/Bagger.js b/src/js/Bagger.js
index c45d395..4d35511 100644
--- a/src/js/Bagger.js
+++ b/src/js/Bagger.js
@@ -2,6 +2,7 @@
import {$} from "./utils.js";
import BagEntry from "./BagEntry.js";
+import BagInfo from "./BagInfo.js";
import Dashboard from "./Dashboard.js";
import SelectFiles from "./SelectFiles.js";
import StorageManager from "./StorageManager.js";
@@ -15,6 +16,8 @@ export default class Bagger {
// manifests after a successful upload:
this.bagEntries = new Map();
+ this.bagInfo = new BagInfo($(".bag-info", elem));
+
this.dashboard = new Dashboard($(".dashboard", elem));
this.storage = new StorageManager($(".server-info", elem), status => {
@@ -389,10 +392,13 @@ export default class Bagger {
);
}
- // FIXME: Bag Info UI — https://github.com/LibraryOfCongress/bagger-js/issues/13
let bagInfo = "Bag-Size: " + filesize(totalBytes, {round: 1});
bagInfo += "\nPayload-Oxum: " + totalBytes + "." + totalFiles + "\n";
+ this.bagInfo.getValues().forEach(([label, value]) => {
+ bagInfo += `${label}: ${value}\n`;
+ });
+
let bagIt = "BagIt-Version: 1.0\nTag-File-Character-Encoding: UTF-8\n";
// FIXME: implement tag manifests!