diff --git a/README.md b/README.md
index 2a12343..525c366 100644
--- a/README.md
+++ b/README.md
@@ -3,5 +3,6 @@ An **AFJROTC & CAP** rack builder for ribbons, badges, ranks, arcs, tapes, etc.
Vist the website [here](https://dotwith.github.io/afjrotc-ribbon-rack-builder/).
## Cite
+- [Wikipedia, USAF](https://en.wikipedia.org/wiki/United_States_Air_Force).
- [Wikipedia, AFJROTC](https://en.m.wikipedia.org/wiki/Junior_Reserve_Officers%27_Training_Corps).
- [Wikipedia, CAP](https://en.wikipedia.org/wiki/Awards_and_decorations_of_the_Civil_Air_Patrol).
diff --git a/afjrotc/app.html b/afjrotc/app.html
index fd7bacb..861d9ae 100644
--- a/afjrotc/app.html
+++ b/afjrotc/app.html
@@ -70,6 +70,7 @@
Your Ribbon Rack
+
diff --git a/afjrotc/app.js b/afjrotc/app.js
index a65fb89..878d72a 100644
--- a/afjrotc/app.js
+++ b/afjrotc/app.js
@@ -1,19 +1,32 @@
-let selectedRibbons = [];
-let selectedRanks = [];
-let selectedBadges = [];
let selectedArcs = [];
+let selectedBadges = [];
+let selectedRanks = [];
+let selectedRibbons = [];
+
+let arcsMeta = {};
+let ranksMeta = {};
+let badgesMeta = {};
+let ribbonsMeta = {};
+document.addEventListener('DOMContentLoaded', () => {
+ restoreStateFromURL(); // Restore the state when the page loads
+});
+
+// Fetching and populating arcs
fetch('arcs/_meta.json')
.then(response => response.json())
.then(data => {
const arcSelection = document.getElementById('arcSelection');
data.forEach(arc => {
+ arcsMeta[arc.id] = arc.path;
+
const label = document.createElement('label');
label.classList.add('badge-checkbox');
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
- checkbox.dataset.arc = arc.path;
+ //checkbox.dataset.arc = arc.path;
+ checkbox.dataset.arc = arc.id;
const img = document.createElement('img');
img.src = `arcs/${arc.path}`;
@@ -28,24 +41,32 @@ fetch('arcs/_meta.json')
arcSelection.appendChild(label);
});
- // Update selected arcs when checkboxes are changed
const arcCheckboxes = document.querySelectorAll('input[data-arc]');
arcCheckboxes.forEach(checkbox => {
- checkbox.addEventListener('change', updateSelectedRibbons);
+ checkbox.addEventListener('change', () => {
+ updateSelectedRibbons();
+ updateURL();
+ });
});
+
+ restoreStateFromURL(); // Restore the state after rendering
});
+// Fetching and populating badges
fetch('badges/_meta.json')
.then(response => response.json())
.then(data => {
const badgeSelection = document.getElementById('badgeSelection');
data.forEach(badge => {
+ badgesMeta[badge.id] = badge.path;
+
const label = document.createElement('label');
label.classList.add('badge-checkbox');
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
- checkbox.dataset.badge = badge.path;
+ //checkbox.dataset.badge = badge.path;
+ checkbox.dataset.badge = badge.id;
const img = document.createElement('img');
img.src = `badges/${badge.path}`;
@@ -60,23 +81,31 @@ fetch('badges/_meta.json')
badgeSelection.appendChild(label);
});
- // Update selected badges when checkboxes are changed
const badgeCheckboxes = document.querySelectorAll('input[data-badge]');
badgeCheckboxes.forEach(checkbox => {
- checkbox.addEventListener('change', updateSelectedRibbons);
+ checkbox.addEventListener('change', () => {
+ updateSelectedRibbons();
+ updateURL();
+ });
});
+
+ restoreStateFromURL(); // Restore the state after rendering
});
+// Fetching and populating ranks
fetch('ranks/_meta.json')
.then(response => response.json())
.then(data => {
function makeRanks(data, kind, rankSelection) {
data[kind].forEach(rank => {
+ ranksMeta[rank.id] = `${kind}/${rank.id}`;
+
const label = document.createElement('label');
label.classList.add('rank-radio');
const radio = document.createElement('input');
- radio.dataset.rank = `${kind}/${rank.id}`;
+ //radio.dataset.rank = `${kind}/${rank.id}`;
+ radio.dataset.rank = rank.id;
radio.type = 'radio';
radio.name = 'rank';
radio.value = rank.id;
@@ -100,18 +129,25 @@ fetch('ranks/_meta.json')
makeRanks(data, 'enlisted', rankSelection);
makeRanks(data, 'officer', rankSelection);
- // Update selected ranks when radios are changed
const ranksRadios = document.querySelectorAll('input[data-rank]');
ranksRadios.forEach(radio => {
- radio.addEventListener('change', updateSelectedRibbons);
+ radio.addEventListener('change', () => {
+ updateSelectedRibbons();
+ updateURL();
+ });
});
+
+ restoreStateFromURL(); // Restore the state after rendering
});
+// Fetching and populating ribbons
fetch('ribbons/_meta.json')
.then(response => response.json())
.then(data => {
const ribbonSelection = document.getElementById('ribbonSelection');
data.forEach(ribbon => {
+ ribbonsMeta[ribbon.id] = ribbon.uses_stars;
+
const label = document.createElement('label');
label.classList.add('ribbon-option');
@@ -132,11 +168,11 @@ fetch('ribbons/_meta.json')
if (ribbon.uses_stars) {
devices.min = 0;
devices.max = 3; // none, bronze, silver, golden
- devices.dataset.uses_stars = true;
+ //devices.dataset.uses_stars = true;
} else {
devices.min = 0;
devices.max = 25;
- devices.dataset.uses_stars = false;
+ //devices.dataset.uses_stars = false;
}
devices.dataset.ribbon = ribbon.id;
@@ -148,30 +184,37 @@ fetch('ribbons/_meta.json')
ribbonSelection.appendChild(label);
});
- // Update selected ribbons when checkboxes are changed
const ribbonCheckboxes = document.querySelectorAll('input[data-ribbon]');
ribbonCheckboxes.forEach(checkbox => {
- checkbox.addEventListener('change', updateSelectedRibbons);
+ checkbox.addEventListener('change', () => {
+ updateSelectedRibbons();
+ updateURL();
+ });
});
const oakNumberInputs = document.querySelectorAll('.ribbon-option-input');
oakNumberInputs.forEach(input => {
- input.addEventListener('input', updateSelectedRibbons);
+ input.addEventListener('input', () => {
+ updateSelectedRibbons();
+ updateURL();
+ });
});
+
+ restoreStateFromURL(); // Restore the state after rendering
});
function updateSelectedRibbons() {
selectedArcs = Array.from(document.querySelectorAll('input[data-arc]'))
.filter(checkbox => checkbox.checked)
- .map(checkbox => checkbox.dataset.arc);
+ .map(checkbox => parseInt(checkbox.dataset.arc));
selectedBadges = Array.from(document.querySelectorAll('input[data-badge]'))
.filter(checkbox => checkbox.checked)
- .map(checkbox => checkbox.dataset.badge);
+ .map(checkbox => parseInt(checkbox.dataset.badge));
selectedRanks = Array.from(document.querySelectorAll('input[data-rank]'))
.filter(radio => radio.checked)
- .map(radio => radio.dataset.rank);
+ .map(radio => parseInt(radio.dataset.rank));
selectedRibbons = Array.from(document.querySelectorAll('input[data-ribbon]'))
.filter(checkbox => checkbox.checked)
@@ -179,13 +222,79 @@ function updateSelectedRibbons() {
const ribbonId = parseInt(checkbox.dataset.ribbon);
const devicesElement = document.querySelector(`.ribbon-option-input[data-ribbon="${checkbox.dataset.ribbon}"]`);
const devices = parseInt(devicesElement.value);
- const usesStars = devicesElement.dataset.uses_stars === 'true';
- return [ribbonId, devices, usesStars];
+ //const usesStars = devicesElement.dataset.uses_stars === 'true';
+ return [ribbonId, devices];
});
updateRack();
}
+function updateURL() {
+ const params = new URLSearchParams();
+
+ // Add arcs, badges, ranks, and ribbons to the URL
+ if (selectedArcs.length > 0) params.set('arcs', selectedArcs.join(','));
+ if (selectedBadges.length > 0) params.set('badges', selectedBadges.join(','));
+ if (selectedRanks.length > 0) params.set('ranks', selectedRanks.join(','));
+ if (selectedRibbons.length > 0) {
+ const ribbonsParam = selectedRibbons.map(ribbon => `${ribbon[0]}-${ribbon[1]}`).join(',');
+ params.set('ribbons', ribbonsParam);
+ }
+
+ // Update the URL
+ window.history.replaceState({}, '', `${window.location.pathname}?${params}`);
+}
+
+function restoreStateFromURL() {
+ const params = new URLSearchParams(window.location.search);
+
+ // Restore arcs
+ const arcs = params.get('arcs');
+ if (arcs) {
+ arcs.split(',').forEach(arc => {
+ const checkbox = document.querySelector(`input[data-arc="${arc}"]`);
+ if (checkbox) checkbox.checked = true;
+ });
+ }
+
+ // Restore badges
+ const badges = params.get('badges');
+ if (badges) {
+ badges.split(',').forEach(badge => {
+ const checkbox = document.querySelector(`input[data-badge="${badge}"]`);
+ if (checkbox) checkbox.checked = true;
+ });
+ }
+
+ // Restore ranks
+ const ranks = params.get('ranks');
+ if (ranks) {
+ ranks.split(',').forEach(rank => {
+ const radio = document.querySelector(`input[data-rank="${rank}"]`);
+ if (radio) radio.checked = true;
+ });
+ }
+
+ // Restore ribbons
+ const ribbons = params.get('ribbons');
+ if (ribbons) {
+ ribbons.split(',').forEach(ribbon => {
+ const [ribbonId, devices, usesStars] = ribbon.split('-');
+ const checkbox = document.querySelector(`input[data-ribbon="${ribbonId}"]`);
+ if (checkbox) checkbox.checked = true;
+
+ const devicesElement = document.querySelector(`.ribbon-option-input[data-ribbon="${ribbonId}"]`);
+ if (devicesElement) {
+ devicesElement.value = devices;
+ ///devicesElement.dataset.uses_stars = usesStars === 'true';
+ }
+ });
+ }
+
+ // Update the selected ribbons after restoring the state
+ updateSelectedRibbons();
+}
+
function updateRack() {
const rack = document.getElementById('rack');
rack.innerHTML = '';
@@ -197,35 +306,53 @@ function updateRack() {
const badgesRow = [];
- selectedRanks.forEach(rank => {
- if (rank == "enlisted/1") {
+ selectedRanks.forEach(rankId => {
+ const rankElement = document.createElement('img');
+ const rankPath = ranksMeta[rankId]; // Use the lookup to get the path
+
+ if (rankPath == "enlisted/1") {
return;
- }
+ } // TODO: Fix
- const rankElement = document.createElement('img');
- rankElement.src = `ranks/${rank}.svg`;
- rankElement.className = 'rack-badge-img';
- rankElement.setAttribute('crossorigin', 'anonymous');
+ if (rankPath) {
+ rankElement.src = `ranks/${rankPath}.svg`;
+ rankElement.className = 'rack-badge-img';
+ rankElement.setAttribute('crossorigin', 'anonymous');
- badgesRow.push(rankElement);
+ badgesRow.push(rankElement);
+ } else {
+ console.error(`Rank with ID ${rankId} not found in meta data.`);
+ }
});
- selectedBadges.forEach(badge => {
+ selectedBadges.forEach(badgeId => {
const badgeElement = document.createElement('img');
- badgeElement.src = `badges/${badge}`;
- badgeElement.className = 'rack-badge-img';
- badgeElement.setAttribute('crossorigin', 'anonymous');
+ const badgePath = badgesMeta[badgeId]; // Use the lookup to get the path
+
+ if (badgePath) {
+ badgeElement.src = `badges/${badgePath}`;
+ badgeElement.className = 'rack-badge-img';
+ badgeElement.setAttribute('crossorigin', 'anonymous');
- badgesRow.push(badgeElement);
+ badgesRow.push(badgeElement);
+ } else {
+ console.error(`Badge with ID ${badgeId} not found in meta data.`);
+ }
});
- selectedArcs.forEach(badge => {
+ selectedArcs.forEach(arcId => {
const arcElement = document.createElement('img');
- arcElement.src = `arcs/${badge}`;
- arcElement.className = 'rack-badge-img';
- arcElement.setAttribute('crossorigin', 'anonymous');
+ const arcPath = arcsMeta[arcId]; // Use the lookup to get the path
- badgesRow.push(arcElement);
+ if (arcPath) {
+ arcElement.src = `arcs/${arcPath}`;
+ arcElement.className = 'rack-badge-img';
+ arcElement.setAttribute('crossorigin', 'anonymous');
+
+ badgesRow.push(arcElement);
+ } else {
+ console.error(`Arc with ID ${arcId} not found in meta data.`);
+ }
});
// Loop through the ribbons in the current row
@@ -241,6 +368,8 @@ function updateRack() {
// Loop through the ribbons in the current row
row.forEach(ribbon => {
+ const uses_stars = ribbonsMeta[ribbon[0]]; // Use the lookup to get the path
+
const ribbonContainer = document.createElement('div');
ribbonContainer.className = 'rack-ribbon';
@@ -255,7 +384,7 @@ function updateRack() {
oakContainer.className = 'ribbon-oak';
if (ribbon[1] > 0) {
- if (ribbon[2] == false) {
+ if (!uses_stars) {
const numberOfGoldenOaks = Math.floor(ribbon[1] / 5 / 5);
const numberOfSilverOaks = Math.floor((ribbon[1] % 25) / 5);
const remainderBronzeOaks = ribbon[1] % 5;
@@ -351,6 +480,42 @@ function sortRibbons(ribbons) {
return rows;
}
+function resetSelections() {
+ // Reset arcs
+ const arcCheckboxes = document.querySelectorAll('input[data-arc]');
+ arcCheckboxes.forEach(checkbox => {
+ checkbox.checked = false;
+ });
+
+ // Reset badges
+ const badgeCheckboxes = document.querySelectorAll('input[data-badge]');
+ badgeCheckboxes.forEach(checkbox => {
+ checkbox.checked = false;
+ });
+
+ // Reset ranks
+ const rankRadios = document.querySelectorAll('input[data-rank]');
+ rankRadios.forEach(radio => {
+ radio.checked = false;
+ });
+
+ // Reset ribbons
+ const ribbonCheckboxes = document.querySelectorAll('input[data-ribbon]');
+ ribbonCheckboxes.forEach(checkbox => {
+ checkbox.checked = false;
+ });
+
+ // Reset ribbon device sliders
+ const oakNumberInputs = document.querySelectorAll('.ribbon-option-input');
+ oakNumberInputs.forEach(input => {
+ input.value = 0;
+ });
+
+ // Update the selections and URL after resetting
+ updateSelectedRibbons();
+ updateURL();
+}
+
function saveRackPng() {
html2canvas(document.getElementById('rack'), { backgroundColor: null, scale: 4 }).then(canvas => {
const link = document.createElement('a');
diff --git a/afjrotc/arcs/_meta.json b/afjrotc/arcs/_meta.json
index 79d3dc4..6c3f499 100644
--- a/afjrotc/arcs/_meta.json
+++ b/afjrotc/arcs/_meta.json
@@ -1,85 +1,106 @@
[
{
+ "id": 1,
"path": "orienteering_arc.svg",
"name": "Orienteering Arc"
},
{
+ "id": 2,
"path": "raider_arc.svg",
"name": "Raider Arc"
},
{
+ "id": 3,
"path": "drill_team_arc.svg",
"name": "Drill Team Arc"
},
{
+ "id": 4,
"path": "staff_arc.svg",
"name": "Staff Arc"
},
{
+ "id": 5,
"path": "color_guard_arc.svg",
"name": "Color Guard Arc"
},
{
+ "id": 6,
"path": "flag_detail_arc.svg",
"name": "Flag Detail Arc"
},
{
+ "id": 7,
"path": "parade_arc.svg",
"name": "Parade Arc"
},
{
+ "id": 8,
"path": "honor_guard_arc.svg",
"name": "Honor Guard Arc"
},
{
+ "id": 9,
"path": "rifle_team_arc.svg",
"name": "Rifle Team Arc"
},
{
+ "id": 10,
"path": "community_service_arc.svg",
"name": "Community Service Arc"
},
{
+ "id": 11,
"path": "sabre_guard_arc.svg",
"name": "Sabre Guard Arc"
},
{
+ "id": 12,
"path": "academics_arc.svg",
"name": "Academics Arc"
},
{
+ "id": 13,
"path": "cyberpatriot_arc.svg",
"name": "Cyberpatriot Arc"
},
{
+ "id": 14,
"path": "rappelling_arc.svg",
"name": "Rappelling Arc"
},
{
+ "id": 15,
"path": "summer_camp_arc.svg",
"name": "Summer Camp Arc"
},
{
+ "id": 16,
"path": "cadet_challenge_arc.svg",
"name": "Cadet Challenge Arc"
},
{
+ "id": 17,
"path": "jclc_arc.svg",
"name": "JCLC Arc"
},
{
+ "id": 18,
"path": "jlab_arc.svg",
"name": "JLAB Arc"
},
{
+ "id": 19,
"path": "honor_roll_arc.svg",
"name": "Honor Roll Arc"
},
{
+ "id": 20,
"path": "leadership_arc.svg",
"name": "Leadership Arc"
},
{
+ "id": 21,
"path": "pt_team_arc.svg",
"name": "P.T. Team Arc"
}
diff --git a/afjrotc/badges/_meta.json b/afjrotc/badges/_meta.json
index 62b20e6..d330d87 100644
--- a/afjrotc/badges/_meta.json
+++ b/afjrotc/badges/_meta.json
@@ -1,77 +1,96 @@
[
{
+ "id": 1,
"path": "01_officers_service_cap_insignia.svg",
"name": "HAP Arnold Optional Enlisted/Officer Service CAP Insignia"
},
{
+ "id": 2,
"path": "02_officers_service_cap_insignia.svg",
"name": "Officers Service CAP Insignia"
},
{
+ "id": 3,
"path": "model_rocketry_badge.svg",
"name": "Model Rocketry Badge"
},
{
+ "id": 4,
"path": "khhs_badge.svg",
"name": "Kitty Hawk Honor Society Badge"
},
{
+ "id": 5,
"path": "distinguished_cadet_badge.svg",
"name": "Distinguished Cadet Badge"
},
{
+ "id": 6,
"path": "ground_school_badge.svg",
"name": "Ground School Badge"
},
{
+ "id": 7,
"path": "flight_solo_badge.svg",
"name": "Flight Solo Badge"
},
{
+ "id": 8,
"path": "flight_certifcate_badge.svg",
"name": "Flight Certifcate Badge"
},
{
+ "id": 9,
"path": "unmanned_aircraft_badge.svg",
"name": "Unmanned Aircraft Badge"
},
{
+ "id": 10,
"path": "marksmanship_shield_badge.svg",
"name": "Marksmanship Shield Badge"
},
{
+ "id": 11,
"path": "apt_badge.svg",
"name": "APT Badge"
},
{
+ "id": 12,
"path": "cyberpatriot_badge.svg",
"name": "CyberPatriot Badge"
},
{
+ "id": 13,
"path": "aef_badge.svg",
"name": "AEF Badge"
},
{
+ "id": 14,
"path": "cmp_junior_gold_distinguished_badge.svg",
"name": "CMP Gold Junior Distinguished Badge"
},
{
+ "id": 15,
"path": "cmp_junior_silver_distinguished_badge.svg",
"name": "CMP Silver Junior Distinguished Badge"
},
{
+ "id": 16,
"path": "cmp_junior_bronze_distinguished_badge.svg",
"name": "CMP Bronze Junior Distinguished Badge"
},
{
+ "id": 17,
"path": "ms_marksman_badge.svg",
"name": "Marksmanship Marksman Badge"
},
{
+ "id": 18,
"path": "ms_sharpshooter_badge.svg",
"name": "Marksmanship Sharpshooter Badge"
},
{
+ "id": 19,
"path": "ms_expert_badge.svg",
"name": "Marksmanship Expert Badge"
}
diff --git a/cap_cadet/ranks/_meta.json b/cap_cadet/ranks/_meta.json
index 560feaf..a566de4 100644
--- a/cap_cadet/ranks/_meta.json
+++ b/cap_cadet/ranks/_meta.json
@@ -1,24 +1,24 @@
{
"enlisted": [
- { "id": 1, "name": "Cadet Airman Basic" },
- { "id": 2, "name": "Cadet Airman" },
- { "id": 3, "name": "Cadet Airman First Class" },
- { "id": 4, "name": "Cadet Senior Airman" },
- { "id": 5, "name": "Cadet Staff Sergeant" },
- { "id": 6, "name": "Cadet Technical Sergeant" },
- { "id": 7, "name": "Cadet Master Sergeant" },
- { "id": 8, "name": "Cadet Senior Master Sergeant" },
- { "id": 9, "name": "Cadet Chief Master Sergeant" },
- { "id": 10.7, "name": "Cadet First Sergeant" },
- { "id": 10.8, "name": "Cadet First Sergeant" },
- { "id": 10.9, "name": "Cadet First Sergeant" }
+ { "id": "1", "name": "Cadet Airman Basic" },
+ { "id": "2", "name": "Cadet Airman" },
+ { "id": "3", "name": "Cadet Airman First Class" },
+ { "id": "4", "name": "Cadet Senior Airman" },
+ { "id": "5", "name": "Cadet Staff Sergeant" },
+ { "id": "6", "name": "Cadet Technical Sergeant" },
+ { "id": "7", "name": "Cadet Master Sergeant" },
+ { "id": "8", "name": "Cadet Senior Master Sergeant" },
+ { "id": "9", "name": "Cadet Chief Master Sergeant" },
+ { "id": "10A", "name": "Cadet First Sergeant" },
+ { "id": "10B", "name": "Cadet First Sergeant" },
+ { "id": "10C", "name": "Cadet First Sergeant" }
],
"officer": [
- { "id": 1, "name": "Cadet Second Lieutenant" },
- { "id": 2, "name": "Cadet First Lieutenant" },
- { "id": 3, "name": "Cadet Captain" },
- { "id": 4, "name": "Cadet Major" },
- { "id": 5, "name": "Cadet Lieutenant Colonel" },
- { "id": 6, "name": "Cadet Colonel" }
+ { "id": "1", "name": "Cadet Second Lieutenant" },
+ { "id": "2", "name": "Cadet First Lieutenant" },
+ { "id": "3", "name": "Cadet Captain" },
+ { "id": "4", "name": "Cadet Major" },
+ { "id": "5", "name": "Cadet Lieutenant Colonel" },
+ { "id": "6", "name": "Cadet Colonel" }
]
}
\ No newline at end of file
diff --git a/cap_cadet/ranks/enlisted/10.7.svg b/cap_cadet/ranks/enlisted/10A.svg
similarity index 100%
rename from cap_cadet/ranks/enlisted/10.7.svg
rename to cap_cadet/ranks/enlisted/10A.svg
diff --git a/cap_cadet/ranks/enlisted/10.8.svg b/cap_cadet/ranks/enlisted/10B.svg
similarity index 100%
rename from cap_cadet/ranks/enlisted/10.8.svg
rename to cap_cadet/ranks/enlisted/10B.svg
diff --git a/cap_cadet/ranks/enlisted/10.9.svg b/cap_cadet/ranks/enlisted/10C.svg
similarity index 100%
rename from cap_cadet/ranks/enlisted/10.9.svg
rename to cap_cadet/ranks/enlisted/10C.svg
diff --git a/index.html b/index.html
index 543f171..49bcafa 100644
--- a/index.html
+++ b/index.html
@@ -4,7 +4,7 @@
- AFJROTC/CAP Ribbon Rack Builder
+ DotWith's Ribbon Rack Builder