use window.history

This commit is contained in:
Adrien MALINGREY 2023-11-25 04:40:40 +01:00
parent 7245e0f073
commit fa979cb973
4 changed files with 57 additions and 99 deletions

View File

@ -6,7 +6,7 @@
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" rel="alternate stylesheet" type="text/css" title="Clair" integrity="sha384-KK94CHFLLe+nY2dmCWGMq91rCGa5gtU4mk92HdvYe+M/SXH301p5ILy+dN9+nJOZ" crossorigin="anonymous" /> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" rel="alternate stylesheet" type="text/css" title="Clair" integrity="sha384-KK94CHFLLe+nY2dmCWGMq91rCGa5gtU4mk92HdvYe+M/SXH301p5ILy+dN9+nJOZ" crossorigin="anonymous" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap-dark-5@1.1.3/dist/css/bootstrap-night.min.css" rel="alternate stylesheet" type="text/css" title="Sombre" /> <link href="https://cdn.jsdelivr.net/npm/bootstrap-dark-5@1.1.3/dist/css/bootstrap-night.min.css" rel="alternate stylesheet" type="text/css" title="Sombre" />
<link href="https://cdn.jsdelivr.net/npm/remixicon@3.2.0/fonts/remixicon.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/remixicon@3.2.0/fonts/remixicon.css" rel="stylesheet">
<link href="css/style.css" rel="stylesheet" type="text/css" /> <link href="style.css" rel="stylesheet" type="text/css" />
<link href="thumbnail.php?grid=<?=$currentGrid?>&size=196" sizes="196x196" rel="icon" type="image/png"> <link href="thumbnail.php?grid=<?=$currentGrid?>&size=196" sizes="196x196" rel="icon" type="image/png">
<link href="thumbnail.php?grid=<?=$currentGrid?>&size=160" sizes="160x160" rel="icon" type="image/png"> <link href="thumbnail.php?grid=<?=$currentGrid?>&size=160" sizes="160x160" rel="icon" type="image/png">

View File

