diff --git a/img/save.svg b/img/save.svg new file mode 100755 index 0000000..7d81658 --- /dev/null +++ b/img/save.svg @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="iso-8859-1"?> +<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve"> +<path style="fill:#DCF4FF;" d="M512,492H0V20h403.213L512,128.787V492z"/> +<polygon style="fill:#CCEFFF;" points="403.213,20 256,20 256,492 512,492 512,128.787 "/> +<path style="fill:#0A4EAF;" d="M403.213,20H396v175H116V20H0v472h116V315c0-16.568,13.431-30,30-30h220c16.569,0,30,13.432,30,30 + v177h116V128.787L403.213,20z"/> +<g> + <rect x="296" y="20" style="fill:#063E8B;" width="40" height="115"/> + <path style="fill:#063E8B;" d="M403.213,20H396v175H256v90h110c16.569,0,30,13.432,30,30v177h116V128.787L403.213,20z"/> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +</svg> diff --git a/style.css b/style.css index 0cd223c..45c4980 100644 --- a/style.css +++ b/style.css @@ -236,7 +236,7 @@ input[type="number"]::-webkit-calendar-picker-indicator { cursor: not-allowed; } -.tools button.warning { +.tools button.warning:hover { background: #ff5050; border-color: #ff5050; } diff --git a/style.css~ b/style.css~ new file mode 100644 index 0000000..0cd223c --- /dev/null +++ b/style.css~ @@ -0,0 +1,292 @@ +body { + font-family: sans-serif; + width: min-content; + margin: auto; +} + +h1 { + text-align: center; + margin: 1rem; +} + +section, div, footer { + align-items: center; + align-items: center; + justify-content: center; + text-align: center; + margin: 0.8rem 0; +} + +div { + display: flex; + flex-wrap: wrap; + row-gap: 0.5rem; + column-gap: 0.3rem; + margin: 0.5rem auto; +} + + +.grid { + border-spacing: 0; + border: 1px solid black; + border-radius: 6px; + margin: auto; + cursor: url(img/ink-pen.svg) 2 22, text; +} + +.grid td, tr { + padding: 0; +} + +.grid tr:first-child td:first-child { + border-top-left-radius: 5px; +} + +.grid tr:first-child td:first-child input { + border-top-left-radius: 4px; +} + +.grid tr:first-child td:last-child { + border-top-right-radius: 5px; +} + +.grid tr:first-child td:last-child input { + border-top-right-radius: 4px; +} + +.grid tr:last-child td:first-child { + border-bottom-left-radius: 5px; +} + +.grid tr:last-child td:first-child > input { + border-bottom-left-radius: 4px; +} + +.grid tr:last-child td:last-child { + border-bottom-right-radius: 5px; +} + +.grid tr:last-child td:last-child input { + border-bottom-right-radius: 4px; +} + +.grid tr:nth-child(3n+1) td { + border-top: 1px solid black; +} + +.grid tr:nth-child(3n+2) td { + border-top: 1px solid grey; + border-bottom: 1px solid grey; +} + +.grid tr:nth-child(3n) td { + border-bottom: 1px solid black; +} + +.grid td:nth-child(3n+1) { + border-left: 1px solid black; +} + +.grid td:nth-child(3n+2) { + border-left: 1px solid grey; + border-right: 1px solid grey; +} + +.grid td:nth-child(3n+3) { + border-right: 1px solid black; +} + + +.grid input { + width: 2.5rem; + height: 2.5rem; + font-size: 1.5rem; + border: 0; + border-radius: 0; + padding: 0; + text-align: center; + transition: background 0.5s; + -moz-appearance: textfield; +} + +input[type="number"]::-webkit-outer-spin-button, +input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} + +input[type="number"]::-webkit-calendar-picker-indicator { + display: none; +} + +.grid input:enabled { + background: white; + color: darkblue; + cursor: inherit; +} + +.grid input.pencil, +.grid input::placeholder { + color: #666 !important; + font-size: 0.9rem !important; +} + +.grid input:disabled { + color: white !important; + background: #6666ff !important; + cursor: not-allowed !important; +} + +.grid input.forbidden:enabled { + background: #ffffaa !important; + cursor: not-allowed; +} + +.grid input.same-value:enabled { + background: #ffff33; + cursor: not-allowed !important; +} + +.grid input.forbidden:disabled { + color: #ffffaa; + background: #6666ff; +} + +.grid input.same-value:disabled, +.tools button.same-value:enabled, +.tools input:enabled:checked + label { + color: #ffffaa !important; + background: #00b359 !important; +} + +.grid input.one-candidate { + cursor: help !important; +} + + +.tools button, +.tools input + label { + color: white; + text-shadow: -1px -1px #5b6c9e; + background: #8ca6f2; + border: 2px outset #8ca6f2; + border-radius: 4px; + font-size: 1.3rem; + min-width:20px; + padding: 4px 5px 5px 5px; + margin: 0px 1px 1px 1px; + cursor: pointer; +} + +.tools img { + display: block; + width: 24px; + height: 24px; +} + +.tools input { + position: fixed; + opacity: 0; + width: 0; + pointer-events: none; +} + +.tools button:enabled:hover, +.tools button:enabled:focus, +.tools input:enabled:hover + label, +.tools input:enabled:focus + label { + border-width: 1px; + border-style: outset; + padding: 5px 5px 5px 6px; + margin: 1px 1px 1px 2px; +} + +.tools input:enabled:checked:hover + label, +.tools input:enabled:checked:focus + label { + border-width: 3px; + border-style: inset; + padding: 4px 2px 2px 5px; + margin: 1px 1px 1px 2px; +} + +.tools input:enabled:checked + label { + text-shadow: -1px -1px #005f2f; + border: 2px inset #00b359; + background: #00b359; + padding: 4px 4px 4px 5px; + margin: 1px 1px 0px 2px; +} + +.tools button:enabled:active, +.tools input:enabled:active + label { + border-width: 4px !important; + border-style: inset !important; + padding: 4px 0px 0px 5px !important; + margin: 0px 1px 0px 2px !important; +} + +.tools button:disabled, +.tools input:disabled + label { + text-shadow: -1px -1px #555; + color: #ccc; + background: darkgrey; + border: 1px outset darkgrey; + padding: 5px 6px 6px 6px; + margin: 0px 1px 1px 1px; + cursor: not-allowed; +} + +.tools button.warning { + background: #ff5050; + border-color: #ff5050; +} + + +.context-menu { + display: none; + z-index: 1000; + position: absolute; + overflow: hidden; + border: 1px solid #CCC; + white-space: nowrap; + font-family: sans-serif; + background: #EEE; + color: #333; + border-top-right-radius: 3px; + border-bottom-left-radius: 3px; + border-bottom-right-radius: 3px; + padding: 0; + margin: 0; +} + +.context-menu li { + padding: 6px 10px; + cursor: default; + list-style-type: none; + transition: all .3s ease; + font-size: 1rem; +} + +.context-menu li:hover { + background-color: #DEF; +} + +.context-menu li.error { + color: #888 +} + +.context-menu li.error:hover { + background-color: #EEE; +} + + +a { + text-decoration: none; +} + + +.credits { + font-size: 0.8rem; + margin: 0; +} + diff --git a/sudoku.js b/sudoku.js index c190432..b556e2e 100644 --- a/sudoku.js +++ b/sudoku.js @@ -10,6 +10,7 @@ let suggestionTimer = null let valueToInsert = "" let history = [] let accessKeyModifiers = "AccessKey+" +let changesToSave = false function shuffle(iterable) { array = Array.from(iterable) @@ -142,6 +143,7 @@ function onclick() { function oninput() { history.push({ box: this, value: this.previousValue, placeholder: this.previousPlaceholder }) undoButton.disabled = false + changesToSave = true if (pencilRadio.checked) { this.value = Array.from(new Set(this.value)).sort().join("") this.previousValue = "" @@ -157,17 +159,10 @@ function oninput() { } function refreshBox(box) { - saveGame() checkBox(box) refreshUI() } -function saveGame() { - let saveGame = boxes.map(box => box.value || UNKNOWN).join("") - localStorage[location.pathname] = saveGame - fixGridLink.href = saveGame -} - function checkBox(box) { box.neighbourhood.concat([box]).forEach(neighbour => { searchCandidatesOf(neighbour) @@ -200,6 +195,7 @@ function checkBox(box) { } else { // Errors on grid box.form.reportValidity() } +} function refreshUI() { enableRadio() @@ -316,6 +312,20 @@ function restart() { } } +function save() { + let saveGame = boxes.map(box => box.value || UNKNOWN).join("") + localStorage[location.pathname] = saveGame + fixGridLink.href = saveGame + changesToSave = false +} + +window.onbeforeunload = function(event) { + if (changesToSave) { + event.preventDefault() + event.returnValue = "" + } +} + function showSuggestion() { const easyBoxes = boxes.filter(box => box.value == "" && box.candidates.size == 1) if (easyBoxes.length) { diff --git a/sudoku.php b/sudoku.php index 52eb670..42fc230 100644 --- a/sudoku.php +++ b/sudoku.php @@ -101,6 +101,7 @@ <input type='radio' id='eraserRadio' name='tool' onclick='grid.style.cursor = "url(img/eraser.svg) 2 22, auto"'/><label for='eraserRadio' title='Effacer une case'><img src='img/eraser.svg' alt='Gomme'/></label> <button type='button' class='warning' onclick='restart()' title='Recommencer'><img src='img/restart.svg' alt='Recommencer'/></button> <button id='undoButton' type='button' onclick='undo()' disabled title='Annuler' accesskey='z'><img src='img/undo.svg' alt='Annuler'/></button> + <button id='undoButton' type='button' onclick='save()' title='Sauvegarder' accesskey='s'><img src='img/save.svg' alt='Disquette'/></button> </div> </section> <section>