Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/145 import export cart #195

Merged
merged 28 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
ac17acd
move download file function to utilits
sabjorn Nov 10, 2024
742a792
move dateString function to utilities
sabjorn Nov 10, 2024
5612947
update date format for cart and downloads to be yy-mm-dd
sabjorn Nov 10, 2024
7d7e86a
add cart export directly to 'player' class -- might need own page bec…
sabjorn Nov 10, 2024
261544c
move re-usable static method to component and re-name component to be…
sabjorn Nov 10, 2024
56e7111
update download_helper and tests for previous changes
sabjorn Nov 10, 2024
ba22ffd
fix potential failure mode of Player and add test
sabjorn Nov 10, 2024
8e6d353
create new page object -- Cart -- and move code from Player into it
sabjorn Nov 10, 2024
82722c0
first pass at import works
sabjorn Nov 10, 2024
c8b1647
update Cart import to place items in cart as they are completed
sabjorn Nov 10, 2024
f94c733
addm observer to prevent 'export' button from being available when ca…
sabjorn Nov 10, 2024
2048e10
update cart refresh button to only exist if cart has been modified
sabjorn Nov 10, 2024
e3ddb5b
add small error catch
sabjorn Nov 10, 2024
6a1a3cb
make import button appear at top of cart
sabjorn Nov 17, 2024
61cdcf0
add test for adding buttons in cart.init()
sabjorn Nov 17, 2024
f3d8eef
add tests for callback for 'export' button
sabjorn Nov 18, 2024
3ab1894
fix test to make sure empty cart items
sabjorn Nov 18, 2024
960c5dd
add test for when export with non-album to track type. fix implementa…
sabjorn Nov 18, 2024
f4ba603
update cart implemenation to wrap window.refresh in mockable function
sabjorn Nov 20, 2024
3c221f6
finish tests for import button callback
sabjorn Nov 20, 2024
eaa5cd5
finish test for when no data in json file -- also fixup Cart implemen…
sabjorn Nov 20, 2024
77efca8
fix cart logging issue
sabjorn Nov 20, 2024
759b122
write tests for reload button and change implementation to use this v…
sabjorn Nov 20, 2024
a396d2a
implement test for observer AND fix observer implementation based on …
sabjorn Nov 20, 2024
cfa6fa1
add test for changes to player
sabjorn Nov 20, 2024
05786cf
fix formatting in test
sabjorn Nov 20, 2024
8547ed6
fix formatting in test
sabjorn Nov 20, 2024
a5ad11a
fix formatting in test
sabjorn Nov 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/components/inputButtonPair.js → src/components/buttons.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,14 @@ export function createInputButtonPair(options = {}) {

return container;
}

export function createButton(options = {}) {
const { className, innerText, buttonClicked } = options;

const button = document.createElement("a");
button.className = className;
button.innerText = innerText;
button.addEventListener("click", buttonClicked);

return button;
}
11 changes: 0 additions & 11 deletions src/components/shoppingCart.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,3 @@ export function createShoppingCartItem(options = {}) {

return itemContainer;
}

export function createShoppingCartResetButton(options = {}) {
const { className, innerText, buttonClicked } = options;

const cartRefreshButton = document.createElement("a");
cartRefreshButton.className = className;
cartRefreshButton.innerText = innerText;
cartRefreshButton.addEventListener("click", buttonClicked);

return cartRefreshButton;
}
40 changes: 7 additions & 33 deletions src/download_helper.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import Logger from "./logger";

import { downloadFile, dateString } from "./utilities";

