-
Notifications
You must be signed in to change notification settings - Fork 0
/
script.js
275 lines (226 loc) · 9.3 KB
/
script.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
/***
* SELLING MY STUFF
* This is a quick and dirty static website to sell my stuff asap. Don't expect best practices, except for security etc.
***/
// DARK MODE
// JS for toggling dark mode
const darkModeComponent = document.getElementById('dark-mode-component');
// the dark-mode-component "switch" is just a HTML `input` type range: it's values are 1 for light mode, 0 for dark mode
if (localStorage.darkModeComponent === "1" ||
(!localStorage.darkModeComponent && window.matchMedia('(prefers-color-scheme: light)').matches)) {
// page initial default value is dark mode, so if user prefers light, it has to change to light mode "1"
document.body.classList.remove('dark');
darkModeComponent.querySelector('input').value = "1";
}
function toggleDarkMode(ev) {
localStorage.darkModeComponent = localStorage.darkModeComponent === "1" ? "0" : "1";
const input = darkModeComponent.querySelector('input');
// this setTimeout is just for GlobalSpeed (Firefox, Chrome version works fine) and other browser extensions that might get incorrectly triggered otherwise by `pointerup` event
setTimeout(() => input.value = localStorage.darkModeComponent, 0);
document.body.classList.toggle('dark');
};
// `pointerup` instead of `click` so that it also triggers when user's finger carries the darkModeComponent slider from one side to the other
// `capture` is needed so that the function runs before the input's change event (which isn't cancelable), otherwise the input's slider could flicker "on/off"
darkModeComponent.addEventListener('pointerup', toggleDarkMode, {capture: true});
// POPULATING PAGE
// add data contained in `stuff.txt` file to salesSection
(async function() {
let salesSection = '<h2><i>On sale!</i></h2>';
// `stuff` to sell is being fetched because I don't want the browser to cache the file. I don't control the server, so there are few other options
let stuff = await fetch('./stuff.txt', {cache: "no-cache"})
.catch((e) => alert('There was an error with the network, please check your internet connection and reload the page\n' + e));
if (!stuff.ok) alert(`The page hasn't loaded successfully! Please reload it`);
stuff = await stuff.text();
/*
// example content for debugging:
let stuff = `
<start>
<music>
title: 88 keys Digital Piano + stand with 3 pedals + bench
price: $400, all included.
img: piano.jpg;
img: piano2.jpg;
desc: Alesis Coda Pro - an 88 weighted keys digital piano
+ official stand, with all 3 piano pedals - sustain, sostenuto, una corda (Coda Piano Stand)
+ bench
+ (extra sustain pedal, to use when somewhere else away from the stand)
This is the perfect beginners kit to learn piano. This 88 weighted keys, velocity-sensitive digital piano has a few voices, 64 voices polyphony, a pitch wheel and many connectors:
<ul><li>MIDI out</li> <li>also Usb-MIDI</li> <li>Stereo 1/4 inch aux input for playing along with external equipment.</li> <li>Stereo 1/4 inch aux <i>output</i></li> <li>Headphone jack (2 options: mute loudspeakers or have both headphone and loudspeakers sound (e.g., for plugging it into an amplifier)</li> </ul>
It has a bunch of functions, like scale, split, demo songs and record.
<b>It comes with the official stand, with all 3 piano pedals</b>. The bench is also included in the price.
It also comes with an extra sustain pedal -- in case you want to play the piano at a party or somewhere else -- and a bench.
This product has been discontinued because a new line was released, but here's the old <a href="https://www.amazon.com/Alesis-Coda-Pro-Hammer-Action-Keyboard/dp/B00SHCDMRQ/">Amazon link</a>.
<end>
<--com> template:
<start>
<music and/or sold>
title: Digital Piano
price: $400
img: ;
desc:
<end>
<com-->
`;
*/
// parse fetched content
stuff = stuff.trim().replace(/\t/g, '').replace(/<--com[\s\S]*com-->/g, '').replace(/\n/g, '<br><split>').split('<split>');
{
let writable = false;
let article = '';
let sold = false;
let music = false;
for (let line of stuff) {
line = line.trim();
if (line.startsWith('<start>')) {
line = `<article class="item">`;
writable = true;
}
else if (line.startsWith('title: ')) {
line = line.replace('title: ', '');
line = `<h3 class="title">${line}</h3>`;
}
else if (line.startsWith('desc: ')) {
line = line.replace('desc: ', '');
line = `<div class="description">${line}`;
}
else if (line.startsWith('price: ')) {
line = line.replace('price: ', '');
// content is being added in CSS .price::before
line = `<div class="price">${line}</div>`;
}
else if (line.startsWith('img: ')) {
line = line.match(/img:\s*(\w+\.\w+)/)[1];
line = `<img src="./images/${line}"></img>`;
}
else if (line.startsWith('<sold>')) {
sold = true;
continue;
}
else if (line.startsWith('<music>')) {
music = true;
continue;
}
else if (line.startsWith('<end>')) {
// </div> closes description
line = `</div></article>`;
article += line;
if (sold) {
article = article.replace(`<article class="`, `<article class="sold `);
sold = false;
}
if (music) {
article = article.replace(`<article class="`, `<article class="music `);
music = false;
}
salesSection += article;
article = '';
writable = false;
}
if (writable) article += line;
}
}
// add fetched content to the DOM
const sale = document.querySelector('#sale');
sale.innerHTML = salesSection;
// group all images together in one div
for (const item of document.getElementsByClassName('item')) {
const images = document.createElement('div');
images.className = 'images';
images.append(...item.getElementsByTagName('img'));
item.appendChild(images);
}
// Remove loading animation once everything loads
const loading = document.getElementById('loading');
loading.style.opacity = 0;
setTimeout(() => {
loading.remove();
}, 500);
})();
// SORTING
// function for sorting items for sale
function sortSales() {
const entries = {};
const sortedItems = [];
for (input of document.querySelectorAll('#sort input')) {
if (input.type !== 'text' && !input.checked) continue;
entries[input.name] = input.value;
}
// validation
if (entries.min && entries.max && (Number(entries.min) >= Number(entries.max))) return alert('Error: minimum price must be smaller than maximum!')
if (entries.min && entries.max && (Number(entries.max) <= Number(entries.min))) return alert('Error: maximum price must be bigger than minimum!')
for (const item of document.getElementsByClassName('item')) {
const price = Number(item.querySelector('.price').textContent.match(/\$(\d+\.?\d*)/)[1]);
item.price = price;
if (entries.music && !item.classList.contains('music')) continue;
if (entries.min && (price < entries.min)) continue;
if (entries.max && (price > entries.max)) continue;
sortedItems.push(item);
}
sortedItems.sort((a,b) => {
if (entries.sort === 'price') {
return entries.direction === 'ascending' ? a.price > b.price : a.price < b.price;
}
else if (entries.sort === 'name') {
return entries.direction === 'ascending' ? a.price > b.price : a.price < b.price;
}
});
// add remaining items for sale to the bottom
for (const item of document.getElementsByClassName('item')) {
if (!sortedItems.includes(item)) sortedItems.push(item);
}
// update DOM
document.getElementById('sale').append(...sortedItems);
closeDialog();
}
// LISTING
// Get all items and make a list of them, then add it to the `list` dialog
function listItems() {
const allItems = [];
for (const item of document.getElementsByClassName('item')) {
const title = item.querySelector('.title').textContent;
const price = item.querySelector('.price').textContent;
const li = document.createElement('li');
li.innerHTML = `${title}: <span class="list-price">${price}</span>`;
// on click, scroll to item
li.addEventListener('click', () => {
let position = item.getBoundingClientRect().top;
let offset = document.getElementById('top-nav').getBoundingClientRect().height;
position = position > 0 ? position - offset : position + offset;
window.scroll({top: position});
});
allItems.push(li);
}
document.querySelector('#list ol#list-items').replaceChildren(...allItems);
// close list dialog after user clicks anywhere
setTimeout(() => {
// capture `true` in case user cancels dialog with Esc
window.addEventListener('click', () => {closeDialog();}, {once: true, capture: true});
}, 0);
// finally, present list to user
document.getElementById('list').showModal();
}
// DIALOGS
// switch closing functions of dialogs to closeDialog, because it has a closing animation that needs JS to retrigger
for (const dialog of document.getElementsByTagName('dialog')) {
dialog.oncancel = (ev) => {
ev.preventDefault();
closeDialog();
};
}
// before closing, this will first reverse the animation. It's incredibly hard to restart an animation without JS
function closeDialog() {
// select whatever modal dialog is open
const dialog = document.querySelector('dialog[open]');
if (!dialog) return;
// reverse the animations
dialog.getAnimations().forEach(
(animation) => {
animation.finish(); // in case user cancels while original animation was starting
animation.reverse();
}
);
setTimeout(() => {
dialog.classList.remove('closing');
dialog.close();
}, 400);
}