1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00

Added navigator and handle OneDrive auth

This commit is contained in:
Laurent Cozic 2017-11-06 18:35:04 +00:00
parent d763b13e44
commit 8dd41dca96
12 changed files with 305 additions and 77 deletions

View File

@ -14,50 +14,96 @@ const { sprintf } = require('sprintf-js');
const { JoplinDatabase } = require('lib/joplin-database.js');
const { DatabaseDriverNode } = require('lib/database-driver-node.js');
const { ElectronAppWrapper } = require('./ElectronAppWrapper');
const { defaultState } = require('lib/reducer.js');
const appDefaultState = Object.assign({}, defaultState, {
route: {
type: 'NAV_GO',
routeName: 'Main',
params: {},
},
navHistory: [],
});
class Application extends BaseApplication {
constructor() {
super();
}
hasGui() {
return true;
}
reducer(state = appDefaultState, action) {
let newState = state;
try {
switch (action.type) {
case 'NAV_BACK':
case 'NAV_GO':
const goingBack = action.type === 'NAV_BACK';
if (goingBack && !state.navHistory.length) break;
const currentRoute = state.route;
newState = Object.assign({}, state);
let newNavHistory = state.navHistory.slice();
if (goingBack) {
let newAction = null;
while (newNavHistory.length) {
newAction = newNavHistory.pop();
if (newAction.routeName !== state.route.routeName) break;
}
if (!newAction) break;
action = newAction;
}
if (!goingBack) newNavHistory.push(currentRoute);
newState.navHistory = newNavHistory
newState.route = action;
break;
case 'WINDOW_CONTENT_SIZE_SET':
newState = Object.assign({}, state);
newState.windowContentSize = action.size;
break;
}
} catch (error) {
error.message = 'In reducer: ' + error.message + ' Action: ' + JSON.stringify(action);
throw error;
}
return super.reducer(newState, action);
}
async start(argv) {
argv = await super.start(argv);
this.initRedux();
//this.gui_ = new ElectronAppWrapper(this.electronApp_, this, this.store());
// Since the settings need to be loaded before the store is created, it will never
// receive the SETTINGS_UPDATE_ALL even, which mean state.settings will not be
// initialised. So we manually call dispatchUpdateAll() to force an update.
Setting.dispatchUpdateAll();
try {
// this.gui_.setLogger(this.logger());
// await this.gui().start();
await FoldersScreenUtils.refreshFolders();
// Since the settings need to be loaded before the store is created, it will never
// receive the SETTINGS_UPDATE_ALL even, which mean state.settings will not be
// initialised. So we manually call dispatchUpdateAll() to force an update.
Setting.dispatchUpdateAll();
const tags = await Tag.allWithNotes();
await FoldersScreenUtils.refreshFolders();
this.dispatch({
type: 'TAGS_UPDATE_ALL',
tags: tags,
});
const tags = await Tag.allWithNotes();
this.dispatch({
type: 'TAGS_UPDATE_ALL',
tags: tags,
});
this.store().dispatch({
type: 'FOLDERS_SELECT',
id: Setting.value('activeFolderId'),
});
} catch (error) {
//await this.gui_.exit();
throw error;
}
this.store().dispatch({
type: 'FOLDERS_SELECT',
id: Setting.value('activeFolderId'),
});
}
}
@ -65,7 +111,6 @@ class Application extends BaseApplication {
let application_ = null;
function app() {
//if (!application_) throw new Error('Application has not been initialized');
if (!application_) application_ = new Application();
return application_;
}

View File

@ -18,6 +18,11 @@ class Bridge {
return { width: s[0], height: s[1] };
}
showMessageBox(options) {
const {dialog} = require('electron');
return dialog.showMessageBox(options);
}
}
let bridge_ = null;

View File

@ -0,0 +1,50 @@
const React = require('react');
const { connect } = require('react-redux');
const { SideBar } = require('./SideBar.min.js');
const { NoteList } = require('./NoteList.min.js');
const { NoteText } = require('./NoteText.min.js');
class MainScreenComponent extends React.Component {
render() {
const style = this.props.style;
const noteListStyle = {
width: Math.floor(style.width / 3),
height: style.height,
display: 'inline-block',
verticalAlign: 'top',
};
const noteTextStyle = {
width: noteListStyle.width,
height: style.height,
display: 'inline-block',
verticalAlign: 'top',
};
const sideBarStyle = {
width: style.width - (noteTextStyle.width + noteListStyle.width),
height: style.height,
display: 'inline-block',
verticalAlign: 'top',
};
return (
<div style={style}>
<SideBar style={sideBarStyle}></SideBar>
<NoteList itemHeight={40} style={noteListStyle}></NoteList>
<NoteText style={noteTextStyle}></NoteText>
</div>
);
}
}
const mapStateToProps = (state) => {
return {};
};
const MainScreen = connect(mapStateToProps)(MainScreenComponent);
module.exports = { MainScreen };

