import { pecBoard, pec } from './pecslib.js'; // gui object classes? // Why am I doing this? class auxillery_dialog { constructor (title) { var me = this; this.root = document.createElement("div"); this.root.classList.add("dialog"); this.title = "string" == typeof title ? title : ""; var title_bar = document.createElement("div"); title_bar.classList.add("title_bar"); var titleText = document.createElement("span"); titleText.innerText = this.title; titleText.classList.add("title_text"); title_bar.appendChild(titleText); this.root.appendChild(title_bar); this.body = document.createElement("div"); this.root.appendChild(me.body); var close = document.createElement("span"); close.innerText = "<"; close.classList.add("close"); title_bar.appendChild(close); close.addEventListener("click", ()=>{ me.root.classList.remove("focused"); }); } } // Functions function systemMsg (message,level=0) { // abstraction? // Yes, but it allows us to easilly redirect error // output to where we want, for now, just the console... switch (level) { case 0: console.log("INFO: "+message); break; case 1: console.error("WARNING: "+message); break; case 2: console.error("ERROR: "+message); break; case 3: console.log("DEBUG: "+message); break; default: console.log(message); } // but later we will also have it push an error toast in the ui. } function initDatabase (e) { var db = e.target.result; var objectStore = db.createObjectStore("pecs", {keyPath: "word"}); objectStore.createIndex("word", "word", {unique: true}); objectStore.createIndex("locate", "locate"); } function saveApp () { var idbHandle = window.indexedDB.open("masterLibrary"); idbHandle.onerror = (e) => { systemMsg("Error loading database",2); }; idbHandle.onupgradeneeded = initDatabase; idbHandle.onsuccess = (e) => { var masterLibrary = idbHandle.result; var saving = masterLibrary.transaction("pecs","readwrite",{durability:"strict"}); var pecStore = saving.objectStore("pecs"); library.forEach(pecItem=>{ var action = pecStore.get(pecItem.word); action.onsuccess = (e)=>{ var record = action.result; if (record == undefined) { record = { word: pecItem.word, image: pecItem.image, locate: "library" }; } else { record.locate = "library"; } pecStore.put(record); }; }); for (var boardID = 0; boardID < app.boards.length; boardID++) { var curBoard = app.boards[boardID]; for (var index = 0; index < curBoard.pecs.length; index++) { var curPec = curBoard.pecs[index]; if (null == curPec) { continue; } //don't store nulls var action = pecStore.get(curPec.word); action.curPec = curPec; action.boardID = boardID; action.pecIndex = index; action.onsuccess = (e)=>{ var record = e.target.result; if (record == undefined) { record = { word: e.target.curPec.word, image: e.target.curPec.image, locate: e.target.boardID + "-" + e.target.pecIndex }; } else { record.locate = e.target.boardID + "-" + e.target.pecIndex; } pecStore.put(record); }; } } saving.oncomplete = (e)=>{ masterLibrary.close(); saveOptions(); loadApp(); }; }; } function saveOptions () { localStorage.setItem("librepecSettings", JSON.stringify(app.options)); } function loadOptions () { var options = JSON.parse(localStorage.getItem("librepecSettings")); if (null != options) { app.options = options; } var darkmode = document.getElementById("darkmode-checkbox"); var kidsmode = document.getElementById("kidsmode-checkbox"); var feedback = document.getElementById("audio-feedback-toggle"); var floodLimit = document.getElementById("flood-limit"); var floodTimeout = document.getElementById("flood-timeout"); darkmode.checked = (undefined == app.options?.darkmode) ? false : (app.options.darkmode ? true : false); toggleDarkmode(darkmode.checked); kidsmode.checked = (undefined == app.options?.kidsmode) ? false : (app.options.kidsmode ? true : false); feedback.checked = (undefined == app.options?.audioFeedback) ? false : (app.options.audioFeedback ? true : false); floodLimit.value = (undefined == app.options?.feedbackFloodLimit) ? 3 : app.options.feedbackFloodLimit; floodTimeout.value = (undefined == app.options?.feedbackTimeout) ? 60 : app.options.feedbackTimeout; } import { library as library } from './library.js'; function initApp () { // Create our initial board app.boards = []; app.boards.push(new pecBoard()); app.currentBoard = app.boards[0]; repopBoards(0); repopGrid(app.currentBoard); // Populate Library app.library = library; repopLibrary(); // Populate Options app.options = {}; app.options.darkmode = false; // should always be an option app.options.kidsMode = false; // disables most interactions app.options.audioFeedback = false; // read word aloude app.options.feedbackFloodLimit = 3; app.options.feedbackTimeout = 60; // 60 seconds // overload defaults with options stored in localstorage } function hardResetApp () { alert("App ahs been hard reset"); initApp(); saveApp(); } function loadAppPec (item) { var locate = item.locate; if ("library" == locate) { app.library.push(new pec(item.word, item.image)); } else { locate = locate.split("-").map(n=>parseInt(n)); if (!(app.boards[locate[0]] instanceof pecBoard)) { app.boards[locate[0]] = new pecBoard(); } app.boards[locate[0]].pecs[locate[1]] = new pec(item.word, item.image); } } function loadApp () { var idbHandle = window.indexedDB.open("masterLibrary"); idbHandle.onerror = (e) => { systemMsg("Error loading database",2); }; idbHandle.onupgradeneeded = initDatabase; window.currentBoardID = app.boards.indexOf(app.currentBoard); currentBoardID = isNaN(currentBoardID) ? 0 : currentBoardID; idbHandle.onsuccess = (e) => { var masterLibrary = idbHandle.result; var loading = masterLibrary.transaction("pecs","readonly",{durability:"strict"}); var pecStore = loading.objectStore("pecs"); var lengthCheck = pecStore.count(); lengthCheck.onsuccess = (e)=>{ if (0 < e.target.result) { app.library = []; // Clear out library cache app.boards = []; // Clear out boards cache app.boards.push(new pecBoard()); app.currentBoard = app.boards[0]; var cursor = pecStore.openCursor(); cursor.onsuccess = (e) => { var c = e.target.result; if (c) { loadAppPec(c.value); c.continue(); } }; } else { // DB is empty, reinit app because // we have nothing to load hardResetApp(); } }; loading.oncomplete = (e) => { masterLibrary.close(); loadOptions(); app.currentBoard = app.boards[currentBoardID]; repopLibrary(); repopBoards(); repopGrid(app.currentBoard); }; }; } function toggleDarkmode () { var html = document.documentElement; if (app.options.darkmode) { if (!(html.classList.contains("darkmode"))) { html.classList.add("darkmode"); } } else { if (html.classList.contains("darkmode")) { html.classList.remove("darkmode"); } } } function inRange(target,min,max) { if (isNaN(target) || isNaN(min) || isNaN(max)) { return false; } return (min <= target && target <= max); } function invokeLibrary(e) { if (app.options.kidsmode) { return; } // Breaking this out of event so its not an // annon function, so we can swap it in and out var targetID = ((e.currentTarget.getAttribute("id")).split("pec"))[1]; if (isNaN(targetID)) { return systemMsg("invokeLibrary targetID not a number",2); } var targetPec = app.gui["pec"+targetID]; if (undefined == targetPec) { return systemMsg("invokeLibrary targetPec undefined: "+targetID,2); } if (!inRange(targetID,0,15)) { return systemMsg("invokeLibrary targetID out of range: "+targetPecIndex,2); } app.targetPec = targetPec; app.targetPecIndex = targetID; app.gui.library.root.classList.add("focused"); } function clearPec (index=null) { if (!inRange(index,0,15)) { return systemMsg("clearPec was given an out of range index",2); } var targetPec = app.gui["pec"+index]; if (undefined == targetPec) { return systemMsg("clearPec targetPec undefined",2); } targetPec.classList.add("empty"); targetPec.innerHTML = "

