Skip to content

Commit

Permalink
feat(foreign-collections-list): support "type" parameter, change "cou…
Browse files Browse the repository at this point in the history
…ntry" to "entry"
  • Loading branch information
Eejit43 committed Mar 7, 2024
1 parent 1aa6497 commit d59ffb5
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 65 deletions.
138 changes: 90 additions & 48 deletions src/public/scripts/pages/info/foreign-collections-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,25 +58,25 @@ async function loadCollectionList() {
newRowMessage.addEventListener('click', async () => {
if (newRowMessage.dataset.disabled === 'true') return;

for (const element of collectionsListTableBody.querySelectorAll('[contenteditable]')) (element as HTMLElement).contentEditable = 'false';
for (const input of collectionsListTableBody.querySelectorAll('input')) input.disabled = true;
newRowMessage.dataset.disabled = 'true';
disableElements();

let id: string;
do id = Math.floor(Math.random() * 9_000_000_000 + 1_000_000_000).toString();
while (collectionData.some((country) => country.id === id));
while (collectionData.some((entry) => entry.id === id));

const result = (await (
await fetch('/foreign-collections-list-add-country', {
await fetch('/foreign-collections-list-add-entry', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Unknown Country', data: { coins: false, banknotes: false, stamps: false }, password: passwordInput.dataset.input }),
body: JSON.stringify({ name: 'Unknown Entry', type: 'Unknown Type', data: { coins: false, banknotes: false, stamps: false }, password: passwordInput.dataset.input }),
})
).json()) as { error?: string; data: ForeignCollectionsList };

if (result.error) showAlert(result.error, 'error');
else {
showAlert('Successfully added a new country row!', 'success');
if (result.error) {
showAlert(result.error, 'error');
reloadTableData();
} else {
showAlert('Successfully added a new entry row!', 'success');

collectionData = result.data;

Expand All @@ -93,57 +93,66 @@ async function loadCollectionList() {
function reloadTableData() {
while (collectionsListTableBody.children.length > 1) collectionsListTableBody.children[0].remove();

for (const country of collectionData) {
for (const entry of collectionData) {
const row = document.createElement('tr');

const nameCell = document.createElement('td');
nameCell.contentEditable = 'true';

if (country.name.includes(', ')) {
const textAfter = country.name.slice(country.name.indexOf(', ') + 2);
nameCell.textContent = textAfter + ' ' + country.name.slice(0, country.name.indexOf(','));
} else nameCell.textContent = country.name;
if (entry.name.includes(', ')) {
const textAfter = entry.name.slice(entry.name.indexOf(', ') + 2);
nameCell.textContent = textAfter + ' ' + entry.name.slice(0, entry.name.indexOf(','));
} else nameCell.textContent = entry.name;

nameCell.addEventListener('keydown', (event) => {
if (event.key === 'Enter') event.preventDefault();
});
nameCell.addEventListener('focus', () => (nameCell.textContent = entry.name));

nameCell.addEventListener('paste', (event) => {
event.preventDefault();
nameCell.addEventListener('blur', async () => {
disableElements();

const result = (await (
await fetch('/foreign-collections-list-edit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: entry.id, name: nameCell.textContent, type: entry.type, data: entry.data, password: passwordInput.dataset.input }),
})
).json()) as { error?: string; data: ForeignCollectionsList };

const text = event.clipboardData!.getData('text/plain').replaceAll(/\r?\n|\r/g, '');
if (result.error) {
showAlert(result.error, 'error');
reloadTableData();
} else {
showAlert('Successfully edited the entry name!', 'success');

const range = document.getSelection()!.getRangeAt(0);
range.deleteContents();
collectionData = result.data;

const textNode = document.createTextNode(text);
range.insertNode(textNode);
range.selectNodeContents(textNode);
range.collapse(false);
reloadTableData();
}

const selection = window.getSelection()!;
selection.removeAllRanges();
selection.addRange(range);
newRowMessage.dataset.disabled = 'false';
});

nameCell.addEventListener('focus', () => (nameCell.textContent = country.name));
row.append(nameCell);

const typeCell = document.createElement('td');
typeCell.contentEditable = 'true';
typeCell.textContent = entry.type;

nameCell.addEventListener('blur', async () => {
nameCell.contentEditable = 'false';
for (const input of collectionsListTableBody.querySelectorAll('input')) input.disabled = true;
newRowMessage.dataset.disabled = 'true';
typeCell.addEventListener('blur', async () => {
disableElements();

const result = (await (
await fetch('/foreign-collections-list-edit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: country.id, name: nameCell.textContent, data: country.data, password: passwordInput.dataset.input }),
body: JSON.stringify({ id: entry.id, name: entry.name, type: typeCell.textContent, data: entry.data, password: passwordInput.dataset.input }),
})
).json()) as { error?: string; data: ForeignCollectionsList };

if (result.error) showAlert(result.error, 'error');
else {
showAlert('Successfully edited the country name!', 'success');
if (result.error) {
showAlert(result.error, 'error');
reloadTableData();
} else {
showAlert('Successfully edited the entry type!', 'success');

collectionData = result.data;

Expand All @@ -153,10 +162,34 @@ function reloadTableData() {
newRowMessage.dataset.disabled = 'false';
});

row.append(nameCell);
row.append(typeCell);

for (const element of [nameCell, typeCell]) {
element.addEventListener('keydown', (event) => {
if (event.key === 'Enter') event.preventDefault();
});

element.addEventListener('paste', (event) => {
event.preventDefault();

const text = event.clipboardData!.getData('text/plain').replaceAll(/\r?\n|\r/g, '');

const range = document.getSelection()!.getRangeAt(0);
range.deleteContents();

const textNode = document.createTextNode(text);
range.insertNode(textNode);
range.selectNodeContents(textNode);
range.collapse(false);

const selection = window.getSelection()!;
selection.removeAllRanges();
selection.addRange(range);
});
}

for (const type of ['coins', 'banknotes', 'stamps'] as const) {
const obtained = country.data[type];
const obtained = entry.data[type];

const cell = document.createElement('td');
cell.dataset.obtained = typeof obtained === 'boolean' ? obtained.toString() : 'na';
Expand All @@ -169,9 +202,7 @@ function reloadTableData() {
if (obtained === null) checkbox.indeterminate = true;

checkbox.addEventListener('click', async () => {
nameCell.contentEditable = 'false';
for (const input of collectionsListTableBody.querySelectorAll('input')) input.disabled = true;
newRowMessage.dataset.disabled = 'true';
disableElements();

let obtained;
if (cell.dataset.obtained === 'na') {
Expand All @@ -182,20 +213,22 @@ function reloadTableData() {
cell.dataset.obtained = obtained ? 'true' : 'na';
}

let sortedData = { ...country.data, [type]: obtained };
let sortedData = { ...entry.data, [type]: obtained };
sortedData = { coins: sortedData.coins, banknotes: sortedData.banknotes, stamps: sortedData.stamps };

const result = (await (
await fetch('/foreign-collections-list-edit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: country.id, name: country.name, data: sortedData, password: passwordInput.dataset.input }),
body: JSON.stringify({ id: entry.id, name: entry.name, type: entry.type, data: sortedData, password: passwordInput.dataset.input }),
})
).json()) as { error?: string; data: ForeignCollectionsList };

if (result.error) showAlert(result.error, 'error');
else {
showAlert(`Successfully updated the country's "${type}" obtained status!`, 'success');
if (result.error) {
showAlert(result.error, 'error');
reloadTableData();
} else {
showAlert(`Successfully updated the entry's "${type}" obtained status!`, 'success');

collectionData = result.data;

Expand All @@ -211,6 +244,15 @@ function reloadTableData() {
}
}

/**
* Disables all elements in the table.
*/
function disableElements() {
for (const element of collectionsListTableBody.querySelectorAll('[contenteditable]')) (element as HTMLElement).contentEditable = 'false';
for (const input of collectionsListTableBody.querySelectorAll('input')) input.disabled = true;
newRowMessage.dataset.disabled = 'true';
}

const parameters = new URLSearchParams(window.location.search);
const password = parameters.get('password');

Expand Down
1 change: 0 additions & 1 deletion src/route-handlers/coins-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,6 @@ function sortObject<T extends DatabaseCoinDenomination | CoinDesign<Coin> | Coin
/**
* Generates a unique coin ID.
* @param design The design to generate the ID for.
* @returns The generated ID.
*/
function generateUniqueCoinId(design: CoinDesign<Coin>) {
let id: string;
Expand Down
34 changes: 18 additions & 16 deletions src/route-handlers/foreign-collections-list.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { FastifyInstance, FastifyRequest } from 'fastify';
import { Schema, model } from 'mongoose';

interface ForeignCollectionCountry {
interface ForeignCollectionEntry {
id: string;
name: string;
type: string;
data: { coins: boolean | null; banknotes: boolean | null; stamps: boolean | null };
}

export type ForeignCollectionsList = ForeignCollectionCountry[];
export type ForeignCollectionsList = ForeignCollectionEntry[];

type DatabaseForeignCollectionsList = { data: ForeignCollectionsList } & { _id?: number; __v?: number }; // eslint-disable-line @typescript-eslint/naming-convention

Expand All @@ -26,8 +27,8 @@ export default function (fastify: FastifyInstance) {
reply.send(JSON.stringify(foundList.data, null, 2));
});

fastify.post('/foreign-collections-list-edit', async (request: FastifyRequest<{ Body: ForeignCollectionCountry & { password: string } }>, reply) => {
const { name, data, password } = request.body;
fastify.post('/foreign-collections-list-edit', async (request: FastifyRequest<{ Body: ForeignCollectionEntry & { password: string } }>, reply) => {
const { name, type, data, password } = request.body;

if (password !== process.env.COINS_PASSWORD) return reply.send(JSON.stringify({ error: 'Invalid password!' }, null, 2));

Expand All @@ -36,11 +37,12 @@ export default function (fastify: FastifyInstance) {
delete foundList._id;
delete foundList.__v;

const foundCountry = foundList.data.find((country) => country.id === request.body.id);
if (!foundCountry) return reply.send(JSON.stringify({ error: 'Country not found!' }, null, 2));
const foundEntry = foundList.data.find((entry) => entry.id === request.body.id);
if (!foundEntry) return reply.send(JSON.stringify({ error: 'Entry not found!' }, null, 2));

foundCountry.name = name;
foundCountry.data = data;
foundEntry.name = name;
foundEntry.type = type;
foundEntry.data = data;

const sortedData = foundList.data.sort((a, b) => a.name.localeCompare(b.name));

Expand All @@ -49,8 +51,8 @@ export default function (fastify: FastifyInstance) {
reply.send(JSON.stringify({ success: true, data: sortedData }, null, 2));
});

fastify.post('/foreign-collections-list-add-country', async (request: FastifyRequest<{ Body: ForeignCollectionCountry & { password: string } }>, reply) => {
const { name, data, password } = request.body;
fastify.post('/foreign-collections-list-add-entry', async (request: FastifyRequest<{ Body: ForeignCollectionEntry & { password: string } }>, reply) => {
const { name, type, data, password } = request.body;

if (password !== process.env.COINS_PASSWORD) return reply.send(JSON.stringify({ error: 'Invalid password!' }, null, 2));

Expand All @@ -59,9 +61,9 @@ export default function (fastify: FastifyInstance) {
delete foundList._id;
delete foundList.__v;

const id = generateUniqueCountryId(foundList.data);
const id = generateUniqueEntryId(foundList.data);

foundList.data.push({ id, name, data });
foundList.data.push({ id, name, type, data });

const sortedData = foundList.data.sort((a, b) => a.name.localeCompare(b.name));

Expand All @@ -72,14 +74,14 @@ export default function (fastify: FastifyInstance) {
}

/**
* Generates a unique coin ID.
* @param list The list of collections to check against.
* Generates a unique entry ID.
* @param list The collections list to check ids against.
*/
function generateUniqueCountryId(list: ForeignCollectionsList) {
function generateUniqueEntryId(list: ForeignCollectionsList) {
let id: string;

do id = Math.floor(Math.random() * 9_000_000_000 + 1_000_000_000).toString();
while (list.some((country) => country.id === id));
while (list.some((entry) => entry.id === id));

return id;
}
1 change: 1 addition & 0 deletions src/views/pages/info/foreign-collections-list.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Welcome! Here is a list of countries, territories, etc. that I have coins, bankn
<thead>
<tr>
<th>Country</th>
<th>Type</th>
<th>Has Coin(s)</th>
<th>Has Banknote(s)</th>
<th>Has Stamp(s)</th>
Expand Down

0 comments on commit d59ffb5

Please sign in to comment.