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