mirror of
https://github.com/laurent22/joplin.git
synced 2024-11-27 08:21:03 +02:00
Electron: added toolbar and fixed various state issues
This commit is contained in:
parent
a346116d5f
commit
52cb10dd4e
25
ElectronClient/app/eventManager.js
Normal file
25
ElectronClient/app/eventManager.js
Normal file
@ -0,0 +1,25 @@
|
||||
const events = require('events');
|
||||
|
||||
class EventManager {
|
||||
|
||||
constructor() {
|
||||
this.emitter_ = new events.EventEmitter();
|
||||
}
|
||||
|
||||
on(eventName, callback) {
|
||||
return this.emitter_.on(eventName, callback);
|
||||
}
|
||||
|
||||
emit(eventName, object = null) {
|
||||
return this.emitter_.emit(eventName, object);
|
||||
}
|
||||
|
||||
removeListener(eventName, callback) {
|
||||
return this.emitter_.removeListener(eventName, callback);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const eventManager = new EventManager();
|
||||
|
||||
module.exports = eventManager;
|
@ -41,7 +41,7 @@ class HeaderComponent extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const style = this.props.style;
|
||||
const style = Object.assign({}, this.props.style);
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const showBackButton = this.props.showBackButton === undefined || this.props.showBackButton === true;
|
||||
style.height = theme.headerHeight;
|
||||
|
@ -15,6 +15,7 @@ const { themeStyle } = require('../theme.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const layoutUtils = require('lib/layout-utils.js');
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
const eventManager = require('../eventManager');
|
||||
|
||||
class MainScreenComponent extends React.Component {
|
||||
|
||||
@ -205,6 +206,7 @@ class MainScreenComponent extends React.Component {
|
||||
|
||||
if (newNote) {
|
||||
await Note.save(newNote);
|
||||
eventManager.emit('alarmChange', { noteId: note.id });
|
||||
}
|
||||
|
||||
this.setState({ promptOptions: null });
|
||||
@ -223,44 +225,58 @@ class MainScreenComponent extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
styles(themeId, width, height) {
|
||||
const styleKey = themeId + '_' + width + '_' + height;
|
||||
if (styleKey === this.styleKey_) return this.styles_;
|
||||
|
||||
const theme = themeStyle(themeId);
|
||||
|
||||
this.styleKey_ = styleKey;
|
||||
|
||||
this.styles_ = {};
|
||||
|
||||
const rowHeight = height - theme.headerHeight;
|
||||
|
||||
this.styles_.header = {
|
||||
width: width,
|
||||
};
|
||||
|
||||
this.styles_.sideBar = {
|
||||
width: Math.floor(layoutUtils.size(width * .2, 150, 300)),
|
||||
height: rowHeight,
|
||||
display: 'inline-block',
|
||||
verticalAlign: 'top',
|
||||
};
|
||||
|
||||
this.styles_.noteList = {
|
||||
width: Math.floor(layoutUtils.size(width * .2, 150, 300)),
|
||||
height: rowHeight,
|
||||
display: 'inline-block',
|
||||
verticalAlign: 'top',
|
||||
};
|
||||
|
||||
this.styles_.noteText = {
|
||||
width: Math.floor(layoutUtils.size(width - this.styles_.sideBar.width - this.styles_.noteList.width, 0)),
|
||||
height: rowHeight,
|
||||
display: 'inline-block',
|
||||
verticalAlign: 'top',
|
||||
};
|
||||
|
||||
this.styles_.prompt = {
|
||||
width: width,
|
||||
height: height,
|
||||
};
|
||||
|
||||
return this.styles_;
|
||||
}
|
||||
|
||||
render() {
|
||||
const style = this.props.style;
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const promptOptions = this.state.promptOptions;
|
||||
const folders = this.props.folders;
|
||||
const notes = this.props.notes;
|
||||
|
||||
const headerStyle = {
|
||||
width: style.width,
|
||||
};
|
||||
|
||||
const rowHeight = style.height - theme.headerHeight;
|
||||
|
||||
const sideBarStyle = {
|
||||
width: Math.floor(layoutUtils.size(style.width * .2, 150, 300)),
|
||||
height: rowHeight,
|
||||
display: 'inline-block',
|
||||
verticalAlign: 'top',
|
||||
};
|
||||
|
||||
const noteListStyle = {
|
||||
width: Math.floor(layoutUtils.size(style.width * .2, 150, 300)),
|
||||
height: rowHeight,
|
||||
display: 'inline-block',
|
||||
verticalAlign: 'top',
|
||||
};
|
||||
|
||||
const noteTextStyle = {
|
||||
width: Math.floor(layoutUtils.size(style.width - sideBarStyle.width - noteListStyle.width, 0)),
|
||||
height: rowHeight,
|
||||
display: 'inline-block',
|
||||
verticalAlign: 'top',
|
||||
};
|
||||
|
||||
const promptStyle = {
|
||||
width: style.width,
|
||||
height: style.height,
|
||||
};
|
||||
const styles = this.styles(this.props.theme, style.width, style.height);
|
||||
|
||||
const headerButtons = [];
|
||||
|
||||
@ -299,23 +315,29 @@ class MainScreenComponent extends React.Component {
|
||||
},
|
||||
});
|
||||
|
||||
if (!this.promptOnClose_) {
|
||||
this.promptOnClose_ = (answer, buttonType) => {
|
||||
return this.state.promptOptions.onClose(answer, buttonType);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={style}>
|
||||
<PromptDialog
|
||||
autocomplete={promptOptions && ('autocomplete' in promptOptions) ? promptOptions.autocomplete : null}
|
||||
value={promptOptions && promptOptions.value ? promptOptions.value : ''}
|
||||
defaultValue={promptOptions && promptOptions.value ? promptOptions.value : ''}
|
||||
theme={this.props.theme}
|
||||
style={promptStyle}
|
||||
onClose={(answer, buttonType) => promptOptions.onClose(answer, buttonType)}
|
||||
style={styles.prompt}
|
||||
onClose={this.promptOnClose_}
|
||||
label={promptOptions ? promptOptions.label : ''}
|
||||
description={promptOptions ? promptOptions.description : null}
|
||||
visible={!!this.state.promptOptions}
|
||||
buttons={promptOptions && ('buttons' in promptOptions) ? promptOptions.buttons : null}
|
||||
inputType={promptOptions && ('inputType' in promptOptions) ? promptOptions.inputType : null} />
|
||||
<Header style={headerStyle} showBackButton={false} buttons={headerButtons} />
|
||||
<SideBar style={sideBarStyle} />
|
||||
<NoteList style={noteListStyle} />
|
||||
<NoteText style={noteTextStyle} visiblePanes={this.props.noteVisiblePanes} />
|
||||
<Header style={styles.header} showBackButton={false} buttons={headerButtons} />
|
||||
<SideBar style={styles.sideBar} />
|
||||
<NoteList style={styles.noteList} />
|
||||
<NoteText style={styles.noteText} visiblePanes={this.props.noteVisiblePanes} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ const { _ } = require('lib/locale.js');
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
const Menu = bridge().Menu;
|
||||
const MenuItem = bridge().MenuItem;
|
||||
const eventManager = require('../eventManager');
|
||||
|
||||
class NoteListComponent extends React.Component {
|
||||
|
||||
@ -69,6 +70,7 @@ class NoteListComponent extends React.Component {
|
||||
for (let i = 0; i < noteIds.length; i++) {
|
||||
const note = await Note.load(noteIds[i]);
|
||||
await Note.save(Note.toggleIsTodo(note));
|
||||
eventManager.emit('noteTypeToggle', { noteId: note.id });
|
||||
}
|
||||
}}));
|
||||
|
||||
@ -119,6 +121,7 @@ class NoteListComponent extends React.Component {
|
||||
todo_completed: checked ? time.unixMs() : 0,
|
||||
}
|
||||
await Note.save(newNote);
|
||||
eventManager.emit('todoToggle', { noteId: item.id });
|
||||
}
|
||||
|
||||
const hPadding = 10;
|
||||
|
@ -1,7 +1,9 @@
|
||||
const React = require('react');
|
||||
const { Note } = require('lib/models/note.js');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const { Setting } = require('lib/models/setting.js');
|
||||
const { IconButton } = require('./IconButton.min.js');
|
||||
const Toolbar = require('./Toolbar.min.js');
|
||||
const { connect } = require('react-redux');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { reg } = require('lib/registry.js');
|
||||
@ -13,6 +15,7 @@ const AceEditor = require('react-ace').default;
|
||||
const Menu = bridge().Menu;
|
||||
const MenuItem = bridge().MenuItem;
|
||||
const { shim } = require('lib/shim.js');
|
||||
const eventManager = require('../eventManager');
|
||||
|
||||
require('brace/mode/markdown');
|
||||
// https://ace.c9.io/build/kitchen-sink.html
|
||||
@ -55,6 +58,10 @@ class NoteTextComponent extends React.Component {
|
||||
this.restoreScrollTop_ = null;
|
||||
}
|
||||
}
|
||||
|
||||
this.onAlarmChange_ = (event) => { if (event.noteId === this.props.noteId) this.reloadNote(this.props); }
|
||||
this.onNoteTypeToggle_ = (event) => { if (event.noteId === this.props.noteId) this.reloadNote(this.props); }
|
||||
this.onTodoToggle_ = (event) => { if (event.noteId === this.props.noteId) this.reloadNote(this.props); }
|
||||
}
|
||||
|
||||
mdToHtml() {
|
||||
@ -82,6 +89,10 @@ class NoteTextComponent extends React.Component {
|
||||
});
|
||||
|
||||
this.lastLoadedNoteId_ = note ? note.id : null;
|
||||
|
||||
eventManager.on('alarmChange', this.onAlarmChange_);
|
||||
eventManager.on('noteTypeToggle', this.onNoteTypeToggle_);
|
||||
eventManager.on('todoToggle', this.onTodoToggle_);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@ -89,6 +100,10 @@ class NoteTextComponent extends React.Component {
|
||||
|
||||
this.mdToHtml_ = null;
|
||||
this.destroyWebview();
|
||||
|
||||
eventManager.removeListener('alarmChange', this.onAlarmChange_);
|
||||
eventManager.removeListener('noteTypeToggle', this.onNoteTypeToggle_);
|
||||
eventManager.removeListener('todoToggle', this.onTodoToggle_);
|
||||
}
|
||||
|
||||
async saveIfNeeded() {
|
||||
@ -135,8 +150,8 @@ class NoteTextComponent extends React.Component {
|
||||
// and then (in the renderer callback) to the value we actually need. The first
|
||||
// operation helps clear the scroll position cache. See:
|
||||
// https://github.com/ajaxorg/ace/issues/2195
|
||||
this.editorSetScrollTop(1);
|
||||
this.restoreScrollTop_ = 0;
|
||||
this.editorSetScrollTop(1);
|
||||
this.restoreScrollTop_ = 0;
|
||||
|
||||
this.setState({
|
||||
note: note,
|
||||
@ -315,6 +330,42 @@ class NoteTextComponent extends React.Component {
|
||||
this.scheduleSave();
|
||||
}
|
||||
|
||||
async commandAttachFile() {
|
||||
const noteId = this.props.noteId;
|
||||
if (!noteId) return;
|
||||
|
||||
const filePaths = bridge().showOpenDialog({
|
||||
properties: ['openFile', 'createDirectory'],
|
||||
});
|
||||
if (!filePaths || !filePaths.length) return;
|
||||
|
||||
await this.saveIfNeeded();
|
||||
const note = await Note.load(noteId);
|
||||
|
||||
try {
|
||||
reg.logger().info('Attaching ' + filePaths[0]);
|
||||
const newNote = await shim.attachFileToNote(note, filePaths[0]);
|
||||
reg.logger().info('File was attached.');
|
||||
this.setState({
|
||||
note: newNote,
|
||||
lastSavedNote: Object.assign({}, newNote),
|
||||
});
|
||||
} catch (error) {
|
||||
reg.logger().error(error);
|
||||
}
|
||||
}
|
||||
|
||||
commandSetAlarm() {
|
||||
const noteId = this.props.noteId;
|
||||
if (!noteId) return;
|
||||
|
||||
this.props.dispatch({
|
||||
type: 'WINDOW_COMMAND',
|
||||
name: 'editAlarm',
|
||||
noteId: noteId,
|
||||
});
|
||||
}
|
||||
|
||||
itemContextMenu(event) {
|
||||
const noteId = this.props.noteId;
|
||||
if (!noteId) return;
|
||||
@ -322,38 +373,26 @@ class NoteTextComponent extends React.Component {
|
||||
const menu = new Menu()
|
||||
|
||||
menu.append(new MenuItem({label: _('Attach file'), click: async () => {
|
||||
const filePaths = bridge().showOpenDialog({
|
||||
properties: ['openFile', 'createDirectory'],
|
||||
});
|
||||
if (!filePaths || !filePaths.length) return;
|
||||
|
||||
await this.saveIfNeeded();
|
||||
const note = await Note.load(noteId);
|
||||
|
||||
try {
|
||||
reg.logger().info('Attaching ' + filePaths[0]);
|
||||
const newNote = await shim.attachFileToNote(note, filePaths[0]);
|
||||
reg.logger().info('File was attached.');
|
||||
this.setState({
|
||||
note: newNote,
|
||||
lastSavedNote: Object.assign({}, newNote),
|
||||
});
|
||||
} catch (error) {
|
||||
reg.logger().error(error);
|
||||
}
|
||||
return this.commandAttachFile();
|
||||
}}));
|
||||
|
||||
menu.append(new MenuItem({label: _('Set or clear alarm'), click: async () => {
|
||||
this.props.dispatch({
|
||||
type: 'WINDOW_COMMAND',
|
||||
name: 'editAlarm',
|
||||
noteId: noteId,
|
||||
});
|
||||
menu.append(new MenuItem({label: _('Set alarm'), click: async () => {
|
||||
return this.commandSetAlarm();
|
||||
}}));
|
||||
|
||||
menu.popup(bridge().window());
|
||||
}
|
||||
|
||||
// shouldComponentUpdate(nextProps, nextState) {
|
||||
// //console.info('NEXT PROPS', JSON.stringify(nextProps));
|
||||
// console.info('NEXT STATE ====================');
|
||||
// for (var n in nextProps) {
|
||||
// if (!nextProps.hasOwnProperty(n)) continue;
|
||||
// console.info(n + ' = ' + (nextProps[n] === this.props[n]));
|
||||
// }
|
||||
// return true;
|
||||
// }
|
||||
|
||||
render() {
|
||||
const style = this.props.style;
|
||||
const note = this.state.note;
|
||||
@ -385,7 +424,7 @@ class NoteTextComponent extends React.Component {
|
||||
height: 30,
|
||||
boxSizing: 'border-box',
|
||||
marginTop: 10,
|
||||
marginBottom: 10,
|
||||
marginBottom: 0,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
};
|
||||
@ -401,7 +440,11 @@ class NoteTextComponent extends React.Component {
|
||||
marginRight: rootStyle.paddingLeft,
|
||||
};
|
||||
|
||||
const bottomRowHeight = rootStyle.height - titleBarStyle.height - titleBarStyle.marginBottom - titleBarStyle.marginTop;
|
||||
const toolbarStyle = {
|
||||
marginBottom: 10,
|
||||
};
|
||||
|
||||
const bottomRowHeight = rootStyle.height - titleBarStyle.height - titleBarStyle.marginBottom - titleBarStyle.marginTop - theme.toolbarHeight - toolbarStyle.marginBottom;
|
||||
|
||||
const viewerStyle = {
|
||||
width: Math.floor(innerWidth / 2),
|
||||
@ -455,6 +498,28 @@ class NoteTextComponent extends React.Component {
|
||||
this.webview_.send('setHtml', html);
|
||||
}
|
||||
|
||||
const toolbarItems = [];
|
||||
|
||||
toolbarItems.push({
|
||||
title: _('Attach file'),
|
||||
iconName: 'fa-paperclip',
|
||||
onClick: () => { return this.commandAttachFile(); },
|
||||
});
|
||||
|
||||
if (note.is_todo) {
|
||||
toolbarItems.push({
|
||||
title: Note.needAlarm(note) ? time.formatMsToLocal(note.todo_due) : _('Set alarm'),
|
||||
iconName: 'fa-clock-o',
|
||||
enabled: !note.todo_completed,
|
||||
onClick: () => { return this.commandSetAlarm(); },
|
||||
});
|
||||
}
|
||||
|
||||
const toolbar = <Toolbar
|
||||
style={toolbarStyle}
|
||||
items={toolbarItems}
|
||||
/>
|
||||
|
||||
const titleEditor = <input
|
||||
type="text"
|
||||
style={titleEditorStyle}
|
||||
@ -509,6 +574,7 @@ class NoteTextComponent extends React.Component {
|
||||
{ titleEditor }
|
||||
{ titleBarMenuButton }
|
||||
</div>
|
||||
{ toolbar }
|
||||
{ editor }
|
||||
{ viewer }
|
||||
</div>
|
||||
|
@ -10,19 +10,19 @@ class PromptDialog extends React.Component {
|
||||
componentWillMount() {
|
||||
this.setState({
|
||||
visible: false,
|
||||
answer: this.props.value ? this.props.value : '',
|
||||
answer: this.props.defaultValue ? this.props.defaultValue : '',
|
||||
});
|
||||
this.focusInput_ = true;
|
||||
}
|
||||
|
||||
componentWillReceiveProps(newProps) {
|
||||
if ('visible' in newProps) {
|
||||
if ('visible' in newProps && newProps.visible !== this.props.visible) {
|
||||
this.setState({ visible: newProps.visible });
|
||||
if (newProps.visible) this.focusInput_ = true;
|
||||
}
|
||||
|
||||
if ('value' in newProps) {
|
||||
this.setState({ answer: newProps.value });
|
||||
if ('defaultValue' in newProps && newProps.defaultValue !== this.props.defaultValue) {
|
||||
this.setState({ answer: newProps.defaultValue });
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,38 +31,43 @@ class PromptDialog extends React.Component {
|
||||
this.focusInput_ = false;
|
||||
}
|
||||
|
||||
render() {
|
||||
const style = this.props.style;
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const buttonTypes = this.props.buttons ? this.props.buttons : ['ok', 'cancel'];
|
||||
styles(themeId, width, height, visible) {
|
||||
const styleKey = themeId + '_' + width + '_' + height + '_' + visible;
|
||||
if (styleKey === this.styleKey_) return this.styles_;
|
||||
|
||||
const modalLayerStyle = {
|
||||
const theme = themeStyle(themeId);
|
||||
|
||||
this.styleKey_ = styleKey;
|
||||
|
||||
this.styles_ = {};
|
||||
|
||||
this.styles_.modalLayer = {
|
||||
zIndex: 9999,
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: style.width,
|
||||
height: style.height,
|
||||
width: width,
|
||||
height: height,
|
||||
backgroundColor: 'rgba(0,0,0,0.6)',
|
||||
display: this.state.visible ? 'flex' : 'none',
|
||||
display: visible ? 'flex' : 'none',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
};
|
||||
|
||||
const promptDialogStyle = {
|
||||
this.styles_.promptDialog = {
|
||||
backgroundColor: 'white',
|
||||
padding: 16,
|
||||
display: 'inline-block',
|
||||
boxShadow: '6px 6px 20px rgba(0,0,0,0.5)',
|
||||
};
|
||||
|
||||
const buttonStyle = {
|
||||
this.styles_.button = {
|
||||
minWidth: theme.buttonMinWidth,
|
||||
minHeight: theme.buttonMinHeight,
|
||||
marginLeft: 5,
|
||||
};
|
||||
|
||||
const labelStyle = {
|
||||
this.styles_.label = {
|
||||
marginRight: 5,
|
||||
fontSize: theme.fontSize,
|
||||
color: theme.color,
|
||||
@ -70,15 +75,43 @@ class PromptDialog extends React.Component {
|
||||
verticalAlign: 'top',
|
||||
};
|
||||
|
||||
const inputStyle = {
|
||||
width: 0.5 * style.width,
|
||||
this.styles_.input = {
|
||||
width: 0.5 * width,
|
||||
maxWidth: 400,
|
||||
};
|
||||
|
||||
const descStyle = Object.assign({}, theme.textStyle, {
|
||||
this.styles_.desc = Object.assign({}, theme.textStyle, {
|
||||
marginTop: 10,
|
||||
});
|
||||
|
||||
return this.styles_;
|
||||
}
|
||||
|
||||
// shouldComponentUpdate(nextProps, nextState) {
|
||||
// console.info(JSON.stringify(nextProps)+JSON.stringify(nextState));
|
||||
|
||||
// console.info('NEXT PROPS ====================');
|
||||
// for (var n in nextProps) {
|
||||
// if (!nextProps.hasOwnProperty(n)) continue;
|
||||
// console.info(n + ' = ' + (nextProps[n] === this.props[n]));
|
||||
// }
|
||||
|
||||
// console.info('NEXT STATE ====================');
|
||||
// for (var n in nextState) {
|
||||
// if (!nextState.hasOwnProperty(n)) continue;
|
||||
// console.info(n + ' = ' + (nextState[n] === this.state[n]));
|
||||
// }
|
||||
|
||||
// return true;
|
||||
// }
|
||||
|
||||
render() {
|
||||
const style = this.props.style;
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const buttonTypes = this.props.buttons ? this.props.buttons : ['ok', 'cancel'];
|
||||
|
||||
const styles = this.styles(this.props.theme, style.width, style.height, this.state.visible);
|
||||
|
||||
const onClose = (accept, buttonType) => {
|
||||
if (this.props.onClose) this.props.onClose(accept ? this.state.answer : null, buttonType);
|
||||
this.setState({ visible: false, answer: '' });
|
||||
@ -100,7 +133,7 @@ class PromptDialog extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
const descComp = this.props.description ? <div style={descStyle}>{this.props.description}</div> : null;
|
||||
const descComp = this.props.description ? <div style={styles.desc}>{this.props.description}</div> : null;
|
||||
|
||||
let inputComp = null;
|
||||
|
||||
@ -113,7 +146,7 @@ class PromptDialog extends React.Component {
|
||||
/>
|
||||
} else {
|
||||
inputComp = <input
|
||||
style={inputStyle}
|
||||
style={styles.input}
|
||||
ref={input => this.answerInput_ = input}
|
||||
value={this.state.answer}
|
||||
type="text"
|
||||
@ -123,14 +156,14 @@ class PromptDialog extends React.Component {
|
||||
}
|
||||
|
||||
const buttonComps = [];
|
||||
if (buttonTypes.indexOf('ok') >= 0) buttonComps.push(<button key="ok" style={buttonStyle} onClick={() => onClose(true, 'ok')}>{_('OK')}</button>);
|
||||
if (buttonTypes.indexOf('cancel') >= 0) buttonComps.push(<button key="cancel" style={buttonStyle} onClick={() => onClose(false, 'cancel')}>{_('Cancel')}</button>);
|
||||
if (buttonTypes.indexOf('clear') >= 0) buttonComps.push(<button key="clear" style={buttonStyle} onClick={() => onClose(false, 'clear')}>{_('Clear')}</button>);
|
||||
if (buttonTypes.indexOf('ok') >= 0) buttonComps.push(<button key="ok" style={styles.button} onClick={() => onClose(true, 'ok')}>{_('OK')}</button>);
|
||||
if (buttonTypes.indexOf('cancel') >= 0) buttonComps.push(<button key="cancel" style={styles.button} onClick={() => onClose(false, 'cancel')}>{_('Cancel')}</button>);
|
||||
if (buttonTypes.indexOf('clear') >= 0) buttonComps.push(<button key="clear" style={styles.button} onClick={() => onClose(false, 'clear')}>{_('Clear')}</button>);
|
||||
|
||||
return (
|
||||
<div style={modalLayerStyle}>
|
||||
<div style={promptDialogStyle}>
|
||||
<label style={labelStyle}>{this.props.label ? this.props.label : ''}</label>
|
||||
<div style={styles.modalLayer}>
|
||||
<div style={styles.promptDialog}>
|
||||
<label style={styles.label}>{this.props.label ? this.props.label : ''}</label>
|
||||
<div style={{display: 'inline-block'}}>
|
||||
{inputComp}
|
||||
{descComp}
|
||||
|
58
ElectronClient/app/gui/Toolbar.jsx
Normal file
58
ElectronClient/app/gui/Toolbar.jsx
Normal file
@ -0,0 +1,58 @@
|
||||
const React = require('react');
|
||||
const { connect } = require('react-redux');
|
||||
const { reg } = require('lib/registry.js');
|
||||
const { themeStyle } = require('../theme.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const ToolbarButton = require('./ToolbarButton.min.js');
|
||||
|
||||
class ToolbarComponent extends React.Component {
|
||||
|
||||
render() {
|
||||
const style = this.props.style;
|
||||
const theme = themeStyle(this.props.theme);
|
||||
style.height = theme.toolbarHeight;
|
||||
style.display = 'flex';
|
||||
style.flexDirection = 'row';
|
||||
style.borderBottom = '1px solid ' + theme.dividerColor;
|
||||
style.boxSizing = 'border-box';
|
||||
|
||||
const itemComps = [];
|
||||
|
||||
if (this.props.items) {
|
||||
for (let i = 0; i < this.props.items.length; i++) {
|
||||
const o = this.props.items[i];
|
||||
let key = o.iconName ? o.iconName : '';
|
||||
key += o.title ? o.title : '';
|
||||
const itemType = !('type' in o) ? 'button' : o.type;
|
||||
|
||||
const props = Object.assign({
|
||||
key: key,
|
||||
theme: this.props.theme,
|
||||
}, o);
|
||||
|
||||
if (itemType === 'button') {
|
||||
itemComps.push(<ToolbarButton
|
||||
{...props}
|
||||
/>);
|
||||
} else if (itemType === 'text') {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="editor-toolbar" style={style}>
|
||||
{ itemComps }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return { theme: state.settings.theme };
|
||||
};
|
||||
|
||||
const Toolbar = connect(mapStateToProps)(ToolbarComponent);
|
||||
|
||||
module.exports = Toolbar;
|
59
ElectronClient/app/gui/ToolbarButton.jsx
Normal file
59
ElectronClient/app/gui/ToolbarButton.jsx
Normal file
@ -0,0 +1,59 @@
|
||||
const React = require('react');
|
||||
const { connect } = require('react-redux');
|
||||
const { themeStyle } = require('../theme.js');
|
||||
|
||||
class ToolbarButton extends React.Component {
|
||||
|
||||
render() {
|
||||
//const style = this.props.style;
|
||||
const theme = themeStyle(this.props.theme);
|
||||
|
||||
const style = {
|
||||
height: theme.toolbarHeight,
|
||||
minWidth: theme.toolbarHeight,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
paddingLeft: theme.headerButtonHPadding,
|
||||
paddingRight: theme.headerButtonHPadding,
|
||||
color: theme.color,
|
||||
textDecoration: 'none',
|
||||
fontFamily: theme.fontFamily,
|
||||
fontSize: theme.fontSize,
|
||||
boxSizing: 'border-box',
|
||||
cursor: 'default',
|
||||
justifyContent: 'center',
|
||||
};
|
||||
|
||||
let icon = null;
|
||||
if (this.props.iconName) {
|
||||
const iconStyle = {
|
||||
fontSize: Math.round(theme.fontSize * 1.4),
|
||||
color: theme.color
|
||||
};
|
||||
if (this.props.title) iconStyle.marginRight = 5;
|
||||
icon = <i style={iconStyle} className={"fa " + this.props.iconName}></i>
|
||||
}
|
||||
|
||||
const isEnabled = (!('enabled' in this.props) || this.props.enabled === true);
|
||||
let classes = ['button'];
|
||||
if (!isEnabled) classes.push('disabled');
|
||||
|
||||
const finalStyle = Object.assign({}, style, {
|
||||
opacity: isEnabled ? 1 : 0.4,
|
||||
});
|
||||
|
||||
return (
|
||||
<a
|
||||
className={classes.join(' ')}
|
||||
style={finalStyle}
|
||||
href="#"
|
||||
onClick={() => { if (isEnabled && this.props.onClick) this.props.onClick() }}
|
||||
>
|
||||
{icon}{this.props.title ? this.props.title : ''}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = ToolbarButton;
|
@ -31,18 +31,21 @@ body, textarea {
|
||||
background-color: #564B6C;
|
||||
}
|
||||
|
||||
.editor-toolbar .button:not(.disabled):hover,
|
||||
.header .button:not(.disabled):hover {
|
||||
background-color: rgba(0,160,255,0.1);
|
||||
border: 1px solid rgba(0,160,255,0.5);
|
||||
box-sizing: 'border-box';
|
||||
}
|
||||
|
||||
.editor-toolbar .button:not(.disabled):active,
|
||||
.header .button:not(.disabled):active {
|
||||
background-color: rgba(0,160,255,0.2);
|
||||
border: 1px solid rgba(0,160,255,0.7);
|
||||
box-sizing: 'border-box';
|
||||
}
|
||||
|
||||
.editor-toolbar .button,
|
||||
.header .button {
|
||||
border: 1px solid rgba(0,160,255,0);
|
||||
}
|
||||
|
@ -28,6 +28,8 @@ const globalStyle = {
|
||||
headerHeight: 35,
|
||||
headerButtonHPadding: 6,
|
||||
|
||||
toolbarHeight: 35,
|
||||
|
||||
raisedBackgroundColor: "#0080EF",
|
||||
raisedColor: "#003363",
|
||||
raisedHighlightedColor: "#ffffff",
|
||||
|
Loading…
Reference in New Issue
Block a user