Skip to content

Commit

Permalink
feat json save load, info modal
Browse files Browse the repository at this point in the history
  • Loading branch information
avidrucker committed Nov 2, 2023
1 parent 3810f87 commit 1d722ed
Show file tree
Hide file tree
Showing 3 changed files with 158 additions and 12 deletions.
94 changes: 82 additions & 12 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import {useState, useEffect} from 'react';
import { addTask, completeBenchmarkTask, benchmarkItem, emptyList, isActionableList } from './core/tasksManager';
import { startReview, handleReviewDecision, isPrioritizableList, genQuestion, getInitialCursor } from './core/reviewManager';
import { getFromLocalStorage, saveToLocalStorage } from './core/localStorageAdapter';
import { exportTasksToJSON, importTasksFromJSON } from './core/tasksIO';
import TodoItem from './TodoItem';
import './App.css';


function App() {
const initialTasks = JSON.parse(localStorage.getItem('tasks') || '[]');
const initialTasks = getFromLocalStorage('tasks', []);
const [tasks, setTasks] = useState(initialTasks);
const [inputValue, setInputValue] = useState('');
const initialPrioritizing = JSON.parse(localStorage.getItem('isPrioritizing') || false);
const initialPrioritizing = getFromLocalStorage('isPrioritizing', false);
const [isPrioritizing, setIsPrioritizing] = useState(initialPrioritizing);
const initialCursor = JSON.parse(localStorage.getItem('cursor') || -1);
const initialCursor = getFromLocalStorage('cursor', -1);
const [cursor, setCursor] = useState(initialCursor);
const [errMsg, setErrMsg] = useState("");
const [showingDeleteModal, setShowingDeleteModal] = useState(false);
const [showingMoreInfo, setShowingMoreInfo] = useState(false);

useEffect(()=>{
saveCursorToLocal();
Expand All @@ -34,15 +38,15 @@ function App() {
}, [isPrioritizing])

const saveTasksToLocal = (tasks) => {
localStorage.setItem('tasks', JSON.stringify(tasks));
saveToLocalStorage('tasks', tasks);
};

const saveCursorToLocal = () => {
localStorage.setItem('cursor', JSON.stringify(cursor));
saveToLocalStorage('cursor', cursor);
};

const saveIsPrioritizingToLocal = () => {
localStorage.setItem('isPrioriziting', JSON.stringify(isPrioritizing));
saveToLocalStorage('isPrioritizing', isPrioritizing);
};

const handlePrioritizeUI = () => {
Expand Down Expand Up @@ -112,11 +116,57 @@ function App() {
setShowingDeleteModal(!showingDeleteModal);
}
}

const handleToggleInfoModal = () => {
setShowingMoreInfo(!showingMoreInfo);
}

// Function to handle exporting tasks to a JSON file
const handleExportTasks = () => {
const json = exportTasksToJSON(tasks);
if (json) {
const blob = new Blob([json], {type: "application/json"});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'tasks.json';
a.click();
URL.revokeObjectURL(url);
} else {
setErrMsg("Failed to export tasks.");
}
};

// Function to handle importing tasks from a JSON file
const handleImportTasks = (event) => {
const file = event.target.files[0];
if (file && file.type === "application/json") {
const reader = new FileReader();
reader.onload = (e) => {
const importedTasks = importTasksFromJSON(e.target.result);
if (importedTasks) {
setTasks([...tasks, ...importedTasks]); // append imported tasks to current tasks
} else {
setErrMsg("Failed to import tasks. Ensure the JSON file has the correct format.");
}
};
reader.readAsText(file);
} else {
setErrMsg("Please select a valid JSON file.");
}
};

return (
<main className="app flex flex-column tc f5 montserrat black bg-white vh-100">
<header className="app-header pa3">
<h1 className="ma0 f2 fw8 tracked-custom">AutoFocus</h1>
<header className="app-header pa3 flex justify-center items-center">
<h1 className="ma0 f3 f2-ns fw8 tracked-custom dib">AutoFocus FV</h1>
<div className="dib pl3">
<button
type="button"
className="button-reset w2 h2 pointer f5 fw6 grow bg-moon-gray br-100 ba bw1 b--gray "
onClick={handleToggleInfoModal}>
i</button>
</div>
</header>

<section className="app-container relative">
Expand Down Expand Up @@ -191,24 +241,44 @@ function App() {

{/*prioritization review modal*/}
{(isPrioritizing && cursor !== -1 && cursor < tasks.length) &&
<div className="absolute f4 top-0 w-100 h-100 bg-white-80">
<section className="absolute f4 top-0 w-100 h-100 bg-white-90">
<p className="ph3 lh-copy balance">{genQuestion(tasks, cursor)}</p>
<button className="br3 w3 fw6 ba bw1 b--gray button-reset bg-moon-gray pa2 pointer ma1"
onClick={handleQuitUI}>Quit</button>
<button className="br3 w3 fw6 ba bw1 b--gray button-reset bg-moon-gray pa2 pointer ma1"
onClick={handleNoUI}>No</button>
<button className="br3 w3 fw6 ba bw1 b--gray button-reset bg-moon-gray pa2 pointer ma1"
onClick={handleYesUI}>Yes</button>
</div>}
</section>}

{showingDeleteModal &&
<div className="absolute f4 top-0 w-100 h-100 bg-white-80">
<section className="absolute f4 top-0 w-100 h-100 bg-white-90">
<p className="ph3 lh-copy balance">Are you sure you want to delete your list?</p>
<button className="br3 w3 fw6 ba bw1 b--gray button-reset bg-moon-gray pa2 pointer ma1"
onClick={handleToggleDeleteModal}>No</button>
<button className="br3 w3 fw6 ba bw1 b--gray button-reset bg-moon-gray pa2 pointer ma1"
onClick={handleDeleteUI}>Yes</button>
</div>}
</section>}

{showingMoreInfo &&
<section className="absolute f4 top-0 w-100 h-100 bg-white-90">
<section className="relative z-1 measure-narrow ml-auto mr-auto">
<p className="ph3 ma0 lh-copy balance">Here you can import (load) and export (save) JSON lists.</p>

<div className="pv3">
<label forhtml="file-upload" className="br3 grow dib button-reset border-box w4 f5 fw6 ba bw1 b--gray bg-moon-gray pa2 pointer ma1">
<span>Import</span>
<input id="file-upload" className="dn input-reset"
type="file" accept=".json" onChange={handleImportTasks} />
</label>
<button className="br3 w4 f5 fw6 ba dib bw1 grow b--gray button-reset bg-moon-gray pa2 pointer ma1"
onClick={handleExportTasks}>Export</button>
</div>
<p className="ph3 ma0 lh-copy balance">AutoFocus Final Version was designed by Mark Forster. This web app was built by Avi Drucker.</p>
<p className="ph3 pt3 ma0 lh-copy balance">Click on the 'i' icon above to close this window.</p>
</section>
<button className="absolute z-0 top-0 left-0 w-100 o-0 vh-75" onClick={handleToggleInfoModal} type="button">Close Info Modal</button>
</section>}
</section>
</main>
);
Expand Down
32 changes: 32 additions & 0 deletions src/core/localStorageAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Save data to local storage under the specified key.
* @param {string} key - The key under which the data should be stored.
* @param {any} data - The data to be stored.
*/
export const saveToLocalStorage = (key, data) => {
try {
const serializedData = JSON.stringify(data);
localStorage.setItem(key, serializedData);
} catch (error) {
console.error("Failed to save data to local storage:", error);
}
};

/**
* Retrieve data from local storage for the given key.
* @param {string} key - The key for which data should be retrieved.
* @param {any} defaultValue - The default value to return if the key doesn't exist.
* @returns {any} - The retrieved data or the default value.
*/
export const getFromLocalStorage = (key, defaultValue = null) => {
try {
const serializedData = localStorage.getItem(key);
if (serializedData === null) {
return defaultValue;
}
return JSON.parse(serializedData);
} catch (error) {
console.error("Failed to retrieve data from local storage:", error);
return defaultValue;
}
};
44 changes: 44 additions & 0 deletions src/core/tasksIO.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// convert tasks list to JSON string
export const exportTasksToJSON = (tasks) => {
try {
return JSON.stringify(tasks);
} catch (error) {
console.error("Failed to convert tasks to JSON:", error);
return null;
}
};

// parse JSON string and return tasks list
export const importTasksFromJSON = (jsonString) => {
try {
const tasks = JSON.parse(jsonString);

if (!Array.isArray(tasks)) {
throw new Error("The JSON content isn't an array.");
}

for (let task of tasks) {
if (typeof task !== 'object') {
throw new Error("A task in the JSON isn't an object.");
}
if (!task.hasOwnProperty('id')) {
throw new Error("A task in the JSON doesn't have an 'id' property.");
}
if (typeof task.id !== 'number') {
throw new Error("The 'id' property in a task isn't a number.");
}
if (!task.hasOwnProperty('text')) {
throw new Error("A task in the JSON doesn't have a 'text' property.");
}
if (typeof task.text !== 'string') {
throw new Error("The 'text' property in a task isn't a string.");
}
}

return tasks;

} catch (error) {
console.error("Failed to parse JSON to tasks:", error);
return null;
}
};

0 comments on commit 1d722ed

Please sign in to comment.