View File

@ -0,0 +1,34 @@
const React = require('react'); const Component = React.Component;
const { connect } = require('react-redux');
class NavigatorComponent extends Component {
render() {
if (!this.props.route) throw new Error('Route must not be null');
const route = this.props.route;
const Screen = this.props.screens[route.routeName].screen;
const screenStyle = {
width: this.props.style.width,
height: this.props.style.height,
};
return (
<div style={this.props.style}>
<Screen style={screenStyle}/>
</div>
);
}
}
const Navigator = connect(
(state) => {
return {
route: state.route,
};
}
)(NavigatorComponent)
module.exports = { Navigator };

View File

@ -0,0 +1,103 @@
const React = require('react');
const { connect } = require('react-redux');
const shared = require('lib/components/shared/side-menu-shared.js');
const { Synchronizer } = require('lib/synchronizer.js');
const { reg } = require('lib/registry.js');
const { bridge } = require('electron').remote.require('./bridge');
class OneDriveAuthScreenComponent extends React.Component {
constructor() {
super();
this.webview_ = null;
this.authCode_ = null;
}
back_click() {
this.props.dispatch({
type: 'NAV_BACK',
});
}
refresh_click() {
if (!this.webview_) return;
this.webview_.src = this.startUrl();
}
componentWillMount() {
this.setState({
webviewUrl: this.startUrl(),
webviewReady: false,
});
}
componentDidMount() {
this.webview_.addEventListener('dom-ready', this.webview_domReady.bind(this));
}
componentWillUnmount() {
this.webview_.addEventListener('dom-ready', this.webview_domReady.bind(this));
}
webview_domReady() {
this.setState({
webviewReady: true,
});
this.webview_.addEventListener('did-navigate', async (event) => {
const url = event.url;
if (this.authCode_) return;
if (url.indexOf(this.redirectUrl() + '?code=') !== 0) return;
let code = url.split('?code=');
this.authCode_ = code[1];
try {
await reg.oneDriveApi().execTokenRequest(this.authCode_, this.redirectUrl(), true);
this.props.dispatch({ type: 'NAV_BACK' });
reg.scheduleSync(0);
} catch (error) {
bridge().showMessageBox({
type: 'error',
message: error.message,
});
}
this.authCode_ = null;
});
}
startUrl() {
return reg.oneDriveApi().authCodeUrl(this.redirectUrl());
}
redirectUrl() {
return reg.oneDriveApi().nativeClientRedirectUrl();
}
render() {
const webviewStyle = {
width: this.props.style.width,
height: this.props.style.height,
overflow: 'hidden',
};
return (
<div>
<a href="#" onClick={() => {this.back_click()}}>BACK</a>
<a href="#" onClick={() => {this.refresh_click()}}>REFRESH</a>
<webview src={this.startUrl()} style={webviewStyle} nodeintegration="1" ref={elem => this.webview_ = elem} />
</div>
);
}
}
const mapStateToProps = (state) => {
return {};
};
const OneDriveAuthScreen = connect(mapStateToProps)(OneDriveAuthScreenComponent);
module.exports = { OneDriveAuthScreen };

View File