@ -3,10 +3,6 @@ body {
margin: auto; margin: auto;
} }
.btn {
padding: .275rem .625rem;
}
input[type="number"]::-webkit-outer-spin-button, input[type="number"]::-webkit-outer-spin-button,
input::-webkit-inner-spin-button { input::-webkit-inner-spin-button {
-webkit-appearance: none !important; -webkit-appearance: none !important;

View File

@ -6,7 +6,6 @@ let rows = Array.from(Array(9), x => [])
let columns = Array.from(Array(9), x => []) let columns = Array.from(Array(9), x => [])
let regions = Array.from(Array(9), x => []) let regions = Array.from(Array(9), x => [])
let valueToInsert = "" let valueToInsert = ""
let history = []
let easyBoxes = [] let easyBoxes = []
let insertRadios = [] let insertRadios = []
@ -53,8 +52,10 @@ window.onload = function() {
rowId++ rowId++
} }
if (localStorage["sightCheckbox.checked"] == "true") sightCheckbox.checked = true if (localStorage["tool"] == "sight") sightCheckbox.checked = true
else if (localStorage["highlighterCheckbox.checked"] == "true") highlighterCheckbox.checked = true else if (localStorage["tool"] == "highlighter") highlighterCheckbox.checked = true
colorPickerInput.value = window.getComputedStyle(grid).getPropertyValue("--bs-body-color")
boxes.forEach(box => { boxes.forEach(box => {
box.neighbourhood = new Set(rows[box.rowId].concat(columns[box.columnId]).concat(regions[box.regionId])) box.neighbourhood = new Set(rows[box.rowId].concat(columns[box.columnId]).concat(regions[box.regionId]))
@ -68,7 +69,6 @@ window.onload = function() {
for (label of document.getElementsByTagName("label")) { for (label of document.getElementsByTagName("label")) {
label.control.label = label label.control.label = label
} }
let accessKeyModifiers = (/Win/.test(navigator.userAgent) || /Linux/.test(navigator.userAgent)) ? "Alt+Maj+" let accessKeyModifiers = (/Win/.test(navigator.userAgent) || /Linux/.test(navigator.userAgent)) ? "Alt+Maj+"
: (/Mac/.test(navigator.userAgent)) ? "⌃⌥" : (/Mac/.test(navigator.userAgent)) ? "⌃⌥"
: "AccessKey+" : "AccessKey+"
@ -78,35 +78,36 @@ window.onload = function() {
else if (node.label) node.label.title += shortcut else if (node.label) node.label.title += shortcut
} }
loadSavedGame() loadGame(history.state)
colorPickerInput.value = window.getComputedStyle(grid).getPropertyValue("--bs-body-color")
if ("serviceWorker" in navigator) { if ("serviceWorker" in navigator) {
navigator.serviceWorker.register(`service-worker.js`) navigator.serviceWorker.register(`service-worker.js`)
} }
} }
function loadSavedGame() { function loadGame(state) {
const savedGame = location.hash.slice(1) if (state) {
if (savedGame.match(/[1-9.]{81}/)) {
boxes.forEach((box, i) => { boxes.forEach((box, i) => {
if (!box.disabled && savedGame[i] != UNKNOWN) { if (!box.disabled) {
box.value = savedGame[i] box.value = state.boxesValues[i]
box.previousValue = savedGame[i] box.placeholder = state.boxesPlaceholders[i]
} }
}) })
restartButton.disabled = false fixGridLink.href = "?" + state.boxesValues.map(value => value || UNKNOWN).join("")
fixGridLink.href = "?" + savedGame } else {
boxes.filter(box => !box.disabled).forEach(box => {
box.value = ""
box.placeholder = ""
})
fixGridLink.href = ""
} }
boxes.forEach(searchCandidatesOf) boxes.forEach(searchCandidatesOf)
boxes.forEach(checkBox)
checkSuccess()
refreshUI() refreshUI()
} }
onhashchange = function(event) { window.onpopstate = (event) => loadGame(event.state)
if (location.hash.slice(1)) loadSavedGame()
else restart()
}
function searchCandidatesOf(box) { function searchCandidatesOf(box) {
box.candidates = new Set(VALUES) box.candidates = new Set(VALUES)
@ -157,30 +158,16 @@ function onclick() {
} }
function oninput() { function oninput() {
history.push({
box: this,
value: this.previousValue,
placeholder: this.previousPlaceholder
})
undoButton.disabled = false
saveButton.disabled = false
restartButton.disabled = false
if (pencilRadio.checked) {
this.previousValue = ""
this.previousPlaceholder = this.value
} else {
this.previousValue = this.value
this.previousPlaceholder = this.placeholder
refreshBox(this)
}
if (penColor) { if (penColor) {
this.style.setProperty("color", penColor) this.style.setProperty("color", penColor)
} }
} if (inkPenRadio.checked) {
checkBox(this)
function refreshBox(box) { checkSuccess()
checkBox(box) refreshUI()
refreshUI() saveGame()
fixGridLink.href = "?" + boxes.map(box => box.value || UNKNOWN).join("")
}
} }
function checkBox(box) { function checkBox(box) {
@ -190,6 +177,7 @@ function checkBox(box) {
searchCandidatesOf(neighbour) searchCandidatesOf(neighbour)
if (neighbour.candidates.size == 0) { if (neighbour.candidates.size == 0) {
neighbour.setCustomValidity("Aucun chiffre possible !") neighbour.setCustomValidity("Aucun chiffre possible !")
neighbour.classList.add("is-invalid")
} }
}) })
@ -206,24 +194,28 @@ function checkBox(box) {
}, ]) }, ])
for (neighbour of area.neighbours) for (neighbour of area.neighbours)
if (box != neighbour && box.value == neighbour.value) { if (box != neighbour && box.value == neighbour.value) {
for (neighbour of[box, neighbour]) { for (neighbour of [box, neighbour]) {
neighbour.setCustomValidity(`Il y a un autre ${box.value} dans cette ${area.name}.`) neighbour.setCustomValidity(`Il y a un autre ${box.value} dans cette ${area.name}.`)
neighbour.classList.add("is-invalid") neighbour.classList.add("is-invalid")
} }
} }
} }
}
if (box.form.checkValidity()) { // Correct grid function checkSuccess() {
if (sudokuForm.checkValidity()) { // Correct grid
if (boxes.filter(box => box.value == "").length == 0) { if (boxes.filter(box => box.value == "").length == 0) {
grid.classList.add("table-success") grid.classList.add("table-success")
saveButton.disabled = true
setTimeout(() => { setTimeout(() => {
if (confirm(`Bravo ! Vous avez résolu la grille. En voulez-vous une autre ?`)) if (confirm(`Bravo ! Vous avez résolu la grille. En voulez-vous une autre ?`))
location = "." location = "."
}, 400) }, 400)
} else {
grid.classList.remove("table-success")
} }
} else { // Errors on grid } else { // Errors on grid
box.form.reportValidity() grid.classList.remove("table-success")
sudokuForm.reportValidity()
} }
} }
@ -283,13 +275,21 @@ function onblur() {
this.value = "" this.value = ""
//this.type = "number" //this.type = "number"
this.classList.remove("pencil") this.classList.remove("pencil")
saveGame()
} }
} }
function saveGame() {
history.pushState({
boxesValues: boxes.map(box => box.value),
boxesPlaceholders: boxes.map(box => box.placeholder)
}, "")
}
function onmouseenter(event) { function onmouseenter(event) {
if (sightCheckbox.checked){ if (sightCheckbox.checked){
box = event.target box = event.target
box.neighbourhood.concat([box]).forEach(neighbour => { box.andNeighbourhood.forEach(neighbour => {
neighbour.parentElement.classList.add("table-active") neighbour.parentElement.classList.add("table-active")
}) })
@ -306,7 +306,7 @@ function onmouseenter(event) {
function onmouseleave(event) { function onmouseleave(event) {
if (sightCheckbox.checked){ if (sightCheckbox.checked){
box = event.target box = event.target
box.neighbourhood.concat([box]).forEach(neighbour => { box.andNeighbourhood.forEach(neighbour => {
neighbour.parentElement.classList.remove("table-active") neighbour.parentElement.classList.remove("table-active")
neighbour.parentElement.classList.remove("table-danger") neighbour.parentElement.classList.remove("table-danger")
}) })
@ -332,51 +332,10 @@ function changeColor() {
colorPickerLabel.style.color = colorPickerInput.value colorPickerLabel.style.color = colorPickerInput.value
} }
function undo() {
if (history.length) {
const previousState = history.pop()
previousState.box.value = previousState.value
previousState.box.placeholder = previousState.placeholder
refreshBox(previousState.box)
if (history.length < 1) {
undoButton.disabled = true
saveButton.disabled = true
}
}
}
function restart() { function restart() {
if (confirm("Effacer toutes les cases ?")) { if (confirm("Effacer toutes les cases ?")) {
boxes.filter(box => !box.disabled).forEach(box => {
box.value = ""
box.previousValue = ""
box.placeholder = ""
box.previousPlaceholder = ""
box.setCustomValidity("")
})
let history = []
undoButton.disabled = true
restartButton.disabled = true restartButton.disabled = true
location.hash = "" location.hash = ""
boxes.forEach(searchCandidatesOf)
refreshUI()
}
}
function save() {
let saveGame = boxes.map(box => box.value || UNKNOWN).join("")
location.hash = saveGame
fixGridLink.href = "?" + saveGame
saveButton.disabled = true
alert("Partie sauvegardée")
}
window.onbeforeunload = function(event) {
localStorage["sightCheckbox.checked"] = sightCheckbox.checked
localStorage["highlighterCheckbox.checked"] = highlighterCheckbox.checked
if (!saveButton.disabled) {
event.preventDefault()
event.returnValue = "La partie n'est pas sauvegardée. Quitter quand même ?"
} }
} }
@ -416,7 +375,7 @@ function oncontextmenu(event) {
} else { } else {
li = document.createElement("li") li = document.createElement("li")
li.innerText = "Aucune possibilité !" li.innerText = "Aucune possibilité !"
li.classList.add("error") li.classList = "list-group-item list-group-item-action disabled"
contextMenu.appendChild(li) contextMenu.appendChild(li)
} }
contextMenu.style.left = `${event.pageX}px` contextMenu.style.left = `${event.pageX}px`
@ -435,4 +394,9 @@ document.onkeydown = function(event) {
event.preventDefault() event.preventDefault()
contextMenu.style.display = "none" contextMenu.style.display = "none"
} }
}
window.onbeforeunload = function(event) {
if (sightCheckbox.checked) localStorage["tool"] = "sight"
else if (highlighterCheckbox.checked) localStorage["tool"] = "highlighter"
} }

View File

@ -27,9 +27,7 @@
<label for='highlighterCheckbox' class='btn btn-info' title='Surligner les lignes, colonnes et régions contenant déjà le chiffre sélectionné'><i class="ri-mark-pen-fill"></i></label> <label for='highlighterCheckbox' class='btn btn-info' title='Surligner les lignes, colonnes et régions contenant déjà le chiffre sélectionné'><i class="ri-mark-pen-fill"></i></label>
</div> </div>
<button id="hintButton" type="button" class='btn btn-info' onclick="showHint()" title="Montrer une case avec une seule possibilité" accesskey="H" disabled=""><i class="ri-lightbulb-line"></i></button> <button id="hintButton" type="button" class='btn btn-info' onclick="showHint()" title="Montrer une case avec une seule possibilité" accesskey="H" disabled=""><i class="ri-lightbulb-line"></i></button>
<button id='restartButton' type='button' class='btn btn-primary' onclick='restart()' disabled title='Recommencer'><i class="ri-restart-line"></i></button> <a class='btn btn-primary' href="" title='Recommencer'><i class="ri-restart-line"></i></a>
<button id='undoButton' type='button' class='btn btn-primary' onclick='undo()' disabled title='Annuler' accesskey='Z'><i class="ri-arrow-go-back-fill"></i></button>
<button id='saveButton' type='button' class='btn btn-primary' onclick='save()' disabled title='Sauvegarder' accesskey='S'><i class="ri-save-2-fill"></i></button>
</div> </div>
<form id='sudokuForm' class='needs-validation' novalidate> <form id='sudokuForm' class='needs-validation' novalidate>
<table id='grid' class='table mb-2'> <table id='grid' class='table mb-2'>
@ -73,7 +71,7 @@
if (isset($warning)) if (isset($warning))
echo("<strong>⚠️ $warning ⚠️</strong><br/>"); echo("<strong>⚠️ $warning ⚠️</strong><br/>");
else else
echo("Remplissez la grille de sorte que chaque ligne, colonne et région (carré de 3×3 cases) contienne tous les chiffres de 1 à 9.") echo("<p>Remplissez la grille de sorte que chaque ligne, colonne et région (carré de 3×3 cases) contienne tous les chiffres de 1 à 9.</p>\n<p>Vous pouvez annuler une action en revenant à la page précédente.</p>")
?></div> ?></div>
<ul id='contextMenu' class='context-menu modal-content shadow list-group w-auto position-absolute'></ul> <ul id='contextMenu' class='context-menu modal-content shadow list-group w-auto position-absolute'></ul>
<footer> <footer>
@ -81,12 +79,12 @@
<a href='.' class='list-group-item list-group-item-action'>Nouvelle grille</a> <a href='.' class='list-group-item list-group-item-action'>Nouvelle grille</a>
<a href='' class='list-group-item list-group-item-action'>Lien vers cette grille</a> <a href='' class='list-group-item list-group-item-action'>Lien vers cette grille</a>
<a href='?.................................................................................' class='list-group-item list-group-item-action'>Grille vierge</a> <a href='?.................................................................................' class='list-group-item list-group-item-action'>Grille vierge</a>
<a href='' id='fixGridLink' class='list-group-item list-group-item-action'>Figer la grille enregistrée</a> <a href='' id='fixGridLink' class='list-group-item list-group-item-action'>Figer la grille</a>
<a href='https://git.malingrey.fr/adrien/Sudoku' target="_blank" class='list-group-item list-group-item-action'>Code source</a> <a href='https://git.malingrey.fr/adrien/Sudoku' target="_blank" class='list-group-item list-group-item-action'>Code source</a>
</div> </div>
</footer> </footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ENjdO4Dr2bkBIFxQpeoTz1HIcje39Wm4jDKdf19U8gI4ddQ3GYNS7NTKfAdVQSZe" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ENjdO4Dr2bkBIFxQpeoTz1HIcje39Wm4jDKdf19U8gI4ddQ3GYNS7NTKfAdVQSZe" crossorigin="anonymous"></script>
<script src='js/sudoku.js' defer></script> <script src='sudoku.js' defer></script>
</body> </body>
</html> </html>