Skip to content

Commit f175b40

Browse files
committed
Feat: add palette settings at the top of the page (closes #99)
1 parent 55873da commit f175b40

File tree

5 files changed

+114
-127
lines changed

5 files changed

+114
-127
lines changed

index.html

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,27 @@ <h1>Pixels Visualiser</h1>
9494
<div id="paletteContainer" class="chart-container">
9595
<div class="options small title"><img src="assets/palette_icon.svg" alt="Palette" class="icon-settings"><span>Customise your palette</span></div>
9696

97-
<div id="paletteGrid"></div>
97+
<div id="paletteGrid" class="palette-grid">
98+
<div class="color-cell">
99+
<input type="color" id="color1" />
100+
</div>
101+
<div class="color-cell">
102+
<input type="color" id="color2" />
103+
</div>
104+
<div class="color-cell">
105+
<input type="color" id="color3" />
106+
</div>
107+
<div class="color-cell">
108+
<input type="color" id="color4" />
109+
</div>
110+
<div class="color-cell">
111+
<input type="color" id="color5" />
112+
</div>
113+
</div>
98114

99115
<div class="dialog-buttons">
100-
<button type="button" class="button button-secondary" id="resetSettingsDialog">Reset</button>
101-
<button type="button" class="button" id="cancelSettingsDialog">Cancel</button>
102-
<button type="submit" class="button button-tertiary" id="saveSettingsDialog">Save</button>
116+
<button type="button" class="button button-secondary" id="resetPaletteSettings">Reset</button>
117+
<button type="submit" class="button button-tertiary" id="savePaletteSettings">Save</button>
103118
</div>
104119
</div>
105120

@@ -342,7 +357,7 @@ <h2>🖼️ Create Pixels image</h2>
342357
<h3>Image settings</h3>
343358

344359
<div class="dialog-settings-group">
345-
<label for="color1">Score 1</label>
360+
<!-- <label for="color1">Score 1</label>
346361
<input type="color" id="color1">
347362
348363
<label for="color2">Score 2</label>
@@ -355,7 +370,7 @@ <h3>Image settings</h3>
355370
<input type="color" id="color4">
356371
357372
<label for="color5">Score 5</label>
358-
<input type="color" id="color5">
373+
<input type="color" id="color5"> -->
359374

360375
<label for="colorEmpty">Background</label>
361376
<input type="color" id="colorEmpty">
@@ -392,9 +407,7 @@ <h3>Image settings</h3>
392407
</div>
393408

394409
<div class="dialog-buttons">
395-
<button type="button" class="button button-secondary" id="resetSettingsDialog">Reset</button>
396-
<button type="button" class="button" id="cancelSettingsDialog">Cancel</button>
397-
<button type="submit" class="button button-tertiary" id="saveSettingsDialog">Save</button>
410+
<button type="submit" class="button button-tertiary" id="saveDialogSettings">Save</button>
398411
</div>
399412
</div>
400413
</dialog>
@@ -495,8 +508,8 @@ <h2>📅 Search Pixels by date</h2>
495508
<img src="assets/pixels_memories_logo.png" alt="Pixels Memories">
496509
</a>
497510
</div>
498-
<div class="version">Version: v1.5.18</div>
499-
<div class="version">Last update: 2025-09-16</div>
511+
<div class="version">Version: v1.5.19</div>
512+
<div class="version">Last update: 2025-09-22</div>
500513
</footer>
501514

502515
<!-- Charts.js for graphs -->

scripts/card.js

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,13 @@ let calendar = null;
1212

1313

1414
async function load_colored_score_SVG(score) {
15-
if (!score || (typeof score !== "number") || (score < 1) || (score > 5)) {
16-
return document.createElement("span");
17-
}
18-
const path = `assets/pixels/score_${score}.svg`;
19-
const res = await fetch(path);
20-
const text = await res.text();
21-
const parser = new DOMParser();
22-
const doc = parser.parseFromString(text, "image/svg+xml");
23-
const svg = doc.querySelector("svg");
24-
25-
const color = png_settings.colors[score]
26-
27-
svg.querySelectorAll("[fill='currentColor']").forEach(el => el.setAttribute("fill", color));
28-
return svg;
15+
if (!Number.isInteger(score) || score < 1 || score > 5) return document.createElement("span");
16+
const res = await fetch(`assets/pixels/score_${score}.svg`);
17+
const text = await res.text();
18+
const doc = new DOMParser().parseFromString(text, "image/svg+xml");
19+
const svg = doc.querySelector("svg");
20+
svg.style.color = png_settings.colors[score];
21+
return svg;
2922
}
3023

3124

scripts/png.js

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ const div_pixel_export_container = document.querySelector("#pixelGridExportConta
22

33
const dialog_settings = document.querySelector("#dialogSettings");
44
const btn_open_dialog_settings = document.querySelector("#openImageSettingsDialog");
5-
const btn_reset_dialog_settings = document.querySelector("#resetSettingsDialog");
6-
const btn_cancel_dialog_settings = document.querySelector("#cancelSettingsDialog");
7-
const btn_save_dialog_settings = document.querySelector("#saveSettingsDialog");
5+
const btn_reset_palette_settings = document.querySelector("#resetPaletteSettings");
6+
const btn_save_palette_settings = document.querySelector("#savePaletteSettings");
7+
const btn_save_dialog_settings = document.querySelector("#saveDialogSettings");
88
const btn_generate_png = document.querySelector("#btnGeneratePixelGrid");
99
const btn_download_png = document.querySelector("#btnDownloadPixelGrid");
1010
const div_result_png = document.querySelector("#pixelGridResults");
@@ -75,6 +75,9 @@ function set_image_settings(settings) {
7575
setting_color3.value = settings.colors[3];
7676
setting_color4.value = settings.colors[4];
7777
setting_color5.value = settings.colors[5];
78+
for (let i = 1; i <= 5; i++) {
79+
update_svg_color(i, settings.colors[i]);
80+
}
7881
setting_colorEmpty.value = settings.colors.empty;
7982
setting_squareSize.value = settings.squareSize.toString();
8083
setting_borderSize.value = settings.borderSize.toString();
@@ -656,12 +659,13 @@ btn_open_dialog_settings.addEventListener("click", () => {
656659
open_dialog_settings();
657660
});
658661

659-
btn_reset_dialog_settings.addEventListener("click", () => {
660-
set_image_settings(png_default_settings);
662+
btn_reset_palette_settings.addEventListener("click", () => {
663+
png_settings.colors = { ...png_default_settings.colors };
664+
close_dialog_settings();
661665
});
662666

663-
btn_cancel_dialog_settings.addEventListener("click", () => {
664-
close_dialog_settings();
667+
btn_save_palette_settings.addEventListener("click", () => {
668+
close_dialog_settings(save=true);
665669
});
666670

667671
btn_save_dialog_settings.addEventListener("click", () => {
@@ -704,6 +708,6 @@ div_pixel_export_container.addEventListener("keydown", function (e) {
704708
dialog_settings.addEventListener("keydown", function (e) {
705709
if (e.key === "Enter") {
706710
e.preventDefault();
707-
btn_save_dialog_settings.click();
711+
btn_save_palette_settings.click();
708712
}
709713
});

scripts/statistics.js

Lines changed: 65 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -3,58 +3,35 @@ let scores_pie_chart_instance = null;
33

44

55

6-
7-
// Loader : NE PAS écraser les fill="currentColor"
8-
async function load_score_SVG(score) {
9-
if (!Number.isInteger(score) || score < 1 || score > 5) return document.createElement("span");
10-
const res = await fetch(`assets/pixels/score_${score}.svg`);
11-
const text = await res.text();
12-
const doc = new DOMParser().parseFromString(text, "image/svg+xml");
13-
const svg = doc.querySelector("svg");
14-
return svg;
6+
async function update_svg_color(score, color) {
7+
const svg = document.querySelector(`#color${score}`).parentElement.querySelector("svg");
8+
if (svg) {
9+
svg.style.color = color;
10+
}
11+
png_settings.colors[score] = color;
1512
}
1613

1714

18-
async function load_colored_score_SVG(score) {
19-
const svg = await load_score_SVG(score);
20-
svg.style.color = png_settings.colors[score];
21-
return svg;
22-
}
23-
2415
async function setup_palette_settings() {
25-
const colors = get_image_settings().colors;
26-
const grid = document.createElement("div");
27-
grid.className = "palette-grid";
28-
palette_grid.innerHTML = "";
29-
30-
for (let score = 1; score <= 5; score++) {
31-
const cell = document.createElement("div");
32-
cell.className = "color-cell";
33-
34-
const svg = await load_score_SVG(score);
35-
svg.classList.add("color-icon");
36-
svg.style.color = colors[score];
37-
38-
const input = document.createElement("input");
39-
input.type = "color";
40-
input.id = `color${score}`;
41-
input.value = colors[score];
42-
input.className = "color-picker-overlay";
43-
44-
input.addEventListener("input", () => {
45-
svg.style.color = input.value;
46-
png_settings.colors[score] = input.value;
47-
});
16+
const colors = get_image_settings().colors;
4817

49-
cell.appendChild(svg);
50-
cell.appendChild(input);
51-
grid.appendChild(cell);
52-
}
18+
for (let score = 1; score <= 5; score++) {
19+
const cell = document.querySelector(`#color${score}`).parentElement;
20+
const input = document.getElementById(`color${score}`);
21+
22+
input.classList.add("color-picker-overlay");
5323

54-
palette_grid.appendChild(grid);
55-
}
24+
const svg = await load_colored_score_SVG(score);
25+
svg.classList.add("color-icon");
26+
svg.style.color = colors[score];
5627

28+
input.addEventListener("input", () => {
29+
update_svg_color(score, input.value);
30+
});
5731

32+
cell.appendChild(svg);
33+
}
34+
}
5835

5936

6037
function calculate_streaks() {
@@ -68,7 +45,7 @@ function calculate_streaks() {
6845
const [year, month, day] = dateStr.split("-").map(Number);
6946
return new Date(Date.UTC(year, month - 1, day));
7047
})
71-
.sort((a, b) => a - b);
48+
.sort((a, b) => a - b);
7249

7350
let bestStreak = 1;
7451
let currentStreak = 1;
@@ -77,7 +54,7 @@ function calculate_streaks() {
7754
const previous_date = dates[i - 1];
7855
const current_date = dates[i];
7956
const diffDays = (current_date - previous_date) / (1000 * 60 * 60 * 24);
80-
57+
8158
currentStreak++;
8259
if (diffDays !== 1) {
8360
currentStreak = 1;
@@ -105,31 +82,31 @@ function calculate_and_display_stats() {
10582
});
10683

10784
stats_array = [
108-
{title: "Number of Pixels", value: `<p>${current_data.filter(entry => entry.scores.length > 0).length}</p>`},
109-
{title: "Average score", value: `<p>${average(allScores).toFixed(2)}</p>`},
110-
{title: "Streaks", value: `<p>Last: ${streaks.currentStreak} | Best: ${streaks.bestStreak}</p>`},
111-
{title: "Score distribution", value: "<canvas title='Update your colors in the \"Export Pixel image\" settings' id='scoresPieChart' class='pie-chart' width='100' height='100'></canvas>"},
85+
{ title: "Number of Pixels", value: `<p>${current_data.filter(entry => entry.scores.length > 0).length}</p>` },
86+
{ title: "Average score", value: `<p>${average(allScores).toFixed(2)}</p>` },
87+
{ title: "Streaks", value: `<p>Last: ${streaks.currentStreak} | Best: ${streaks.bestStreak}</p>` },
88+
{ title: "Score distribution", value: "<canvas title='Update your colors in the \"Export Pixel image\" settings' id='scoresPieChart' class='pie-chart' width='100' height='100'></canvas>" },
11289
];
11390

114-
stats_container.innerHTML = stats_array.map(({title, value}) => `
91+
stats_container.innerHTML = stats_array.map(({ title, value }) => `
11592
<div class="stat-card">
11693
<h3>${title}</h3>
11794
${value}
11895
</div>
11996
`).join("");
120-
97+
12198
setup_palette_settings();
12299
create_scores_pie_chart();
123100
}
124101

125102

126103
async function create_scores_pie_chart() {
127104
const rawScores = current_data
128-
.flatMap(entry => entry.scores)
129-
.reduce((acc, score) => {
130-
acc[score] = (acc[score] || 0) + 1;
131-
return acc;
132-
}, {});
105+
.flatMap(entry => entry.scores)
106+
.reduce((acc, score) => {
107+
acc[score] = (acc[score] || 0) + 1;
108+
return acc;
109+
}, {});
133110

134111
const scoresCount = Object.keys(rawScores).map(Number);
135112
const values = scoresCount.map(score => rawScores[score] || 0);
@@ -308,9 +285,9 @@ function get_word_frequency() {
308285
searchTextLower = searchTextLower.replace(/\*\*/g, "*");
309286
}
310287
const pattern = searchTextLower
311-
.split("*")
312-
.map(escape_regex)
313-
.join("(\\w+)");
288+
.split("*")
289+
.map(escape_regex)
290+
.join("(\\w+)");
314291
searchPattern = new RegExp(pattern, "gi");
315292
}
316293
}
@@ -342,24 +319,24 @@ function get_word_frequency() {
342319
let words = wordRegexSearch
343320
? []
344321
: notesLower
345-
.replace(/[^\p{L}\p{N}\p{Extended_Pictographic}\u200D\uFE0F]+/gu, " ")
346-
.split(/\s+/)
347-
.filter(word =>
348-
(word.replace(/[^a-zA-Z]/g, "").length >= 3 || /\p{Extended_Pictographic}/u.test(word)) && // Word is at least 3 letters long or emoji
349-
(!STOP_WORDS.has(word)) && // Word is not a stop word
350-
((searchWords.length === 0) || (word !== searchTextLower)) && // Word is not the search term (avoid duplicates)
351-
(
352-
(searchWords.length === 0) || // Either no search words or
353-
searchWords.some((sw, i) => {
354-
if (i === searchWords.length - 1) { // Last search word can be a prefix or exact match
355-
return (word.startsWith(sw) || sw.startsWith(word));
356-
}
357-
else { // Other search words must be exact matches
358-
return (word === sw);
359-
}
360-
})
361-
)
362-
);
322+
.replace(/[^\p{L}\p{N}\p{Extended_Pictographic}\u200D\uFE0F]+/gu, " ")
323+
.split(/\s+/)
324+
.filter(word =>
325+
(word.replace(/[^a-zA-Z]/g, "").length >= 3 || /\p{Extended_Pictographic}/u.test(word)) && // Word is at least 3 letters long or emoji
326+
(!STOP_WORDS.has(word)) && // Word is not a stop word
327+
((searchWords.length === 0) || (word !== searchTextLower)) && // Word is not the search term (avoid duplicates)
328+
(
329+
(searchWords.length === 0) || // Either no search words or
330+
searchWords.some((sw, i) => {
331+
if (i === searchWords.length - 1) { // Last search word can be a prefix or exact match
332+
return (word.startsWith(sw) || sw.startsWith(word));
333+
}
334+
else { // Other search words must be exact matches
335+
return (word === sw);
336+
}
337+
})
338+
)
339+
);
363340

364341
// If searchPattern is defined, find words that match the pattern
365342
if (searchPattern) {
@@ -403,7 +380,7 @@ function get_word_frequency() {
403380
words_data[word].count += 1;
404381
words_data[word].scores.push(average_score);
405382
});
406-
383+
407384
});
408385

409386

@@ -432,18 +409,18 @@ async function create_word_frequency_section() {
432409
if (words_filtered.length > 0) {
433410
word_freq_container.innerHTML = `
434411
${words_filtered.map(word => {
435-
let isWordSearched = false;
436-
if (wordSearchText) {
437-
isWordSearched = (normalize_string(word.word) === normalize_string(wordSearchText)) ||
438-
wordSearchText.split(/\s+/)
439-
.some(term => normalize_string(word.word) === (normalize_string(term)));
440-
}
441-
return `<div class="word-card ${isWordSearched ? "searched-word" : ""}">
412+
let isWordSearched = false;
413+
if (wordSearchText) {
414+
isWordSearched = (normalize_string(word.word) === normalize_string(wordSearchText)) ||
415+
wordSearchText.split(/\s+/)
416+
.some(term => normalize_string(word.word) === (normalize_string(term)));
417+
}
418+
return `<div class="word-card ${isWordSearched ? "searched-word" : ""}">
442419
<h4>${capitalize(word.word)}</h4>
443420
<p title="Number of apearance">count: ${wordDisplayPercentage ? (100 * word.count / current_data.length).toFixed(1) + "%" : word.count}</p>
444421
<p title="Average score">score: ${(word.avg_score).toFixed(2)}</p>
445422
</div>`
446-
}
423+
}
447424
).join("")}`
448425
}
449426
else { word_freq_container.innerHTML = "<p>No word frequency data available. Try to change search words, or lower the minimum count.</p>"; }
@@ -489,7 +466,7 @@ async function update_wordcloud() {
489466
if (!wordOrderByScore) {
490467
const minimumCount = words[words.length - 1][1];
491468
adjustedWords = words.map(([word, count]) => {
492-
const adjustedCount = Math.pow(count - minimumCount + 1, (3 / (wordcloudCompression+2))) + 1;
469+
const adjustedCount = Math.pow(count - minimumCount + 1, (3 / (wordcloudCompression + 2))) + 1;
493470
return [word, adjustedCount];
494471
});
495472
}

0 commit comments

Comments
 (0)