+

"; targetPec.addEventListener("click", invokeLibrary); } function setPec(pecTemplate=null, targetIndex=null) { if (null == pecTemplate) { return systemMsg("setPec was not provided data",2); } if (!(pecTemplate instanceof pec)) { return systemMsg("setPec was not provided a valid template",2); } if (isNaN(targetIndex)) { return systemMsg("setPec was not provided a valid targetIndex",2); } var targetPec = app.gui["pec"+targetIndex]; targetPec.removeEventListener("click", invokeLibrary); targetPec.innerHTML = ""; targetPec.classList.remove("empty"); var freshPec = new pec(pecTemplate.word, pecTemplate.image); targetPec.appendChild(freshPec.DOM); } function repopBoards (current=0) { //app.boards; //app.currentBoards; //app.menu; // Normally we only want <, but we want an extra // iteration here for the "add new board" element for (var i = 0; i <= app.boards.length; i++) { var boardTab = document.getElementById("board"+i); if (null == boardTab) { boardTab = document.createElement("span"); boardTab.setAttribute("id", "board"+i); boardTab.classList.add("board_tab"); app.gui.menu.appendChild(boardTab); } // Clear event listeners var oldTab = boardTab; boardTab = oldTab.cloneNode(true); oldTab.parentNode.replaceChild(boardTab, oldTab); // not sure how memory safe the above lines are var name = "Board "+i; if (i == app.boards.length) { name = "+"; boardTab.addEventListener("click",(e)=>{ if (app.options.kidsmode) { return; } var current = app.boards.findIndex(i=>i==app.currentBoard); app.boards.push(new pecBoard()); repopBoards(current); repopGrid(app.currentBoard); }); } else { if (current != i) { boardTab.classList.remove("active_board"); boardTab.addEventListener("click", (e)=>{ var sourceID = ((e.currentTarget.getAttribute("id")).split("board"))[1]; app.currentBoard = app.boards[sourceID]; repopBoards(sourceID); repopGrid(app.currentBoard); }); } else { boardTab.classList.add("active_board"); } if (undefined == app.boards[i]) { app.boards[i] = new pecBoard(); } if (undefined != app.boards[i].name) { name = app.boards[i].name; } } boardTab.innerText = name; } } function repopGrid (board=null) { if (null == board) { return systemMsg("repopGrid was not provided data",2); } if (!(board instanceof pecBoard)) { return systemMsg("repopGrid was not provided data of type pecBoard",2); } board.pecs.forEach((p,i)=>{ if (null == p) { return clearPec(i); } if (!(p instanceof pec)) { systemMsg("Invalid Pec found, scrubbing and returning to null",1); board.pecs[i] = null; return clearPec(i); } setPec(p,i); }); } function useThisPec (e) { var sourceID = ((e.currentTarget.getAttribute("id")).split("lPec"))[1]; app.gui.library.root.classList.remove("focused"); app.currentBoard.pecs[app.targetPecIndex] = app.library.splice(sourceID,1)[0]; saveApp(); repopLibrary(); repopGrid(app.currentBoard); } function createNewPec () { var name = app.gui.newpec_name.value; var image = app.gui.newpec_image.files[0]; app.library.push(new pec(name, URL.createObjectURL(image))); saveApp(); repopLibrary(); app.gui.newpec.root.classList.remove("focused"); } function updatePreview () { var name = app.gui.newpec_name.value; var image = app.gui.newpec_image.files[0]; app.gui.newpec_preview.innerHTML = ""; app.gui.newpec_preview.removeEventListener("click", createNewPec); app.gui.newpec_status.innerText = "Create your new pec by naming it and providing an image."; var correct = 0; if (["image/png","image/jpeg","image/svg"].includes(image?.type)) { var imageBox = document.createElement("img"); imageBox.src = URL.createObjectURL(image); imageBox.classList.add("pecImage"); app.gui.newpec_preview.appendChild(imageBox); correct++; } if ("" != name) { var nameBox = document.createElement("div"); nameBox.innerText = name; nameBox.classList.add("pecName"); app.gui.newpec_preview.appendChild(nameBox); correct++; } if (1 < correct) { app.gui.newpec_status.innerText = "Click/Tap the preview to save."; app.gui.newpec_preview.addEventListener("click", createNewPec); } } function addNewPec (e) { // clear out any previous values in form. app.gui.newpec_form.reset(); updatePreview(); // make form visible app.gui.newpec.root.classList.add("focused"); } function repopLibrary () { // clear library app.gui.library.body.innerHTML = ""; // recreate items from app.library app.library.forEach((p,i)=>{ var newPec = document.createElement("span"); newPec.classList.add("libSlot"); newPec.innerText = p.word; newPec.id = "lPec"+i; newPec.addEventListener("click", useThisPec); app.gui.library.body.appendChild(newPec); }); // add new pec button at end of list var newPec = document.createElement("span"); newPec.classList.add("libSlot"); newPec.innerText = " + "; newPec.id = "lPec"+(app.library.length + 1); newPec.addEventListener("click", addNewPec); app.gui.library.body.appendChild(newPec); } // Initialize gui window.app = {}; app.gui = {}; app.gui.main = document.createElement("div"); app.gui.main.id = "root"; document.body.appendChild(app.gui.main); app.gui.menu = document.createElement("nav"); app.gui.menu.id = "navigation"; app.gui.main.appendChild(app.gui.menu); app.gui.optionsButton = document.createElement("div"); app.gui.optionsButton.classList.add("options_button"); app.gui.optionsButton.innerHTML = "☰"; app.gui.menu.appendChild(app.gui.optionsButton); app.gui.view = document.createElement("div"); app.gui.view.id = "view"; app.gui.main.appendChild(app.gui.view); // Library popup app.gui.library = new auxillery_dialog("Library"); app.gui.library.body.classList.add("library_body"); app.gui.main.append(app.gui.library.root); // Options popup app.gui.options = new auxillery_dialog("Options"); app.gui.options.body.classList.add("options_body"); app.gui.main.append(app.gui.options.root); app.gui.optionsButton.addEventListener("click", ()=>{ if (app.options.kidsmode) { return; } loadOptions(); app.gui.options.root.classList.add("focused"); }); app.gui.optionsButton.addEventListener("mousedown", ()=>{ if (!app.options.kidsmode) { return; } app.gui.optionsButton.classList.add("holding"); app.optionHold = setTimeout(()=>{ app.gui.optionsButton.classList.remove("holding"); loadOptions(); app.gui.options.root.classList.add("focused"); },3000); }); app.gui.optionsButton.addEventListener("mouseup", ()=>{ if (!app.options.kidsmode) { return; } app.gui.optionsButton.classList.remove("holding"); clearTimeout(app.optionHold); }); // New Pec popup app.gui.newpec = new auxillery_dialog("Create New Pec"); app.gui.newpec.body.classList.add("newpec_body"); app.gui.main.append(app.gui.newpec.root); // pre-populate grid with emptys for (var x = 0; x < 16; x++) { var name = "pec"+x; var thisPec = document.createElement("span"); app.gui[name] = thisPec; thisPec.classList.add("pecSlot"); thisPec.id = name; clearPec(x); app.gui.view.appendChild(thisPec); } // build Options UI app.gui.options_darkmode = document.createElement("div"); app.gui.options.body.appendChild(app.gui.options_darkmode); app.gui.options_darkmode.innerHTML = ' '; app.gui.options_darkmode.addEventListener("change", (e)=>{ app.options.darkmode = e.target.checked; toggleDarkmode(); saveOptions(); }); app.gui.options_kidsmode = document.createElement("div"); app.gui.options.body.appendChild(app.gui.options_kidsmode); app.gui.options_kidsmode.innerHTML = ' '; app.gui.options_kidsmode.addEventListener("change", (e) => { app.options.kidsmode = e.target.checked; saveOptions(); }); app.gui.options_audioFeedback = document.createElement("div"); app.gui.options.body.appendChild(app.gui.options_audioFeedback); app.gui.options_audioFeedback.innerHTML = ' '; app.gui.options_audioFeedback.addEventListener("change", (e) => { app.options.audioFeedback = e.target.checked; saveOptions(); }); app.gui.options_feedbackFloodLimit = document.createElement("div"); app.gui.options.body.appendChild(app.gui.options_feedbackFloodLimit); app.gui.options_feedbackFloodLimit.innerHTML = ' '; app.gui.options_feedbackFloodLimit.addEventListener("change", (e) => { app.options.feedbackFloodLimit = e.target.value; saveOptions(); }); app.gui.options_feedbackTimeout = document.createElement("div"); app.gui.options.body.appendChild(app.gui.options_feedbackTimeout); app.gui.options_feedbackTimeout.innerHTML = ' '; app.gui.options_feedbackTimeout.addEventListener("change", (e) => { app.options.feedbackTimeout = e.target.value; saveOptions(); }); app.gui.options_reset = document.createElement("div"); app.gui.options.body.appendChild(app.gui.options_reset); app.gui.options_reset.innerHTML = 'Completely Reset App (hold down for 3 seconds)Clear All Boards'; app.gui.options_reset_hard = document.getElementById("reset-hard-reset"); app.gui.options_reset_soft = document.getElementById("reset-soft-reset"); // Add functionality for resets here app.gui.options_about = document.createElement("div"); app.gui.options.body.appendChild(app.gui.options_about); app.gui.options_about.innerHTML = 'App licenced Cards from Assistive Cards Project Source labs.murkfall.net'; // Build New Pec UI app.gui.newpec.body.innerHTML = '



Create your new pec by naming it and providing an image.
'; app.gui.newpec_form = document.getElementById("new-pec-form"); app.gui.newpec_name = document.getElementById("new-pec-name"); app.gui.newpec_image = document.getElementById("new-pec-image"); app.gui.newpec_preview = document.getElementById("new-pec-preview"); app.gui.newpec_status = document.getElementById("new-pec-status"); app.gui.newpec_preview.classList.add("pecSlot"); app.gui.newpec_name.addEventListener("change", updatePreview); app.gui.newpec_image.addEventListener("change", updatePreview); // initialize and load app initApp(); loadApp();