const React = require('react'); const { connect } = require('react-redux'); const { themeStyle } = require('lib/theme'); const { _ } = require('lib/locale.js'); const { bridge } = require('electron').remote.require('./bridge'); const CommandService = require('lib/services/CommandService').default; const commands = [ require('./commands/focusSearch'), ]; class HeaderComponent extends React.Component { constructor() { super(); this.state = { searchQuery: '', showSearchUsageLink: false, showButtonLabels: true, }; for (const command of commands) { CommandService.instance().registerRuntime(command.declaration.name, command.runtime(this)); } this.scheduleSearchChangeEventIid_ = null; this.searchOnQuery_ = null; this.searchElement_ = null; const triggerOnQuery = query => { clearTimeout(this.scheduleSearchChangeEventIid_); if (this.searchOnQuery_) this.searchOnQuery_(query); this.scheduleSearchChangeEventIid_ = null; }; this.search_onChange = event => { this.setState({ searchQuery: event.target.value }); if (this.scheduleSearchChangeEventIid_) clearTimeout(this.scheduleSearchChangeEventIid_); this.scheduleSearchChangeEventIid_ = setTimeout(() => { triggerOnQuery(this.state.searchQuery); }, 500); }; this.search_onClear = () => { this.resetSearch(); if (this.searchElement_) this.searchElement_.focus(); }; this.search_onFocus = () => { if (this.hideSearchUsageLinkIID_) { clearTimeout(this.hideSearchUsageLinkIID_); this.hideSearchUsageLinkIID_ = null; } this.setState({ showSearchUsageLink: true }); }; this.search_onBlur = () => { if (this.hideSearchUsageLinkIID_) return; this.hideSearchUsageLinkIID_ = setTimeout(() => { this.setState({ showSearchUsageLink: false }); }, 5000); }; this.search_keyDown = event => { if (event.keyCode === 27) { // ESCAPE this.resetSearch(); } }; this.resetSearch = () => { this.setState({ searchQuery: '' }); triggerOnQuery(''); }; this.searchUsageLink_click = () => { bridge().openExternal('https://joplinapp.org/#searching'); }; } componentDidUpdate(prevProps) { if (prevProps.notesParentType !== this.props.notesParentType && this.props.notesParentType !== 'Search' && this.state.searchQuery) { this.resetSearch(); } if (this.props.zoomFactor !== prevProps.zoomFactor || this.props.size !== prevProps.size) { this.determineButtonLabelState(); } } componentDidMount() { this.determineButtonLabelState(); } componentWillUnmount() { if (this.hideSearchUsageLinkIID_) { clearTimeout(this.hideSearchUsageLinkIID_); this.hideSearchUsageLinkIID_ = null; } for (const command of commands) { CommandService.instance().unregisterRuntime(command.declaration.name); } } determineButtonLabelState() { const mediaQuery = window.matchMedia(`(max-width: ${780 * this.props.zoomFactor}px)`); const showButtonLabels = !mediaQuery.matches; if (this.state.showButtonLabels !== showButtonLabels) { this.setState({ showButtonLabels: !mediaQuery.matches, }); } } back_click() { this.props.dispatch({ type: 'NAV_BACK' }); } makeButton(key, style, options) { // TODO: "tab" type is not finished if (options.type === 'tab') { const buttons = []; for (let i = 0; i < options.items.length; i++) { const item = options.items[i]; buttons.push(this.makeButton(key + item.title, style, Object.assign({}, options, { title: item.title, type: 'button', }))); } return {buttons}; } const theme = themeStyle(this.props.theme); let icon = null; if (options.iconName) { const iconStyle = { fontSize: Math.round(style.fontSize * 1.1), color: theme.iconColor, }; if (options.title) iconStyle.marginRight = 5; if ('undefined' != typeof options.iconRotation) { iconStyle.transition = 'transform 0.15s ease-in-out'; iconStyle.transform = `rotate(${options.iconRotation}deg)`; } icon = ; } const isEnabled = !('enabled' in options) || options.enabled; const classes = ['button']; if (!isEnabled) classes.push('disabled'); const finalStyle = Object.assign({}, style, { opacity: isEnabled ? 1 : 0.4, }); const title = options.title ? options.title : ''; if (options.type === 'checkbox' && options.checked) { finalStyle.backgroundColor = theme.selectedColor; finalStyle.borderWidth = 1; finalStyle.borderTopColor = theme.selectedDividerColor; finalStyle.borderLeftColor = theme.selectedDividerColor; finalStyle.borderTopStyle = 'solid'; finalStyle.borderLeftStyle = 'solid'; finalStyle.paddingLeft++; finalStyle.paddingTop++; finalStyle.paddingBottom--; finalStyle.paddingRight--; finalStyle.boxSizing = 'border-box'; } return ( { if (isEnabled) options.onClick(); }} > {icon} {title} ); } makeSearch(key, style, options, state) { const theme = themeStyle(this.props.theme); const inputStyle = { display: 'flex', flex: 1, marginLeft: 10, paddingLeft: 6, paddingRight: 6, paddingTop: 1, // vertical alignment with buttons paddingBottom: 0, // vertical alignment with buttons height: style.fontSize * 2, maxWidth: 300, color: style.color, fontSize: style.fontSize, fontFamily: style.fontFamily, backgroundColor: style.searchColor, border: '1px solid', borderColor: style.dividerColor, }; const searchButton = { paddingLeft: 4, paddingRight: 4, paddingTop: 2, paddingBottom: 2, textDecoration: 'none', }; const iconStyle = { display: 'flex', fontSize: Math.round(style.fontSize) * 1.2, color: style.color, }; const containerStyle = { display: 'flex', flexDirection: 'row', flexGrow: 1, alignItems: 'center', }; const iconName = state.searchQuery ? 'fa-times' : 'fa-search'; const icon = ; if (options.onQuery) this.searchOnQuery_ = options.onQuery; const usageLink = !this.state.showSearchUsageLink ? null : ( {_('Usage')} ); return (