const preamble = `#!/usr/bin/env bash
# Generated by Bandcamp Enhancement Suite (https://github.com/sabjorn/BandcampEnhancementSuite)
#
Expand All @@ -17,6 +19,10 @@ export default class DownloadHelper {
this.mutationCallback = DownloadHelper.callback.bind(this); // necessary for class callback
this.observer = new MutationObserver(this.mutationCallback);

// re-import
DownloadHelper.dateString = dateString;
DownloadHelper.downloadFile = downloadFile;

this.linksReady;
this.button;
}
Expand Down Expand Up @@ -65,7 +71,7 @@ export default class DownloadHelper {
const preamble = DownloadHelper.getDownloadPreamble();
const downloadDocument = preamble + downloadList;

DownloadHelper.download(`bandcamp_${date}.txt`, downloadDocument);
DownloadHelper.downloadFile(`bandcamp_${date}.txt`, downloadDocument);
});
}

Expand All @@ -91,38 +97,6 @@ export default class DownloadHelper {
return filelist;
}

static download(filename, text) {
var element = document.createElement("a");

element.setAttribute(
"href",
"data:text/plain;charset=utf-8," + encodeURIComponent(text)
);
element.setAttribute("download", filename);

element.style.display = "none";
document.body.appendChild(element);

element.click();

document.body.removeChild(element);
}

static dateString() {
const currentdate = new Date();
const ye = new Intl.DateTimeFormat("en", { year: "2-digit" }).format(
currentdate
);
const mo = new Intl.DateTimeFormat("en", { month: "2-digit" }).format(
currentdate
);
const da = new Intl.DateTimeFormat("en", { day: "2-digit" }).format(
currentdate
);

return `${da}-${mo}-${ye}`;
}

static callback() {
let allDownloadLinks = document.querySelectorAll(
".download-title .item-button"
Expand Down
9 changes: 9 additions & 0 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import DownloadHelper from "./download_helper.js";
import Player from "./player.js";
import AudioFeatures from "./audioFeatures.js";
import Checkout from "./checkout.js";
import Cart from "./pages/cart";

const main = () => {
const log = new Logger();
Expand Down Expand Up @@ -45,6 +46,14 @@ const main = () => {
let checkout = new Checkout(config_port);
checkout.init();
}

const { has_cart } = JSON.parse(
document.querySelector("[data-blob]").getAttribute("data-blob")
);
if (has_cart) {
const cart = new Cart();
cart.init();
}
};

main();
144 changes: 144 additions & 0 deletions src/pages/cart.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import Logger from "../logger";

import { createButton } from "../components/buttons.js";
import {
downloadFile,
dateString,
loadJsonFile,
addAlbumToCart
} from "../utilities";
import { createShoppingCartItem } from "../components/shoppingCart.js";

export default class Cart {
constructor() {
this.log = new Logger();

// re-import
this.createButton = createButton;
this.loadJsonFile = loadJsonFile;
this.addAlbumToCart = addAlbumToCart;
this.createShoppingCartItem = createShoppingCartItem;
this.downloadFile = downloadFile;
this.reloadWindow = () => location.reload();
}

init() {
this.log.info("cart init");

const importCartButton = this.createButton({
className: "buttonLink",
innerText: "import",
buttonClicked: async () => {
try {
const { tracks_export } = await this.loadJsonFile();
const promises = tracks_export.map(track =>
this.addAlbumToCart(
track.item_id,
track.unit_price,
track.item_type
).then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}

const cartItem = this.createShoppingCartItem({
itemId: track.item_id,
itemName: track.item_title,
itemPrice: track.unit_price,
itemCurrency: track.currency
});

document.querySelector("#item_list").append(cartItem);
})
);

await Promise.all(promises).then(results => {
if (!results || results.length < 1) {
return;
}
this.reloadWindow();
});
} catch (error) {
this.log.error("Error loading JSON:", error);
}
}
});
document.querySelector("#sidecartReveal").prepend(importCartButton);

const exportCartButton = this.createButton({
className: "buttonLink",
innerText: "export",
buttonClicked: () => {
const { items } = JSON.parse(
document.querySelector("[data-cart]").getAttribute("data-cart")
);
if (items.length < 1) {
this.log.error("error trying to export cart with length of 0");
return;
}

const cart_id = items[0].cart_id;
const date = dateString();
const tracks_export = items
.filter(item => item.item_type === "a" || item.item_type === "t")
.map(
({
band_name,
item_id,
item_title,
unit_price,
url,
currency,
item_type
}) => ({
band_name,
item_id,
item_title,
unit_price,
url,
currency,
item_type
})
);
if (tracks_export.length < 1) return;

const filename = `${date}_${cart_id}_bes_cart_export.json`;
const data = JSON.stringify({ date, cart_id, tracks_export }, null, 2);
this.downloadFile(filename, data);
}
});
document.querySelector("#sidecartReveal").append(exportCartButton);

const cartRefreshButton = this.createButton({
className: "buttonLink",
innerText: "⟳",
buttonClicked: () => this.reloadWindow()
});
cartRefreshButton.style.display = "none";
document.querySelector("#sidecartReveal").append(cartRefreshButton);

const observer = new MutationObserver(() => {
const item_list = document.querySelectorAll("#item_list .item");
const cartDataElement = document.querySelector("[data-cart]");

if (!cartDataElement) {
return;
}
const actual_cart = JSON.parse(cartDataElement.getAttribute("data-cart"))
.items;

cartRefreshButton.style.display =
item_list.length === actual_cart.length ? "none" : "block";

exportCartButton.style.display =
item_list.length === actual_cart.length ? "block" : "none";
});

const itemList = document.getElementById("item_list");
if (itemList) {
observer.observe(itemList, {
childList: true
});
}
}
}
17 changes: 4 additions & 13 deletions src/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,8 @@ import {
getTralbumDetails,
addAlbumToCart
} from "./utilities.js";
import { createInputButtonPair } from "./components/inputButtonPair.js";
import {
createShoppingCartItem,
createShoppingCartResetButton
} from "./components/shoppingCart.js";
import { createInputButtonPair } from "./components/buttons.js";
import { createShoppingCartItem } from "./components/shoppingCart.js";
import emptyPlaylistTable from "../html/empty_playlist_table.html";

const stepSize = 10;
Expand All @@ -26,7 +23,6 @@ export default class Player {
this.addAlbumToCart = addAlbumToCart;
this.createInputButtonPair = createInputButtonPair;
this.createShoppingCartItem = createShoppingCartItem;
this.createShoppingCartResetButton = createShoppingCartResetButton;
this.extractBandFollowInfo = extractBandFollowInfo;
this.getTralbumDetails = getTralbumDetails.bind(this);
this.createInputButtonPair = createInputButtonPair;
Expand All @@ -44,13 +40,6 @@ export default class Player {

Player.movePlaylist();

const cartRefreshButton = this.createShoppingCartResetButton({
className: "buttonLink",
innerText: "⟳",
buttonClicked: () => location.reload()
});
document.querySelector("#sidecartReveal").append(cartRefreshButton);

this.updatePlayerControlInterface();

const bandFollowInfo = this.extractBandFollowInfo();
Expand Down Expand Up @@ -92,6 +81,8 @@ export default class Player {
downloadCol.append(oneClick);

document.querySelectorAll("tr.track_row_view").forEach((row, i) => {
if (tralbumDetails.tracks[i] === undefined) return;
sabjorn marked this conversation as resolved.
Show resolved Hide resolved

const {
price,
currency,
Expand Down
61 changes: 61 additions & 0 deletions src/utilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,64 @@ export function getTralbumDetails(item_id, item_type = "a") {

return fetch(`/api/mobile/25/tralbum_details`, requestOptions);
}

export function downloadFile(filename, text) {
var element = document.createElement("a");

element.setAttribute(
"href",
"data:text/plain;charset=utf-8," + encodeURIComponent(text)
);
element.setAttribute("download", filename);

element.style.display = "none";
document.body.appendChild(element);

element.click();

document.body.removeChild(element);
}

export function dateString() {
const currentdate = new Date();
const ye = new Intl.DateTimeFormat("en", { year: "2-digit" }).format(
currentdate
);
const mo = new Intl.DateTimeFormat("en", { month: "2-digit" }).format(
currentdate
);
const da = new Intl.DateTimeFormat("en", { day: "2-digit" }).format(
currentdate
);

return `${ye}-${mo}-${da}`;
}

export function loadJsonFile() {
return new Promise((resolve, reject) => {
const fileInput = document.createElement("input");
fileInput.type = "file";
fileInput.accept = ".json";

fileInput.onchange = event => {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();

reader.onload = e => {
try {
const jsonObject = JSON.parse(e.target.result);
resolve(jsonObject);
} catch (error) {
reject(error);
}
};

reader.onerror = error => reject(error);
reader.readAsText(file);
}
};

fileInput.click();
});
}
Loading
Loading