Skip to content

Commit

Permalink
Basic app
Browse files Browse the repository at this point in the history
  • Loading branch information
trys committed Dec 26, 2018
0 parents commit 2c98402
Show file tree
Hide file tree
Showing 24 changed files with 586 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .babelrc
@@ -0,0 +1,9 @@
{
"env": {
"test": {
"presets": [
["preact-cli/babel", { "modules": "commonjs" }]
]
}
}
}
7 changes: 7 additions & 0 deletions .gitignore
@@ -0,0 +1,7 @@
node_modules
/build
/*.log
*.lock

package-lock.json
.env
22 changes: 22 additions & 0 deletions README.md
@@ -0,0 +1,22 @@
# journal

## CLI Commands

``` bash
# install dependencies
npm install

# serve with hot reload at localhost:8080
npm run dev

# build for production with minification
npm run build

# test the production build locally
npm run serve

# run tests with jest and preact-render-spy
npm run test
```

For detailed explanation on how things work, checkout the [CLI Readme](https://github.com/developit/preact-cli/blob/master/README.md).
36 changes: 36 additions & 0 deletions package.json
@@ -0,0 +1,36 @@
{
"private": true,
"name": "Journal",
"version": "0.0.1",
"license": "MIT",
"scripts": {
"start": "per-env",
"start:production": "npm run -s serve",
"start:development": "npm run -s dev",
"build": "preact build",
"serve": "preact build && preact serve",
"dev": "preact watch",
"lint": "eslint src"
},
"eslintConfig": {
"extends": "eslint-config-synacor"
},
"eslintIgnore": [
"build/*"
],
"devDependencies": {
"eslint": "^4.9.0",
"eslint-config-synacor": "^2.0.2",
"identity-obj-proxy": "^3.0.0",
"per-env": "^1.0.2",
"preact-cli": "^2.1.0",
"preact-cli-plugin-env-vars": "^1.2.1",
"preact-render-spy": "^1.2.1"
},
"dependencies": {
"idb": "^2.1.3",
"preact": "^8.2.6",
"preact-compat": "^3.17.0",
"preact-router": "^2.5.7"
}
}
5 changes: 5 additions & 0 deletions preact.config.js
@@ -0,0 +1,5 @@
import envVars from 'preact-cli-plugin-env-vars';

export default function(config, env, helpers) {
envVars(config, env, helpers);
}
Binary file added src/assets/favicon.ico
Binary file not shown.
Binary file added src/assets/icons/android-chrome-192x192.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/icons/android-chrome-512x512.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/icons/apple-touch-icon.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/icons/favicon-16x16.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/icons/favicon-32x32.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/icons/mstile-150x150.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
47 changes: 47 additions & 0 deletions src/components/app.js
@@ -0,0 +1,47 @@
import { h, Component } from 'preact';
import { Router } from 'preact-router';
import idb from 'idb';

import Header from './header';

// Code-splitting is automated for routes
import Home from '../routes/home';
import Day from '../routes/day';
import Questions from '../routes/questions';
import Roadmap from '../routes/roadmap';

const tables = [
() => {},
db => {
db.createObjectStore('questions');
},
db => {
db.createObjectStore('entries');
}
];

export default class App extends Component {
componentDidMount() {
const key = Number(process.env.PREACT_APP_DB_VERSION);
const dbPromise = idb.open('entries-store', key, upgradeDB => {
for (let index = upgradeDB.oldVersion + 1; index <= key; index++) {
console.log(index);
tables[index] && tables[index](upgradeDB);
}
});
}

render() {
return (
<div id="app">
<Header />
<Router onChange={this.handleRoute}>
<Home path="/" />
<Roadmap path="/roadmap/" />
<Questions path="/questions/" />
<Day path="/:year/:month/:day" />
</Router>
</div>
);
}
}
16 changes: 16 additions & 0 deletions src/components/header/index.js
@@ -0,0 +1,16 @@
import { h } from 'preact';
import { Link } from 'preact-router/match';
import style from './style';

const Header = () => (
<header class={style.header}>
<Link activeClassName={style.active} href="/">
J
</Link>
<Link activeClassName={style.active} href="/questions/">
Questions
</Link>
</header>
);

export default Header;
22 changes: 22 additions & 0 deletions src/components/header/style.css
@@ -0,0 +1,22 @@
.header {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 56px;
padding: 0;
background: #673AB7;
z-index: 50;
}

.header a {
display: inline-block;
height: 56px;
line-height: 56px;
padding: 0 15px;
min-width: 50px;
text-align: center;
background: rgba(255,255,255,0);
text-decoration: none;
color: #FFF;
}
4 changes: 4 additions & 0 deletions src/index.js
@@ -0,0 +1,4 @@
import './style';
import App from './components/app';

export default App;
21 changes: 21 additions & 0 deletions src/manifest.json
@@ -0,0 +1,21 @@
{
"name": "Journal",
"short_name": "Journal",
"start_url": "/",
"display": "standalone",
"orientation": "portrait",
"background_color": "#fff",
"theme_color": "#673ab8",
"icons": [
{
"src": "/assets/icons/android-chrome-192x192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "/assets/icons/android-chrome-512x512.png",
"type": "image/png",
"sizes": "512x512"
}
]
}
148 changes: 148 additions & 0 deletions src/routes/day/index.js
@@ -0,0 +1,148 @@
import { h, Component } from 'preact';
import style from './style';
import idb from 'idb';
import { Link } from 'preact-router/match';
import { ymd, url, format } from '../../utils/date';

export default class Day extends Component {
state = {
date: null,
db: null,
questions: null,
key: ''
};

componentDidMount() {
this.getData(this.props);
}

componentWillReceiveProps(props) {
this.getData(props);
}

getData = props => {
const { day, month, year } = props;
const date = new Date(year, Number(month) - 1, day);
const key = ymd(date);

if (date.toString() === 'Invalid Date') {
window.location.href = '/';
return;
}

const dbPromise = idb.open('entries-store');

const db = {
get(key, table = 'entries') {
return dbPromise.then(db => {
return db
.transaction(table)
.objectStore(table)
.get(key);
});
},
set(key, val, table = 'entries') {
return dbPromise.then(db => {
const tx = db.transaction(table, 'readwrite');
tx.objectStore(table).put(val, key);
return tx.complete;
});
},
keys(table = 'questions') {
return dbPromise.then(db => {
const tx = db.transaction(table);
const keys = [];
const store = tx.objectStore(table);

// This would be store.getAllKeys(), but it isn't supported by Edge or Safari.
// openKeyCursor isn't supported by Safari, so we fall back
(store.iterateKeyCursor || store.iterateCursor).call(
store,
cursor => {
if (!cursor) return;
keys.push(cursor.key);
cursor.continue();
}
);

return tx.complete.then(() => keys);
});
}
};

db.keys().then(keys => {
Promise.all(keys.map(x => db.get(x, 'questions'))).then(results => {
const questions = results.map((question, index) => ({
slug: keys[index],
question
}));

Promise.all(questions.map(({ slug }) => db.get(`${key}_${slug}`))).then(
answers => {
questions.forEach((question, index) => {
question.answer = answers[index] || '';
});

this.setState({ date, db, questions, key });
}
);
});
});
};

updateAnswer = (slug, answer) => {
const questions = [...this.state.questions];
const question = questions.find(x => x.slug === slug);
if (!question) {
return;
}

const { key } = this.state;
question.answer = answer;
this.state.db.set(`${key}_${slug}`, answer);

this.setState({ questions });
};

render(props, { date, questions }) {
if (date === null) {
return null;
}

const today = new Date();
const yesterday = new Date(date);
yesterday.setDate(yesterday.getDate() - 1);

const tomorrow = new Date(date);
tomorrow.setDate(tomorrow.getDate() + 1);

const isToday = ymd(today) === ymd(date);

return (
<div>
<h1>
{format(date)}
<Link href={url(yesterday)}>&lt;</Link>
<Link disabled={isToday} href={isToday ? '' : url(tomorrow)}>
&gt;
</Link>
</h1>

{questions === null ? null : questions.length ? (
questions.map(({ slug, question, answer = '' }) => (
<div key={slug} class={style.question}>
<label for={slug}>{question}</label>
<textarea
id={slug}
value={answer}
onInput={event => this.updateAnswer(slug, event.target.value)}
/>
</div>
))
) : (
<Link href="/questions/">Write your first question</Link>
)}
</div>
);
}
}
3 changes: 3 additions & 0 deletions src/routes/day/style.css
@@ -0,0 +1,3 @@
.question {
margin-bottom: 30px;
}
13 changes: 13 additions & 0 deletions src/routes/home/index.js
@@ -0,0 +1,13 @@
import { h } from 'preact';
import { Link } from 'preact-router/match';
import { url } from '../../utils/date';

const today = url(new Date());

const Home = () => (
<div>
<Link href={today}>Today</Link>
</div>
);

export default Home;

0 comments on commit 2c98402

Please sign in to comment.