Permalink
Browse files

Created a basic localStorage ORM + refactored app to use it

  • Loading branch information...
1 parent ce97f52 commit 69e5b0daf53c8a89c26a1adbe7e3deb537f3fda2 @krrishd committed Dec 30, 2016
Showing with 204 additions and 42 deletions.
  1. +1 โˆ’1 README.md
  2. +3 โˆ’1 package.json
  3. +6 โˆ’13 src/Article.js
  4. +3 โˆ’8 src/Editor.js
  5. +16 โˆ’15 src/Saved.js
  6. +7 โˆ’0 src/configure-localStore.js
  7. +22 โˆ’4 src/index.js
  8. +109 โˆ’0 src/localStore.js
  9. +37 โˆ’0 src/localStore.test.js
View
@@ -22,7 +22,7 @@ In order to run it locally, clone this repository, run `npm install`, and then e
Have suggestions/ideas for improvement? Feel free to submit them in the form of an issue (pull requests also welcome).
- [ ] Setting up DNS for write.itskrish.co (currently accessible at [write.surge.sh](http://write.surge.sh))
-- [ ] Refactoring storage from localStorage to a backwards-compatible ORM-esque system.
+- [x] Refactoring storage from localStorage to a backwards-compatible ORM-esque system.
- [x] Refactoring timekeeping with system time instead of setTimeout.
- [ ] Setting up benchmarking + optimizing for speed.
- [ ] Packaging as an Electron (Desktop) app.
View
@@ -42,6 +42,8 @@
"rimraf": "2.5.4",
"strip-ansi": "3.0.1",
"style-loader": "0.13.1",
+ "tape": "^4.6.3",
+ "tape-run": "^2.1.5",
"url-loader": "0.5.7",
"webpack": "1.13.2",
"webpack-dev-server": "1.16.2",
@@ -88,4 +90,4 @@
"eslintConfig": {
"extends": "react-app"
}
-}
+}
View
@@ -11,12 +11,11 @@ import '../node_modules/sweetalert/dist/sweetalert.css';
class Article extends Component {
constructor(props) {
super(props);
- this.state = {
- savedWriting: JSON.parse(localStorage.getItem('savedWriting'))
- }
+ this.store = props.route.store;
+ this.article = this.store.getItemsByProperty('slug', props.params.slug)[0];
}
- delete(e, article) {
+ delete(e, article, self) {
e.preventDefault();
swal({
title: 'Are you sure?',
@@ -26,10 +25,7 @@ class Article extends Component {
confirmButtonText: 'Yes, delete it!',
closeOnConfirm: false
}, () => {
- let savedWriting = this.state.savedWriting;
- savedWriting.splice(
- (savedWriting.indexOf(article)), 1);
- localStorage.setItem('savedWriting', JSON.stringify(savedWriting));
+ self.store.removeItem(article);
swal({
title:'Deleted!',
text: 'This writing no longer exists here.'
@@ -40,10 +36,7 @@ class Article extends Component {
}
render() {
- let article = this.state
- .savedWriting.find(el => {
- return (el.slug == this.props.params.slug);
- });
+ let article = this.article;
let content = article.content;
let date = article.date;
document.title = date;
@@ -62,7 +55,7 @@ class Article extends Component {
className="delete extLink"
onClick={(e) => {
e.preventDefault();
- this.delete(e, article);
+ this.delete(e, article, this);
}}>/delete</a>
</div>
);
View
@@ -36,6 +36,8 @@ class Editor extends Component {
constructor(props) {
super(props);
+ this.store = props.route.store;
+
this.state = {
timeSinceLastEdit: 0,
timeSinceStarted: 0,
@@ -77,18 +79,11 @@ class Editor extends Component {
});
}
else if (!self.timerCleared) {
- let savedItems;
- if (localStorage.getItem('savedWriting')) {
- savedItems = JSON.parse(localStorage.getItem('savedWriting'));
- } else {
- savedItems = [];
- }
- savedItems.push(
+ this.store.addItem(
this.generateSavedArticle(
this.state.content
)
);
- localStorage.setItem('savedWriting', JSON.stringify(savedItems));
swal({
title:'You\'re done!',
text: 'You can access your writing at /saved. Happy editing!'
View
@@ -13,12 +13,8 @@ import '../node_modules/sweetalert/dist/sweetalert.css';
class Saved extends Component {
constructor(props) {
super(props);
- let savedWriting;
- if (!localStorage.getItem('savedWriting')) {
- savedWriting = []
- } else {
- savedWriting = JSON.parse(localStorage.getItem('savedWriting'))
- }
+ this.store = props.route.store;
+ let savedWriting = props.route.store.getAll();
this.state = {
savedWritingAll: savedWriting,
savedWriting
@@ -56,7 +52,7 @@ class Saved extends Component {
return ('data:text/plain;charset=utf-8,' + encodeURIComponent(text));
}
- deleteAll(e) {
+ deleteAll(e, self) {
e.preventDefault();
swal({
title: 'Are you sure?',
@@ -66,7 +62,7 @@ class Saved extends Component {
confirmButtonText: 'Yes, delete it all!',
closeOnConfirm: false
}, () => {
- localStorage.removeItem('savedWriting');
+ self.store.reset();
swal({
title:'All writing deleted!',
text: 'Hope you backed up the stuff you cared about.'
@@ -77,7 +73,7 @@ class Saved extends Component {
})
}
- onDrop(acceptedFiles, rejectedFiles) {
+ onDrop(acceptedFiles, rejectedFiles, self) {
let reader = new FileReader();
if (rejectedFiles.length > 0 ||
acceptedFiles.length == 0) {
@@ -110,7 +106,7 @@ class Saved extends Component {
type: 'error'
});
}
- localStorage.setItem('savedWriting', content);
+ self.store.setAll(contentJSON);
swal({
title: 'Uploaded!',
text: 'The contents of your savefile can now be viewed locally at /saved.'
@@ -167,18 +163,23 @@ class Saved extends Component {
<a
href="#"
className="delete extLink"
- onClick={this.deleteAll}>/delete-all</a>
+ onClick={(e) => {
+ this.deleteAll(e, this)
+ }}>/delete-all</a>
<a
href={
- this.genDownloadURI(localStorage.getItem('savedWriting'))
+ this.genDownloadURI(JSON.stringify(
+ self.store.getAll()
+ ))
}
download="savefile.json"
- className="download extLink"
- onClick={this.downloadSaveFile}>/download</a>
+ className="download extLink">/download</a>
<Dropzone
className="dropzone"
accept="application/json"
- onDrop={this.onDrop}>
+ onDrop={(a, r) => {
+ this.onDrop(a, r, this);
+ }}>
<a
className="drop extLink"
href="#"
@@ -0,0 +1,7 @@
+import LocalStore from './localStore';
+
+let store = (name, existing) => {
+ return new LocalStore('savedWriting', existing);
+};
+
+export default store;
View
@@ -14,14 +14,32 @@ import {
browserHistory
} from 'react-router';
+import store from './configure-localStore';
+
+let appStore = store(
+ 'savedWriting',
+ localStorage.getItem('savedWriting')
+);
+
ReactDOM.render(
(
<Router history={browserHistory}>
<Route path='/' component={Menu} />
- <Route path='/write/:duration' component={Editor} />
- <Route path='/saved' component={Saved} />
- <Route path='/saved/:slug' component={Article} />
- <Route path='/about' component={About} />
+ <Route
+ path='/write/:duration'
+ store={appStore}
+ component={Editor} />
+ <Route
+ path='/saved'
+ store={appStore}
+ component={Saved} />
+ <Route
+ path='/saved/:slug'
+ store={appStore}
+ component={Article} />
+ <Route
+ path='/about'
+ component={About} />
</Router>
),
document.getElementById('root')
View
@@ -0,0 +1,109 @@
+export default class LocalStore {
+ constructor(storeName, existingStore) {
+ this.storeName = storeName;
+ if (existingStore) {
+ localStorage[storeName] = existingStore;
+ } else {
+ localStorage[storeName] = JSON.stringify([]);
+ }
+ }
+
+ getAll() {
+ return JSON.parse(
+ localStorage[this.storeName]
+ );
+ }
+
+ setAll(mutatedStore) {
+ localStorage[this.storeName] = JSON.stringify(mutatedStore);
+ }
+
+ addItem(item) {
+ let storeObject = this.getAll();
+ storeObject.push(item);
+ this.setAll(storeObject);
+ }
+
+ removeItem(item) {
+ let storeObject = this.getAll();
+ let index;
+ if (typeof item === 'object') {
+ index = indexOfWithObject(storeObject, item);
+ } else {
+ index = storeObject.indexOf(item);
+ }
+ storeObject.splice(
+ index,
+ 1
+ );
+ this.setAll(storeObject);
+ }
+
+ getItemByIndex(index) {
+ let storeObject = this.getAll();
+ return storeObject[index];
+ }
+
+ setItemByIndex(index, mutatedItem) {
+ let storeObject = this.getAll();
+ let mutatedStoreObject = storeObject;
+ mutatedStoreObject[index] = mutatedItem;
+ this.setAll(mutatedStoreObject);
+ }
+
+ getItemsByProperty(propertyKey, matchingValue) {
+ let storeObject = this.getAll();
+ let results = storeObject.filter(item => {
+ return item[propertyKey] === matchingValue;
+ });
+ return results;
+ }
+
+ setItemByProperty(propertyKey, matchingValue, modifiedItem) {
+ let storeObject = this.getAll();
+ let indexOfitemToModify = storeObject.findIndex(item => {
+ return item[propertyKey] == matchingValue;
+ });
+ storeObject[indexOfitemToModify] = modifiedItem;
+ this.setAll(storeObject);
+ }
+
+ delete() {
+ localStorage.removeItem(this.storeName);
+ }
+
+ reset() {
+ localStorage[this.storeName] = JSON.stringify([]);
+ }
+}
+
+// http://stackoverflow.com/questions/12604062/javascript-array-indexof-doesnt-search-objects
+function indexOfWithObject(arr, value) {
+ var a;
+ for (var i=0, iLen=arr.length; i<iLen; i++) {
+ a = arr[i];
+ if (a === value) return i;
+ if (typeof a == 'object') {
+ if (compareObj(arr[i], value)) {
+ return i;
+ }
+ } else {
+ // deal with other types
+ }
+ }
+ return -1;
+ // Extremely simple function, expects the values of all
+ // enumerable properties of both objects to be primitives.
+ function compareObj(o1, o2, cease) {
+ var p;
+ if (typeof o1 == 'object' && typeof o2 == 'object') {
+ for (p in o1) {
+ if (o1[p] != o2[p]) return false;
+ }
+ if (cease !== true) {
+ compareObj(o2, o1, true);
+ }
+ return true;
+ }
+ }
+}
View
@@ -0,0 +1,37 @@
+/*
+ Not functional yet due to reliance upon
+ browser-specific localStorage implementation.
+*/
+
+'use strict';
+
+let test = require('tape');
+let LocalStore = require('./localStore');
+
+test('getAll() test', t => {
+ t.plan(1);
+ let store = new LocalStore('test');
+ t.equal(store.getAll(), []);
+});
+
+test('setAll() test', t => {
+ t.plan(1);
+ let store = new LocalStore('test');
+ store.setAll([1,2,3]);
+ t.equal(store.getAll(), [1,2,3]);
+});
+
+test('addItem(item) test', t => {
+ t.plan(1);
+ let store = new LocalStore('test');
+ store.addItem({});
+ t.equal(store.getAll(), [{}]);
+});
+
+test('removeItem(item) test', t => {
+ t.plan(1);
+ let store = new LocalStore('test');
+ store.addItem({});
+ store.removeItem({});
+ t.equal(store.removeItem({}), []);
+});

0 comments on commit 69e5b0d

Please sign in to comment.