diff --git a/.gitignore b/.gitignore index c283e53..0060495 100644 --- a/.gitignore +++ b/.gitignore @@ -1,22 +1,22 @@ - -.DS_Store -*.swift -*.xcplayground -*.xcworkspacedata -*.xcuserstate -Crossword.playground/Resources/example.xml -Crossword.playground/Sources/example.xml -Crossword.playground/timeline.xctimeline -example.puz -example.xml -example2.xml -invalid.xml -noclues.xml -nogrid.xml -PUZ Format.pdf -wordlist-write.txt -words 2.txt -XPF Format.pdf + +.DS_Store +*.swift +*.xcplayground +*.xcworkspacedata +*.xcuserstate +Crossword.playground/Resources/example.xml +Crossword.playground/Sources/example.xml +Crossword.playground/timeline.xctimeline +example.puz +example.xml +example2.xml +invalid.xml +noclues.xml +nogrid.xml +PUZ Format.pdf +wordlist-write.txt +words 2.txt +XPF Format.pdf old_cross.py xw.js Screen Shot 2017-09-01 at 5.30.27 PM.png @@ -82,9 +82,9 @@ Other/Screenshots/Screen Shot 2017-10-03 at 12.27.48 PM.png Other/Screenshots/Screen Shot 2017-10-03 at 12.36.10 PM.png Other/Screenshots/Screen Shot 2017-10-03 at 12.42.52 PM.png Other/Puzzles/17.10.03.json.txt -*.o -depend.mk -glucose +*.o +depend.mk +glucose third_party/glucose-3.0/simp/xwsolve.js third_party/glucose-3.0/simp/xwsolve.wasm xwsolve.js diff --git a/api_key.js b/api_key.js new file mode 100644 index 0000000..39343f7 --- /dev/null +++ b/api_key.js @@ -0,0 +1 @@ +var MY_API_KEY = "AIzaSyALPcu4vprNzxhwOrQHMM-Mmo24viUtclc"; \ No newline at end of file diff --git a/cross.js b/cross.js index f03ae37..e56dfa7 100644 --- a/cross.js +++ b/cross.js @@ -200,6 +200,7 @@ class Toolbar { "clearFill": new Button("clear-fill"), "toggleSymmetry": new Button("toggle-symmetry"), "openWordlist": new Button("open-wordlist"), + //"openCustomDictionary": new Button("open-custom-dictionary"), "autoFill": new Button("auto-fill") } } @@ -283,12 +284,14 @@ current.update(); // F U N C T I O N S function createNewPuzzle(rows, cols) { + xw = {}; // clear all fields, start from scratch xw["clues"] = {}; xw["title"] = DEFAULT_TITLE; xw["author"] = DEFAULT_AUTHOR; xw["rows"] = rows || DEFAULT_SIZE; xw["cols"] = cols || xw.rows; xw["fill"] = []; + for (let i = 0; i < xw.rows; i++) { xw.fill.push(""); for (let j = 0; j < xw.cols; j++) { @@ -422,6 +425,10 @@ function keyboardHandler(e) { history.pos = isUndo ? hpos - 1 : hpos; } } + else if (e.which == keyboard.s) { + console.log('save changes...'); + save(); + } } else { @@ -439,7 +446,7 @@ function keyboardHandler(e) { // new entry holder let newFill = null; - + // stop default keypresses if (isSpace || isEnter || isBackspace || isDirection) e.preventDefault(); @@ -453,17 +460,17 @@ function keyboardHandler(e) { // get string newFill = String.fromCharCode(e.which); - + // move cursor after forceNextInput = current.direction == ACROSS ? keyboard.right : keyboard.down; - } + } // blacks if (isBlack && !wasBlack) newFill = BLACK; - + // blanks if (isBlack && wasBlack || isBackspace) newFill = BLANK; - + // move after backspace if (isBackspace) forceNextInput = current.direction == ACROSS ? keyboard.left : keyboard.up; @@ -486,10 +493,10 @@ function keyboardHandler(e) { // check for symmetry if (isSymmetrical && (wasBlack && newFill == BLANK || wasBlank && newFill == BLACK)) { - + // fill partner xw.fill[symRow] = xw.fill[symRow].slice(0, symCol) + newFill + xw.fill[symRow].slice(symCol + 1); - + // save sym history histObj.sym = { row: symRow, @@ -518,7 +525,7 @@ function keyboardHandler(e) { // check if real or fake input const isFakeEvent = e.key == undefined; - // let content = xw.fill[current.row][current.col]; + // let content = xw.fill[current.row][current.col]; let isNextBlack; switch (e.which) { case keyboard.left: @@ -528,11 +535,11 @@ function keyboardHandler(e) { break; case keyboard.up: // check if on board first (rows > cols) - let prevRow = xw.fill[current.row - 1]; + let prevRow = xw.fill[current.row - 1]; if (prevRow != undefined) { isNextBlack = prevRow[current.col] == BLACK; if (!isFakeEvent || (isFakeEvent && !isNextBlack)) current.row -= (current.row == 0) ? 0 : 1; - } + } changeDirection(DOWN); break; case keyboard.right: @@ -542,17 +549,17 @@ function keyboardHandler(e) { break; case keyboard.down: // check if on board first (rows > cols) - let nextRow = xw.fill[current.row + 1]; + let nextRow = xw.fill[current.row + 1]; if (nextRow != undefined) { isNextBlack = nextRow[current.col] == BLACK; if (!isFakeEvent || (isFakeEvent && !isNextBlack)) current.row += (current.row == xw.rows - 1) ? 0 : 1; - } + } changeDirection(DOWN); break; } } } - + // reactivate new current cell activeCell = grid.querySelector('[data-row="' + current.row + '"]').querySelector('[data-col="' + current.col + '"]'); activeCell.classList.add("active"); @@ -609,10 +616,10 @@ function updateGridUI() { let reflected_j = xw.cols - j - 1; let reflectedFill = xw.fill[reflected_i][reflected_j]; let warnActive = fill == BLANK && (reflectedFill != BLANK && reflectedFill != BLACK); - let warnOpposite = reflectedFill == BLANK && (fill != BLANK && fill != BLACK); + let warnOpposite = reflectedFill == BLANK && (fill != BLANK && fill != BLACK); // only warn pairs - if (warnOpposite || warnActive){ + if (warnOpposite || warnActive) { // pair contains a warn if (warnActive) { @@ -622,18 +629,18 @@ function updateGridUI() { const reflectedCell = grid.querySelector('[data-row="' + reflected_i + '"]').querySelector('[data-col="' + reflected_j + '"]'); reflectedCell.classList.add("warning"); } - + } else { // neither are warned activeCell.classList.remove("warning"); } } else { activeCell.classList.remove("warning"); - } + } // set letters activeCell.lastChild.innerHTML = fill; - + // show/hide black squares if (fill == BLACK) { activeCell.classList.add("black"); @@ -696,12 +703,16 @@ function updateInfoUI() { // count and pct of black squares var blackCount = 0; + var fillCount = 0; for (var i = 0; i < xw.fill.length; i++) { - blackCount += (xw.fill[i].match(/\./g) || []).length + blackCount += (xw.fill[i].match(/\./g) || []).length; + fillCount += (xw.fill[i].match(/[^\s]/g) || []).length; } var blackPct = Math.round((blackCount * 100) / (xw.rows * xw.cols)); - document.getElementById("puzzle-black-count").innerHTML = 'black: ' + blackCount + ' (' + blackPct + '%)'; + var fillPct = Math.round((fillCount * 100) / (xw.rows * xw.cols)); + document.getElementById("puzzle-black-count").innerHTML = 'black: ' + blackCount + ' (' + blackPct + '%)'; + document.getElementById("puzzle-fill-count").innerHTML = 'fill: ' + fillCount + ' (' + fillPct + '%)'; } function createGrid(rows, cols) { diff --git a/firebase.js b/firebase.js new file mode 100644 index 0000000..62d9c3c --- /dev/null +++ b/firebase.js @@ -0,0 +1,136 @@ +var current_user = null; // firebaseConfig will handle checking login +var database = null; // firebase will config this +var customDictionary = []; +var allPuzzles = []; + + +function save() { + console.log('save...'); + + var userId = current_user.uid; + + // for custom dictionary + var dict = document.getElementById("custom-dictionary").value; + console.log('dict', dict); + var split = dict.split('\n'); + console.log('split', split); + + // clean empties and convert to lowercase + var savelist = []; + for (var i = 0; i < split.length; i++) { + if (split[i] != "") savelist.push(split[i].toLowerCase()); + } + + // save dictionary + firebase.database().ref('users/' + userId + '/dictionary').set(savelist); + console.log('saved dictionary...'); + + + + // save puzzle + if (xw._firebaseId) { + xw._lastSaved = Date.now(); + console.log('updating...', xw._firebaseId); + // update current + firebase.database().ref('users/' + userId + '/puzzles/' + xw._firebaseId).set(xw); + } + else { + console.log('saving new...'); + + xw._firstSaved = Date.now(); + xw._lastSaved = xw._firstSaved; + + firebase.database().ref('users/' + userId + '/puzzles').push(xw).then(resp => { + console.log('saved'); + console.log('key', resp.key) + xw._firebaseId = resp.key; + } + ); + } + +} + +function fetch() { + var userId = current_user.uid; + + // get dictionary + firebase.database().ref('users/' + userId + '/dictionary').once("value", + function (resp) { + var list = resp.val(); + console.log('dictionary', list); + customDictionary = list; + + if (customDictionary && customDictionary.length > 0) { + // add to dictionary list + addToWordlist(customDictionary); + sortWordlist(); + + // restore custom dictionary to textarea + var inputVal = ''; + for (var i = 0; i < list.length; i++) { + inputVal += list[i] + '\n'; + } + document.getElementById("custom-dictionary").value = inputVal; + } + }); + + allPuzzles = []; + var puzzlesSelect = document.getElementById('puzzles-select'); + + puzzlesSelect.options.length = 0; + var blank_opt = document.createElement("OPTION"); + puzzlesSelect.appendChild(blank_opt); + + // get puzzle (first for now) + firebase.database().ref('users/' + userId + '/puzzles').once("value", + function (resp) { + + var puzzles = resp.val(); + + if (puzzles) { + + var keys = Object.keys(puzzles); + var puzzlesArray = []; + console.log('puzzles', puzzles) + + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + var puz = puzzles[key]; + + console.log(puz.title + ' ' + puz._lastSaved, puz); + puzzlesArray.push(puz); + + var opt = document.createElement("OPTION"); + opt.value = i; // index of puzzle is the value + var timestamp = new Date(puz._lastSaved); + var timestring = timestamp.toDateString('MM/dd/yyyy'); + opt.innerText = puz.title + ' - ' + timestring; + puzzlesSelect.appendChild(opt); + } + + allPuzzles = puzzlesArray; + } + } + ); + + console.log('allPuzzles', allPuzzles); +} + +function loadFromList() { + var puzzlesSelect = document.getElementById('puzzles-select'); + if (puzzlesSelect.value) { + xw = allPuzzles[puzzlesSelect.value]; // model + current = new Interface(xw.rows, xw.cols); // view-controller + current.update(); + updateUI(); + } +} + +function deletePuzzle() { + console.log(xw); + if (xw._firebaseId && confirm('Are you sure?')) { + var userId = current_user.uid; + firebase.database().ref('users/' + userId + '/puzzles').child(xw._firebaseId).remove(); + fetch(); + } +} \ No newline at end of file diff --git a/firebaseConfig.js b/firebaseConfig.js new file mode 100644 index 0000000..00a4ffa --- /dev/null +++ b/firebaseConfig.js @@ -0,0 +1,100 @@ +// Firebase config +var config = { + apiKey: MY_API_KEY, + authDomain: "kevin-xword.firebaseapp.com", + databaseURL: "https://kevin-xword.firebaseio.com", + projectId: "kevin-xword", + storageBucket: "kevin-xword.appspot.com", + messagingSenderId: "693272376803" +}; +firebase.initializeApp(config); + +var uiConfig = { + callbacks: { + signInSuccessWithAuthResult: function (authResult, redirectUrl) { + var user = authResult.user; + var credential = authResult.credential; + var isNewUser = authResult.additionalUserInfo.isNewUser; + var providerId = authResult.additionalUserInfo.providerId; + var operationType = authResult.operationType; + + return true; + }, + signInFailure: function (error) { + + return handleUIError(error); + }, + uiShown: function () { + // The widget is rendered. + // Hide the loader. + // document.getElementById('loader').style.display = 'none'; + } + }, + credentialHelper: firebaseui.auth.CredentialHelper.ACCOUNT_CHOOSER_COM, + // Query parameter name for mode. + queryParameterForWidgetMode: 'mode', + // Query parameter name for sign in success url. + queryParameterForSignInSuccessUrl: 'signInSuccessUrl', + // Will use popup for IDP Providers sign-in flow instead of the default, redirect. + signInFlow: 'popup', + signInSuccessUrl: '/', + signInOptions: [ + // Leave the lines as is for the providers you want to offer your users. + firebase.auth.GoogleAuthProvider.PROVIDER_ID, + firebase.auth.GithubAuthProvider.PROVIDER_ID, + firebase.auth.EmailAuthProvider.PROVIDER_ID + ], + // tosUrl and privacyPolicyUrl accept either url string or a callback + // function. + // Terms of service url/callback. + tosUrl: '', + // Privacy policy url/callback. + privacyPolicyUrl: function () { + window.location.assign(''); + } +}; + +// Initialize the FirebaseUI Widget using Firebase. +var ui = new firebaseui.auth.AuthUI(firebase.auth()); + +function initApp() { + console.log('app init'); + + database = firebase.database(); + + firebase.auth().onAuthStateChanged(function (user) { + if (user) { + console.log('user', user); + current_user = user; + // User is signed in. + var displayName = user.displayName; + var email = user.email; + var emailVerified = user.emailVerified; + var photoURL = user.photoURL; + var isAnonymous = user.isAnonymous; + var uid = user.uid; + var providerData = user.providerData; + + document.getElementById("auth-status").innerText = displayName; + document.getElementById("login-button").classList.add("hidden"); + document.getElementById("logout-button").classList.remove("hidden"); + + // load list of puzzles + fetch(); + + // ... + } else { + // User is signed out. + // ... + current_user = null; + document.getElementById("logout-button").classList.add("hidden"); + document.getElementById("login-button").classList.remove("hidden"); + document.getElementById("auth-status").innerText = "not signed in"; + } + }); +} + +// Triggers init on load +window.addEventListener('load', function () { + initApp() +}); \ No newline at end of file diff --git a/index.html b/index.html index 72193e2..cca89ce 100644 --- a/index.html +++ b/index.html @@ -15,15 +15,36 @@ return (false); } --> + + + + + + + + + + + + + + + + +
+ @@ -36,6 +57,8 @@

+ + + + + + + @@ -96,7 +132,7 @@

Export as:

- +