Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Dropbox import/export #43

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
PREACT_APP_TRACKING=

PREACT_APP_DROPBOX_CLIENT_ID=
PREACT_APP_DROPBOX_REDIRCT_URI=
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"prettier": "1.15.3"
},
"dependencies": {
"dropbox": "^4.0.15",
"eslint-config-prettier": "^3.3.0",
"idb": "^3.0.2",
"preact": "^8.2.6",
Expand Down
4 changes: 4 additions & 0 deletions src/components/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import Settings from '../routes/settings';
import GetStarted from '../routes/get-started';
import Highlights from '../routes/highlights';
import About from '../routes/about';
import Auth from '../routes/auth';
import AuthCallback from '../routes/auth-callback';
import NotFound from '../routes/not-found';
import { getDefaultTheme, prefersAnimation } from '../utils/theme';
import { connect } from 'unistore/preact';
Expand Down Expand Up @@ -98,6 +100,8 @@ class App extends Component {
<GetStarted path="/get-started/" />
<Settings path="/settings/" />
<About path="/about/" />
<Auth path="/auth/:provider" />
<AuthCallback path="/auth/callback/:provider" />
<Highlights path="/highlights/" />
<Day path="/:year/:month/:day" />
<Month path="/:year/:month" />
Expand Down
21 changes: 21 additions & 0 deletions src/routes/auth-callback/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { h, Component } from 'preact';
import { route } from 'preact-router';
import storage from '../../utils/storage';

class AuthCallback extends Component {
constructor(props) {
super();
const { provider, ...query } = props;
const msg = storage.authCallback(provider, query, () =>
route('/settings', true)
);
this.state = {
msg,
};
}
render({}, { msg }) {
return <div>{msg}</div>;
}
}

export default AuthCallback;
17 changes: 17 additions & 0 deletions src/routes/auth/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { h, Component } from 'preact';
import storage from '../../utils/storage';

class Auth extends Component {
constructor(props) {
super();
const { provider } = props;
const msg = storage.requestAuth(provider);
this.state = { msg };
}

render({}, { msg }) {
return <div>{msg}</div>;
}
}

export default Auth;
240 changes: 130 additions & 110 deletions src/routes/settings/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { h, Component } from 'preact';
import { ymd } from '../../utils/date';
import { Link } from 'preact-router/match';
import { slugify } from '../../utils/slugify';
import { QuestionList } from '../../components/QuestionList';
import { AddQuestion } from '../../components/AddQuestion';
import { ScaryButton } from '../../components/ScaryButton';
import { getDefaultTheme, prefersAnimation } from '../../utils/theme';
import { actions } from '../../store/actions';
import { connect } from 'unistore/preact';
import storage from '../../utils/storage';

class Settings extends Component {
state = {
Expand All @@ -24,10 +26,14 @@ class Settings extends Component {
this.setState({ questions });
}

updateSetting = key => {
return event => {
this.props.updateSetting({ key, value: event.target.value });
};
updateSetting = key => event => {
this.props.updateSetting({ key, value: event.target.value });
};

updateStorageAdapter = event => {
const adapter = event.target.value;
this.props.updateSetting({ key: 'storageAdapter', value: adapter });
storage.setAdapter(adapter);
};

updateQuestion = (slug, value, attribute = 'text') => {
Expand Down Expand Up @@ -93,95 +99,48 @@ class Settings extends Component {
}
};

prepareExport = async () => {
try {
const MIME_TYPE = 'text/json;charset=utf-8';

this.clean();

this.setState({ exporting: 1, files: [] });
deleteData = async () => {
await this.props.db.clear('entries');
await this.props.db.clear('questions');
await this.props.db.clear('highlights');
localStorage.removeItem('journalbook_onboarded');
window.location.href = '/';
};

const data = await this.getData();
const blob = new Blob([JSON.stringify(data)], { type: MIME_TYPE });
export = async () => {
this.clean();

const file = {
name: `journalbook_${ymd()}.json`,
data: window.URL.createObjectURL(blob),
};
this.setState({ files: [file], exporting: 2 });
this.setState({ exporting: 1, files: [] });
try {
const files = await storage.adapter.export();
this.setState({ files, exporting: 2 }, () =>
setTimeout(() => this.setState({ exporting: 0 }), 1500)
);
} catch (e) {
console.error(e);
this.setState({ files: [], exporting: 0 });
}
};

importData = async event => {
const reader = new FileReader();
const file = event.target.files[0];
import = async () => {
this.setState({ importing: true });

reader.onload = (() => async e => {
const { entries, questions, highlights = [], settings = {} } = JSON.parse(
e.target.result
);
if (!entries || !questions || !Array.isArray(highlights)) {
return;
}

const questionKeys = Object.keys(questions);
questionKeys.map(async key => {
const current = await this.props.db.get('questions', key);
if (!current) {
await this.props.db.set('questions', key, questions[key]);
}
});

const entryKeys = Object.keys(entries);
await Promise.all(
entryKeys.map(async key => {
const current = await this.props.db.get('entries', key);
if (!current) {
return this.props.db.set('entries', key, entries[key]);
}
})
);

const settingKeys = Object.keys(settings);
await Promise.all(
settingKeys.map(async key => {
const current = await this.props.db.get('settings', key);
if (!current) {
return this.props.db.set('settings', key, settings[key]);
}
})
);

await Promise.all(
highlights.map(async key => {
return this.props.db.set('highlights', key, true);
})
);

localStorage.setItem('journalbook_onboarded', true);
localStorage.setItem('journalbook_dates_migrated', true);

window.location.reload();
})();

reader.readAsText(file);
await storage.adapter.import();
localStorage.setItem('journalbook_onboarded', true);
localStorage.setItem('journalbook_dates_migrated', true);
this.setState({ importing: false });
window.location.reload();
};

deleteData = async () => {
await this.props.db.clear('entries');
await this.props.db.clear('questions');
await this.props.db.clear('highlights');
await this.props.db.clear('highlights');
localStorage.removeItem('journalbook_onboarded');
window.location.href = '/';
logout = async () => {
storage.adapter.logout();
storage.setAdapter('file');
this.updateStorageAdapter({ target: { value: 'file' } });
window.location.reload();
};

render({ settings = {} }, { questions, exporting, files, importing }) {
const theme = settings.theme || getDefaultTheme(settings);
const storageAdapter = settings.storageAdapter || storage.getAdapter();
const animation = settings.animation || prefersAnimation(settings);

return (
Expand All @@ -199,41 +158,102 @@ class Settings extends Component {

<h2>Manage your data</h2>

{exporting === 2 && files.length ? (
<a
class="button button--space"
download={files[0].name}
href={files[0].data}
onClick={() => {
setTimeout(() => {
this.clean();
this.setState({ exporting: 0 });
}, 1500);
}}
>
Click to Download
</a>
) : (
<button
type="button"
class={`button button--space button--grey`}
onClick={this.prepareExport}
>
{['Export', 'Exporting'][exporting]}
</button>
<label for="storage">Storage</label>
<fieldset id="storage">
<label for="file">
<input
type="radio"
id="file"
name="storage"
value="file"
checked={storageAdapter === 'file'}
onChange={this.updateStorageAdapter}
/>
<span class="button button--space button--grey">Local File</span>
</label>
<label for="dropbox">
<input
type="radio"
id="dropbox"
name="storage"
value="dropbox"
checked={storageAdapter === 'dropbox'}
onChange={this.updateStorageAdapter}
/>
<span class="button button--space button--grey">Dropbox</span>
</label>
</fieldset>

{storageAdapter === 'file' && (
<div>
<label>Local File</label>
{exporting === 2 && files.length ? (
<a
class="button button--space"
download={files[0].name}
href={files[0].data}
onClick={() => {
setTimeout(() => {
this.clean();
this.setState({ exporting: 0 });
}, 1500);
}}
>
Click to Download
</a>
) : (
<button
type="button"
class={`button button--space button--grey`}
onClick={this.export}
>
{['Export', 'Exporting'][exporting]}
</button>
)}

<input
type="file"
class="screen-reader-only"
id="import"
onChange={this.import}
accept="application/json"
/>
<label for="import" class="button button--grey">
{importing ? 'Importing...' : 'Import'}
</label>
</div>
)}

<input
type="file"
class="screen-reader-only"
id="import"
onChange={this.importData}
accept="application/json"
/>
<label for="import" class="button button--grey">
{importing ? 'Importing...' : 'Import'}
</label>
{storageAdapter === 'dropbox' && (
<div>
<label>Dropbox</label>
{storage.adapters.dropbox.isAuthenticated() ? (
<div>
<button
type="button"
class={`button button--space button--grey`}
onClick={this.export}
>
{['Export', 'Exporting', 'Exported!'][exporting]}
</button>
<button
type="button"
class={`button button--grey`}
onClick={this.import}
>
{importing ? 'Importing...' : 'Import'}
</button>
<ScaryButton onClick={this.logout}>Sign Out</ScaryButton>
</div>
) : (
<Link href="/auth/dropbox" class="button">
Login with Dropbox
</Link>
)}
</div>
)}

<label for="delete">Browser data</label>
<ScaryButton onClick={this.deleteData}>Delete your data</ScaryButton>
</div>

Expand Down
1 change: 1 addition & 0 deletions src/store/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const store = {
settings: {
theme: '',
animation: '',
storageAdapter: '',
},
db: null,
};
Expand Down
Loading