@ -3,9 +3,9 @@ const { render } = require('react-dom');
const { createStore } = require('redux');
const { connect, Provider } = require('react-redux');
const { SideBar } = require('./SideBar.min.js');
const { NoteList } = require('./NoteList.min.js');
const { NoteText } = require('./NoteText.min.js');
const { MainScreen } = require('./MainScreen.min.js');
const { OneDriveAuthScreen } = require('./OneDriveAuthScreen.min.js');
const { Navigator } = require('./Navigator.min.js');
const { app } = require('../app');
@ -25,7 +25,7 @@ async function initialize(dispatch) {
});
}
class ReactRootComponent extends React.Component {
class RootComponent extends React.Component {
async componentDidMount() {
if (this.props.appState == 'starting') {
@ -44,38 +44,18 @@ class ReactRootComponent extends React.Component {
}
render() {
const style = {
const navigatorStyle = {
width: this.props.size.width,
height: this.props.size.height,
};
const noteListStyle = {
width: Math.floor(this.props.size.width / 3),
height: this.props.size.height,
display: 'inline-block',
verticalAlign: 'top',
};
const noteTextStyle = {
width: noteListStyle.width,
height: this.props.size.height,
display: 'inline-block',
verticalAlign: 'top',
};
const sideBarStyle = {
width: this.props.size.width - (noteTextStyle.width + noteListStyle.width),
height: this.props.size.height,
display: 'inline-block',
verticalAlign: 'top',
const screens = {
Main: { screen: MainScreen },
OneDriveAuth: { screen: OneDriveAuthScreen },
};
return (
<div style={style}>
<SideBar style={sideBarStyle}></SideBar>
<NoteList itemHeight={40} style={noteListStyle}></NoteList>
<NoteText style={noteTextStyle}></NoteText>
</div>
<Navigator style={navigatorStyle} screens={screens} />
);
}
@ -88,13 +68,13 @@ const mapStateToProps = (state) => {
};
};
const ReactRoot = connect(mapStateToProps)(ReactRootComponent);
const Root = connect(mapStateToProps)(RootComponent);
const store = app().store();
render(
<Provider store={store}>
<ReactRoot />
<Root />
</Provider>,
document.getElementById('react-root')
)

View File

@ -19,6 +19,13 @@ class SideBarComponent extends React.Component {
});
}
sync_click() {
this.props.dispatch({
type: 'NAV_GO',
routeName: 'OneDriveAuth',
});
}
folderItem(folder, selected) {
let classes = [];
if (selected) classes.push('selected');
@ -36,7 +43,7 @@ class SideBarComponent extends React.Component {
}
synchronizeButton(label) {
return <div key="sync_button">{label}</div>
return <a href="#" key="sync_button" onClick={() => {this.sync_click()}}>{label}</a>
}
render() {

View File

@ -231,8 +231,12 @@ class BaseApplication {
if (this.store()) return this.store().dispatch(action);
}
reducer(state = defaultState, action) {
return reducer(state, action);
}
initRedux() {
this.store_ = createStore(reducer, applyMiddleware(this.generalMiddleware()));
this.store_ = createStore(this.reducer, applyMiddleware(this.generalMiddleware()));
BaseModel.dispatch = this.store().dispatch;
FoldersScreenUtils.dispatch = this.store().dispatch;
}

View File

@ -32,7 +32,7 @@ class OneDriveLoginScreenComponent extends BaseScreenComponent {
}
redirectUrl() {
return 'https://login.microsoftonline.com/common/oauth2/nativeclient';
return reg.oneDriveApi().nativeClientRedirectUrl();
}
async webview_load(noIdeaWhatThisIs) {

View File

@ -47,6 +47,10 @@ class OneDriveApi {
return 'https://login.microsoftonline.com/common/oauth2/v2.0/token';
}
nativeClientRedirectUrl() {
return 'https://login.microsoftonline.com/common/oauth2/nativeclient';
}
auth() {
return this.auth_;
}

View File

@ -23,12 +23,6 @@ const defaultState = {
searchQuery: '',
settings: {},
appState: 'starting',
sideMenuOpenPercent: 0,
route: {
type: 'NAV_GO',
routeName: 'Welcome',
params: {},
},
windowContentSize: { width: 0, height: 0 },
};
@ -107,7 +101,6 @@ function defaultNotesParentType(state, exclusion) {
const reducer = (state = defaultState, action) => {
let newState = state;
let historyGoingBack = false;
try {
switch (action.type) {
@ -305,12 +298,6 @@ const reducer = (state = defaultState, action) => {
newState.appState = action.state;
break;
case 'WINDOW_CONTENT_SIZE_SET':
newState = Object.assign({}, state);
newState.windowContentSize = action.size;
break;
}
} catch (error) {
error.message = 'In reducer: ' + error.message + ' Action: ' + JSON.stringify(action);

View File

@ -74,7 +74,16 @@ function historyCanGoBackTo(route) {
return true;
}
const appReducer = (state = defaultState, action) => {
const appDefaultState = Object.assign({}, defaultState, {
sideMenuOpenPercent: 0,
route: {
type: 'NAV_GO',
routeName: 'Welcome',
params: {},
},
});
const appReducer = (state = appDefaultState, action) => {
let newState = state;
let historyGoingBack = false;