mirror of
https://github.com/cfultz/mtgcsv.git
synced 2024-11-22 01:10:02 +01:00
refactor
This commit is contained in:
parent
1aed7fb673
commit
6d93ad6203
BIN
assets/cash-money.mp3
Normal file
BIN
assets/cash-money.mp3
Normal file
Binary file not shown.
BIN
assets/icon.png
Normal file
BIN
assets/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 68 KiB |
89
css/base.css
Normal file
89
css/base.css
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
/* base.css */
|
||||||
|
:root {
|
||||||
|
--background-color: #f0f0f0;
|
||||||
|
--text-color: #333;
|
||||||
|
--card-background: #ffffff;
|
||||||
|
--button-background: #007acc;
|
||||||
|
--button-hover: #005fa3;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Sorts Mill Goudy', serif;
|
||||||
|
background-color: var(--background-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background-color: var(--card-background);
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
form div {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input, select, textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(--card-background);
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: var(--button-background);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background-color: var(--button-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
#csv-output {
|
||||||
|
margin-top: 20px;
|
||||||
|
height: 200px;
|
||||||
|
background-color: var(--card-background);
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.5rem;
|
||||||
|
color: #666;
|
||||||
|
margin-top: 0px;
|
||||||
|
padding: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a {
|
||||||
|
color: #007acc;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
106
css/components.css
Normal file
106
css/components.css
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
/* components.css */
|
||||||
|
.mana-icon {
|
||||||
|
font-family: 'mana', sans-serif;
|
||||||
|
font-size: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 5px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mana-icon:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.menu {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger-menu {
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 30px;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-content {
|
||||||
|
display: none; /* Ensure it's hidden by default */
|
||||||
|
position: absolute;
|
||||||
|
top: 40px;
|
||||||
|
left: 0;
|
||||||
|
background-color: var(--background-color);
|
||||||
|
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
|
||||||
|
padding: 10px;
|
||||||
|
z-index: 1;
|
||||||
|
min-width: 150px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-content.show {
|
||||||
|
display: block; /* Display menu when toggled */
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-modal {
|
||||||
|
display: none; /* Ensure modal is hidden by default */
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1000;
|
||||||
|
padding-top: 100px;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
background-color: rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoomed-image {
|
||||||
|
margin: auto;
|
||||||
|
display: block;
|
||||||
|
width: 80%;
|
||||||
|
max-width: 700px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode styles */
|
||||||
|
/* Dark mode styles */
|
||||||
|
body.dark-mode .menu-content {
|
||||||
|
background-color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .hamburger-menu,
|
||||||
|
body.dark-mode .menu-content label,
|
||||||
|
body.dark-mode .menu-content input {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-popup {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
right: 20px;
|
||||||
|
max-width: 20%;
|
||||||
|
z-index: 1000;
|
||||||
|
background-color: rgba(0, 0, 0, 0.7);
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.5s ease-in-out;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-popup img {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.card-popup {
|
||||||
|
max-width: 40%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 400px) {
|
||||||
|
.card-popup {
|
||||||
|
max-width: 60%;
|
||||||
|
}
|
||||||
|
}
|
102
css/themes.css
Normal file
102
css/themes.css
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
/* Light and Dark mode base theme */
|
||||||
|
:root {
|
||||||
|
--background-color: #f0f0f0;
|
||||||
|
--text-color: #333;
|
||||||
|
--card-background: #ffffff;
|
||||||
|
--button-background: #007acc;
|
||||||
|
--button-hover: #005fa3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--background-color: #1e1e1e;
|
||||||
|
--text-color: #ffffff;
|
||||||
|
--card-background: #2e2e2e;
|
||||||
|
--button-background: #1f6fb2;
|
||||||
|
--button-hover: #145b8c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mana Color Themes */
|
||||||
|
.theme-white {
|
||||||
|
--background-color: #f9f8f6;
|
||||||
|
--text-color: #1e1e1e;
|
||||||
|
--card-background: #ffffff;
|
||||||
|
--button-background: #e5e5e5;
|
||||||
|
--button-hover: #cccccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-blue {
|
||||||
|
--background-color: #d7e7f9;
|
||||||
|
--text-color: #002d72;
|
||||||
|
--card-background: #e0f2ff;
|
||||||
|
--button-background: #007acc;
|
||||||
|
--button-hover: #005fa3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-black {
|
||||||
|
--background-color: #333333;
|
||||||
|
--text-color: #ffffff;
|
||||||
|
--card-background: #444444;
|
||||||
|
--button-background: #1f1f1f;
|
||||||
|
--button-hover: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-red {
|
||||||
|
--background-color: #fbe4e4;
|
||||||
|
--text-color: #8b0000;
|
||||||
|
--card-background: #ffe5e5;
|
||||||
|
--button-background: #d32f2f;
|
||||||
|
--button-hover: #b71c1c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-green {
|
||||||
|
--background-color: #e7f4e7;
|
||||||
|
--text-color: #004d00;
|
||||||
|
--card-background: #e0ffe0;
|
||||||
|
--button-background: #388e3c;
|
||||||
|
--button-hover: #2e7d32;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark Mode Variants for Mana Themes */
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.theme-white {
|
||||||
|
--background-color: #1e1e1e;
|
||||||
|
--text-color: #ffffff;
|
||||||
|
--card-background: #333333;
|
||||||
|
--button-background: #555555;
|
||||||
|
--button-hover: #777777;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-blue {
|
||||||
|
--background-color: #001f3f;
|
||||||
|
--text-color: #ffffff;
|
||||||
|
--card-background: #002d72;
|
||||||
|
--button-background: #005fa3;
|
||||||
|
--button-hover: #007acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-black {
|
||||||
|
--background-color: #000000;
|
||||||
|
--text-color: #e5e5e5;
|
||||||
|
--card-background: #1c1c1c;
|
||||||
|
--button-background: #333333;
|
||||||
|
--button-hover: #555555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-red {
|
||||||
|
--background-color: #330000;
|
||||||
|
--text-color: #ffaaaa;
|
||||||
|
--card-background: #8b0000;
|
||||||
|
--button-background: #b71c1c;
|
||||||
|
--button-hover: #d32f2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-green {
|
||||||
|
--background-color: #002200;
|
||||||
|
--text-color: #ccffcc;
|
||||||
|
--card-background: #004d00;
|
||||||
|
--button-background: #2e7d32;
|
||||||
|
--button-hover: #388e3c;
|
||||||
|
}
|
||||||
|
}
|
59
index.html
59
index.html
@ -1,28 +1,33 @@
|
|||||||
|
<!-- index.html -->
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>MTG Card CSV Generator</title>
|
<title>MTG Card CSV Generator</title>
|
||||||
<link rel="stylesheet" href="styles.css">
|
|
||||||
|
<!-- Stylesheets -->
|
||||||
|
<link rel="stylesheet" href="css/base.css">
|
||||||
|
<link rel="stylesheet" href="css/themes.css">
|
||||||
|
<link rel="stylesheet" href="css/components.css">
|
||||||
<link rel="manifest" href="manifest.json">
|
<link rel="manifest" href="manifest.json">
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Sorts+Mill+Goudy&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Sorts+Mill+Goudy&display=swap" rel="stylesheet">
|
||||||
<link rel="icon" href="icon.png" type="image/png">
|
<link rel="icon" href="assets/icon.png" type="image/png">
|
||||||
<link href="//cdn.jsdelivr.net/npm/mana-font@latest/css/mana.css" rel="stylesheet" type="text/css" />
|
<link href="https://cdn.jsdelivr.net/npm/mana-font@latest/css/mana.css" rel="stylesheet" type="text/css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
<div class="hamburger-menu" onclick="toggleMenu()">☰</div>
|
<div class="hamburger-menu" id="hamburger-menu">☰</div>
|
||||||
<div id="menu-content" class="menu-content">
|
<div id="menu-content" class="menu-content">
|
||||||
<label>Select Theme:</label>
|
<label>Select Theme:</label>
|
||||||
<div id="theme-selector">
|
<div id="theme-selector">
|
||||||
<span class="mana-icon" onclick="setTheme('')"></span> <!-- Colorless -->
|
<span class="mana-icon" data-theme="" onclick="setTheme('')"></span> <!-- Colorless -->
|
||||||
<span class="mana-icon" onclick="setTheme('theme-white')"></span> <!-- White -->
|
<span class="mana-icon" data-theme="theme-white" onclick="setTheme('theme-white')"></span> <!-- White -->
|
||||||
<span class="mana-icon" onclick="setTheme('theme-blue')"></span> <!-- Blue -->
|
<span class="mana-icon" data-theme="theme-blue" onclick="setTheme('theme-blue')"></span> <!-- Blue -->
|
||||||
<span class="mana-icon" onclick="setTheme('theme-black')"></span> <!-- Black -->
|
<span class="mana-icon" data-theme="theme-black" onclick="setTheme('theme-black')"></span> <!-- Black -->
|
||||||
<span class="mana-icon" onclick="setTheme('theme-red')"></span> <!-- Red -->
|
<span class="mana-icon" data-theme="theme-red" onclick="setTheme('theme-red')"></span> <!-- Red -->
|
||||||
<span class="mana-icon" onclick="setTheme('theme-green')"></span> <!-- Green -->
|
<span class="mana-icon" data-theme="theme-green" onclick="setTheme('theme-green')"></span> <!-- Green -->
|
||||||
</div>
|
</div>
|
||||||
<label for="sound-threshold">Sound Threshold (USD):</label>
|
<label for="sound-threshold">Sound Threshold (USD):</label>
|
||||||
<input type="number" id="sound-threshold" min="0.01" step="0.01" value="1.00">
|
<input type="number" id="sound-threshold" min="0.01" step="0.01" value="1.00">
|
||||||
@ -30,6 +35,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1>MTG Card CSV Generator</h1>
|
<h1>MTG Card CSV Generator</h1>
|
||||||
|
|
||||||
<form id="card-form">
|
<form id="card-form">
|
||||||
<div>
|
<div>
|
||||||
<label for="card-name">Card Name:</label>
|
<label for="card-name">Card Name:</label>
|
||||||
@ -57,9 +63,23 @@
|
|||||||
</div>
|
</div>
|
||||||
<button type="button" id="add-card-button">Add Card</button>
|
<button type="button" id="add-card-button">Add Card</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<div class="action-buttons">
|
||||||
<button id="download-csv-button">Download CSV</button>
|
<button id="download-csv-button">Download CSV</button>
|
||||||
|
<button id="save-collection-button">Save Collection</button>
|
||||||
|
<button id="clear-collection-button">Clear Collection</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<textarea id="csv-output" readonly></textarea>
|
<textarea id="csv-output" readonly></textarea>
|
||||||
<div id="card-popup" class="card-popup"></div>
|
|
||||||
|
<div id="card-popup" class="card-popup">
|
||||||
|
<img id="card-popup-image" src="" alt="Card Image">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="zoom-modal" class="zoom-modal">
|
||||||
|
<span id="close-modal" class="close-modal">×</span>
|
||||||
|
<img id="zoomed-image" class="zoomed-image" src="" alt="">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
@ -69,10 +89,21 @@
|
|||||||
</p>
|
</p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<script type="module" src="app.js"></script>
|
<!-- Scripts -->
|
||||||
|
<script type="module" src="js/theme.js"></script>
|
||||||
|
<script type="module" src="js/main.js"></script>
|
||||||
|
<script type="module" src="js/card.js"></script>
|
||||||
|
<script type="module" src="js/storage.js"></script>
|
||||||
|
<script type="module" src="js/csv.js"></script>
|
||||||
|
<script type="module" src="js/popup.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
if (typeof navigator.serviceWorker !== 'undefined') {
|
if ('serviceWorker' in navigator) {
|
||||||
navigator.serviceWorker.register('sw.js')
|
navigator.serviceWorker.register('js/sw.js').then(function(registration) {
|
||||||
|
console.log('Service Worker registered with scope:', registration.scope);
|
||||||
|
}).catch(function(error) {
|
||||||
|
console.error('Service Worker registration failed:', error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
37
js/api.js
Normal file
37
js/api.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// api.js
|
||||||
|
|
||||||
|
export async function fetchCardSuggestions(query) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`https://api.scryfall.com/cards/autocomplete?q=${encodeURIComponent(query)}`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Network response was not ok');
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
return data.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fetch error:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchCardDetails(cardName, setCode) {
|
||||||
|
const response = await fetch(`https://api.scryfall.com/cards/named?exact=${encodeURIComponent(cardName)}&set=${encodeURIComponent(setCode)}`);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.object === 'error') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
set: data.set.toUpperCase(),
|
||||||
|
card_id: data.collector_number,
|
||||||
|
mana_cost: data.mana_cost || 'N/A',
|
||||||
|
power: data.power || 'N/A',
|
||||||
|
toughness: data.toughness || 'N/A',
|
||||||
|
type: data.type_line.split('—')[0].trim(),
|
||||||
|
rarity: data.rarity || 'N/A',
|
||||||
|
price: data.prices.usd || '0.00',
|
||||||
|
imageUrl: data.image_uris ? data.image_uris.small : null,
|
||||||
|
name: data.name
|
||||||
|
};
|
||||||
|
}
|
68
js/card.js
Normal file
68
js/card.js
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
// card.js
|
||||||
|
|
||||||
|
import { updateCardDetails, loadCollection } from './storage.js';
|
||||||
|
import { showCardPopup } from './popup.js';
|
||||||
|
|
||||||
|
export async function addCard(cardList) {
|
||||||
|
const cardName = document.getElementById('card-name').value;
|
||||||
|
const setCodeInput = document.getElementById('set-code');
|
||||||
|
const cardIdInput = document.getElementById('card-id');
|
||||||
|
|
||||||
|
let setCode = setCodeInput.value;
|
||||||
|
let cardId = cardIdInput.value;
|
||||||
|
|
||||||
|
const cardDetails = await fetchCardDetails(cardName, setCode);
|
||||||
|
|
||||||
|
if (cardDetails) {
|
||||||
|
if (!setCode) {
|
||||||
|
setCode = cardDetails.set;
|
||||||
|
setCodeInput.value = setCode;
|
||||||
|
}
|
||||||
|
if (!cardId) {
|
||||||
|
cardId = cardDetails.card_id;
|
||||||
|
cardIdInput.value = cardId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cardData = {
|
||||||
|
name: cardName,
|
||||||
|
set: setCode,
|
||||||
|
card_id: cardId,
|
||||||
|
quantity: document.getElementById('quantity').value,
|
||||||
|
foil: document.getElementById('foil').value,
|
||||||
|
mana_cost: cardDetails.mana_cost,
|
||||||
|
power: cardDetails.power,
|
||||||
|
toughness: cardDetails.toughness,
|
||||||
|
type: cardDetails.type,
|
||||||
|
rarity: cardDetails.rarity,
|
||||||
|
price: cardDetails.price
|
||||||
|
};
|
||||||
|
|
||||||
|
updateCardDetails(cardData);
|
||||||
|
cardList.push(cardData); // Add the card to the list
|
||||||
|
showCardPopup(cardData.name, cardDetails.imageUrl, cardDetails.price);
|
||||||
|
} else {
|
||||||
|
alert("Card not found with the given name.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchCardDetails(cardName, setCode) {
|
||||||
|
const response = await fetch(`https://api.scryfall.com/cards/named?exact=${encodeURIComponent(cardName)}&set=${encodeURIComponent(setCode)}`);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.object === 'error') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
set: data.set.toUpperCase(),
|
||||||
|
card_id: data.collector_number,
|
||||||
|
mana_cost: data.mana_cost || 'N/A',
|
||||||
|
power: data.power || 'N/A',
|
||||||
|
toughness: data.toughness || 'N/A',
|
||||||
|
type: data.type_line.split('—')[0].trim(),
|
||||||
|
rarity: data.rarity || 'N/A',
|
||||||
|
price: data.prices.usd || '0.00',
|
||||||
|
imageUrl: data.image_uris ? data.image_uris.small : null,
|
||||||
|
name: data.name
|
||||||
|
};
|
||||||
|
}
|
44
js/csv.js
Normal file
44
js/csv.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// csv.js
|
||||||
|
|
||||||
|
import { getRandomWordsFromFile } from './randomWords.js';
|
||||||
|
|
||||||
|
export function generateCSVContent(cardList) {
|
||||||
|
let csvContent = "Card Name,Set,Card ID,Quantity,Foil,Mana Type,Power,Toughness,Type,Rarity\n";
|
||||||
|
cardList.forEach(card => {
|
||||||
|
const escapedValues = [
|
||||||
|
card.name,
|
||||||
|
card.set,
|
||||||
|
card.card_id,
|
||||||
|
card.quantity,
|
||||||
|
card.foil,
|
||||||
|
card.mana_cost,
|
||||||
|
card.power,
|
||||||
|
card.toughness,
|
||||||
|
card.type,
|
||||||
|
card.rarity
|
||||||
|
].map(value => `"${value.replace(/"/g, '""')}"`); // Escape any existing quotes by doubling them
|
||||||
|
csvContent += escapedValues.join(",") + "\n";
|
||||||
|
});
|
||||||
|
return csvContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function downloadCSV(cardList) {
|
||||||
|
const csvContent = generateCSVContent(cardList);
|
||||||
|
const randomFileName = getRandomWordsFromFile();
|
||||||
|
|
||||||
|
// Create a blob with the CSV content
|
||||||
|
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
|
||||||
|
// Create a temporary link element
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.setAttribute("href", url);
|
||||||
|
link.setAttribute("download", `${randomFileName}.csv`);
|
||||||
|
document.body.appendChild(link);
|
||||||
|
|
||||||
|
// Trigger the download
|
||||||
|
link.click();
|
||||||
|
|
||||||
|
// Clean up and remove the link
|
||||||
|
document.body.removeChild(link);
|
||||||
|
}
|
93
js/main.js
Normal file
93
js/main.js
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
// main.js
|
||||||
|
|
||||||
|
import { addCard } from './card.js';
|
||||||
|
import { downloadCSV } from './csv.js';
|
||||||
|
import { saveCollection, loadCollection, clearCollection } from './storage.js';
|
||||||
|
import { setTheme, applySavedTheme } from './theme.js';
|
||||||
|
import { fetchCardSuggestions } from './api.js';
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
applySavedTheme();
|
||||||
|
|
||||||
|
let cardList = loadCollection();
|
||||||
|
|
||||||
|
document.getElementById('add-card-button').addEventListener('click', () => {
|
||||||
|
addCard(cardList);
|
||||||
|
updateCSVContent(cardList);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('download-csv-button').addEventListener('click', () => downloadCSV(cardList));
|
||||||
|
document.getElementById('save-collection-button').addEventListener('click', () => saveCollection(cardList));
|
||||||
|
|
||||||
|
document.getElementById('clear-collection-button').addEventListener('click', () => {
|
||||||
|
if (confirm("Are you sure you want to clear your collection? This action cannot be undone.")) {
|
||||||
|
cardList = []; // Reset the card list
|
||||||
|
clearCollection(cardList);
|
||||||
|
updateCSVContent(cardList);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('card-name').addEventListener('input', async function() {
|
||||||
|
const query = this.value.trim();
|
||||||
|
if (query.length < 2) {
|
||||||
|
document.getElementById('suggestions').innerHTML = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const suggestions = await fetchCardSuggestions(query);
|
||||||
|
const suggestionsDiv = document.getElementById('suggestions');
|
||||||
|
suggestionsDiv.innerHTML = '';
|
||||||
|
|
||||||
|
suggestions.forEach(suggestion => {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.textContent = suggestion;
|
||||||
|
div.onclick = () => {
|
||||||
|
document.getElementById('card-name').value = suggestion;
|
||||||
|
suggestionsDiv.innerHTML = '';
|
||||||
|
};
|
||||||
|
suggestionsDiv.appendChild(div);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('sound-threshold').addEventListener('input', (event) => {
|
||||||
|
const soundThreshold = parseFloat(event.target.value);
|
||||||
|
localStorage.setItem('soundThreshold', soundThreshold);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('hamburger-menu').addEventListener('click', () => {
|
||||||
|
const menuContent = document.getElementById('menu-content');
|
||||||
|
menuContent.classList.toggle('show');
|
||||||
|
});
|
||||||
|
|
||||||
|
const isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
|
if (isDarkMode) {
|
||||||
|
document.body.classList.add('dark-mode');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update the CSV content in the textarea
|
||||||
|
function updateCSVContent(cardList) {
|
||||||
|
// Sort the cardList by set and then by card ID
|
||||||
|
cardList.sort((a, b) => {
|
||||||
|
if (a.set < b.set) return -1;
|
||||||
|
if (a.set > b.set) return 1;
|
||||||
|
return parseInt(a.card_id) - parseInt(b.card_id);
|
||||||
|
});
|
||||||
|
|
||||||
|
let csvContent = "Card Name,Set,Card ID,Quantity,Foil,Mana Type,Power,Toughness,Type,Rarity\n";
|
||||||
|
cardList.forEach(card => {
|
||||||
|
const escapedValues = [
|
||||||
|
card.name || '',
|
||||||
|
card.set || '',
|
||||||
|
card.card_id || '',
|
||||||
|
card.quantity || '',
|
||||||
|
card.foil || '',
|
||||||
|
card.mana_type || '',
|
||||||
|
card.power || '',
|
||||||
|
card.toughness || '',
|
||||||
|
card.type || '',
|
||||||
|
card.rarity || ''
|
||||||
|
].map(value => `"${(value !== undefined ? value : '').replace(/"/g, '""')}"`);
|
||||||
|
csvContent += escapedValues.join(",") + "\n";
|
||||||
|
});
|
||||||
|
document.getElementById('csv-output').value = csvContent;
|
||||||
|
}
|
4
js/menu.js
Normal file
4
js/menu.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export function toggleMenu() {
|
||||||
|
const menuContent = document.getElementById('menu-content');
|
||||||
|
menuContent.classList.toggle('show');
|
||||||
|
}
|
40
js/popup.js
Normal file
40
js/popup.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// popup.js
|
||||||
|
|
||||||
|
export async function showCardPopup(cardName, imageUrl, price) {
|
||||||
|
const soundThreshold = parseFloat(localStorage.getItem('soundThreshold')) || 1.00;
|
||||||
|
const cardPopup = document.getElementById('card-popup');
|
||||||
|
|
||||||
|
cardPopup.innerHTML = `
|
||||||
|
<div style="font-family: 'Sorts Mill Goudy', serif; text-align: center;">
|
||||||
|
<strong>${cardName}</strong>
|
||||||
|
</div>
|
||||||
|
<img src="${imageUrl}" alt="${cardName}">
|
||||||
|
<div style="font-family: 'Sorts Mill Goudy', serif; text-align: center; margin-top: 10px;">
|
||||||
|
Price: $${price}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
cardPopup.style.display = 'block';
|
||||||
|
cardPopup.style.opacity = 1;
|
||||||
|
|
||||||
|
// Check if the price exceeds the sound threshold and play sound
|
||||||
|
if (parseFloat(price) >= soundThreshold) {
|
||||||
|
playSound(); // Play the sound if the price exceeds the threshold
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dissolve the popup after a few seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
cardPopup.style.opacity = 0;
|
||||||
|
setTimeout(() => {
|
||||||
|
cardPopup.style.display = 'none';
|
||||||
|
}, 500);
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function playSound() {
|
||||||
|
const audio = new Audio('assets/cash-money.mp3');
|
||||||
|
audio.play().then(() => {
|
||||||
|
console.log('Sound played successfully');
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('Error playing sound:', error);
|
||||||
|
});
|
||||||
|
}
|
32
js/randomWords.js
Normal file
32
js/randomWords.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// randomWords.js
|
||||||
|
|
||||||
|
export const randomWords = [
|
||||||
|
"nonstop", "detailed", "aspiring", "shock", "play", "bashful", "long", "quarter",
|
||||||
|
"six", "charge", "dock", "crabby", "dime", "wry", "story", "wiry", "rampant",
|
||||||
|
"return", "dare", "open", "shame", "many", "brush", "babies", "stem", "squeeze",
|
||||||
|
"judge", "heavenly", "defeated", "optimal", "invention", "object", "change",
|
||||||
|
"sink", "verdant", "jaded", "adjoining", "muddled", "switch", "helpless",
|
||||||
|
"motionless", "skirt", "shrug", "trip", "haircut", "oven", "lip", "habitual",
|
||||||
|
"yielding", "bag", "wheel", "attach", "ticket", "visit", "reflect", "suppose",
|
||||||
|
"present", "wound", "voyage", "real", "aunt", "religion", "redundant", "necessary",
|
||||||
|
"fail", "flower", "unpack", "join", "gamy", "tired", "welcome", "rightful", "jeans",
|
||||||
|
"obscene", "spring", "basket", "battle", "utter", "descriptive", "caring", "fry",
|
||||||
|
"resonant", "supply", "geese", "pets", "impulse", "scintillating", "tame", "release",
|
||||||
|
"tail", "depend", "lively", "nondescript", "punishment", "meek", "crooked",
|
||||||
|
"representative", "twist", "manage", "bored", "grotesque", "demonic", "camp",
|
||||||
|
"temporary", "coil", "passenger", "appliance", "clam", "smoggy", "tasteless", "guess",
|
||||||
|
"verse", "drab", "peep", "business", "paper", "female", "admire", "way", "moor",
|
||||||
|
"breezy", "opposite", "comparison", "tank", "suit", "ludicrous", "minister", "stiff",
|
||||||
|
"whine", "request", "camera", "internal", "improve", "unnatural", "decisive", "exist",
|
||||||
|
"grip", "electric", "bathe", "scandalous", "steer", "humdrum", "action", "rot",
|
||||||
|
"roll", "quartz", "amused", "sidewalk", "roll", "curve"
|
||||||
|
];
|
||||||
|
|
||||||
|
export function getRandomWordsFromFile() {
|
||||||
|
let randomWordsSelected = [];
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
const randomIndex = Math.floor(Math.random() * randomWords.length);
|
||||||
|
randomWordsSelected.push(randomWords[randomIndex]);
|
||||||
|
}
|
||||||
|
return randomWordsSelected.join('-');
|
||||||
|
}
|
24
js/storage.js
Normal file
24
js/storage.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// storage.js
|
||||||
|
|
||||||
|
export function saveCollection(cardList) {
|
||||||
|
localStorage.setItem('cardList', JSON.stringify(cardList));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadCollection() {
|
||||||
|
const savedCollection = localStorage.getItem('cardList');
|
||||||
|
if (savedCollection) {
|
||||||
|
return JSON.parse(savedCollection);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateCardDetails(cardData) {
|
||||||
|
const cardList = loadCollection();
|
||||||
|
cardList.push(cardData);
|
||||||
|
saveCollection(cardList);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearCollection(cardList) {
|
||||||
|
cardList.length = 0; // Clear the cardList array
|
||||||
|
saveCollection(cardList); // Save the empty list to localStorage
|
||||||
|
}
|
54
js/sw.js
Normal file
54
js/sw.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// This is the service worker with the combined offline experience (Offline page + Offline copy of pages)
|
||||||
|
|
||||||
|
const CACHE = "pwabuilder-offline-page";
|
||||||
|
|
||||||
|
importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.1.2/workbox-sw.js');
|
||||||
|
|
||||||
|
// TODO: replace the following with the correct offline fallback page i.e.: const offlineFallbackPage = "offline.html";
|
||||||
|
const offlineFallbackPage = "ToDo-replace-this-name.html";
|
||||||
|
|
||||||
|
self.addEventListener("message", (event) => {
|
||||||
|
if (event.data && event.data.type === "SKIP_WAITING") {
|
||||||
|
self.skipWaiting();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener('install', async (event) => {
|
||||||
|
event.waitUntil(
|
||||||
|
caches.open(CACHE)
|
||||||
|
.then((cache) => cache.add(offlineFallbackPage))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (workbox.navigationPreload.isSupported()) {
|
||||||
|
workbox.navigationPreload.enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
workbox.routing.registerRoute(
|
||||||
|
new RegExp('/*'),
|
||||||
|
new workbox.strategies.StaleWhileRevalidate({
|
||||||
|
cacheName: CACHE
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
self.addEventListener('fetch', (event) => {
|
||||||
|
if (event.request.mode === 'navigate') {
|
||||||
|
event.respondWith((async () => {
|
||||||
|
try {
|
||||||
|
const preloadResp = await event.preloadResponse;
|
||||||
|
|
||||||
|
if (preloadResp) {
|
||||||
|
return preloadResp;
|
||||||
|
}
|
||||||
|
|
||||||
|
const networkResp = await fetch(event.request);
|
||||||
|
return networkResp;
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
const cache = await caches.open(CACHE);
|
||||||
|
const cachedResp = await cache.match(offlineFallbackPage);
|
||||||
|
return cachedResp;
|
||||||
|
}
|
||||||
|
})());
|
||||||
|
}
|
||||||
|
});
|
19
js/theme.js
Normal file
19
js/theme.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
export function setTheme(theme) {
|
||||||
|
// Remove all theme classes first
|
||||||
|
document.body.classList.remove('theme-white', 'theme-blue', 'theme-black', 'theme-red', 'theme-green');
|
||||||
|
|
||||||
|
// Add the selected theme class if it exists
|
||||||
|
if (theme) {
|
||||||
|
document.body.classList.add(theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the selected theme to localStorage
|
||||||
|
localStorage.setItem('selectedTheme', theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applySavedTheme() {
|
||||||
|
const savedTheme = localStorage.getItem('selectedTheme');
|
||||||
|
if (savedTheme) {
|
||||||
|
setTheme(savedTheme);
|
||||||
|
}
|
||||||
|
}
|
@ -8,12 +8,12 @@
|
|||||||
"description": "A PWA for generating CSV files with Magic: The Gathering card details",
|
"description": "A PWA for generating CSV files with Magic: The Gathering card details",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "icon.png",
|
"src": "assets/icon.png",
|
||||||
"sizes": "192x192",
|
"sizes": "192x192",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "icon.png",
|
"src": "assets/icon.png",
|
||||||
"sizes": "512x512",
|
"sizes": "512x512",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
<link href="https://fonts.googleapis.com/css2?family=Sorts+Mill+Goudy&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Sorts+Mill+Goudy&display=swap" rel="stylesheet">
|
||||||
<link rel="icon" href="icon.png" type="image/png">
|
<link rel="icon" href="icon.png" type="image/png">
|
||||||
<link href="//cdn.jsdelivr.net/npm/mana-font@latest/css/mana.css" rel="stylesheet" type="text/css" />
|
<link href="//cdn.jsdelivr.net/npm/mana-font@latest/css/mana.css" rel="stylesheet" type="text/css" />
|
||||||
<link rel="stylesheet" href="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.css">
|
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@ -27,9 +26,5 @@
|
|||||||
This app is not affiliated with Wizards of the Coast or Hasbro. All rights and trademarks are owned by their respective owners.
|
This app is not affiliated with Wizards of the Coast or Hasbro. All rights and trademarks are owned by their respective owners.
|
||||||
</p>
|
</p>
|
||||||
</footer>
|
</footer>
|
||||||
<script src="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.js"></script>
|
|
||||||
<script>
|
|
||||||
mdc.autoInit(); // Initialize all MDC components
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
Loading…
Reference in New Issue
Block a user