From 229dd7a6dd940feee3e07890d99147eb28c25913 Mon Sep 17 00:00:00 2001 From: Caleb John Date: Sun, 24 Feb 2019 03:55:19 -0700 Subject: [PATCH] New tag adding dialogue (#1206) * Update tag adding dialogue - use datalist to autocomplete tags - display tags with TagList * Move the tagItem highlight color to theme.js --- ElectronClient/app/gui/MainScreen.jsx | 8 +-- ElectronClient/app/gui/PromptDialog.jsx | 69 +++++++++++++++++++++++-- ElectronClient/app/gui/TagItem.jsx | 60 +++++++++++++++++++-- ElectronClient/app/gui/TagList.jsx | 5 +- ElectronClient/app/style.css | 8 +++ ElectronClient/app/theme.js | 3 ++ 6 files changed, 142 insertions(+), 11 deletions(-) diff --git a/ElectronClient/app/gui/MainScreen.jsx b/ElectronClient/app/gui/MainScreen.jsx index e485e402ea..1ab6b0fbd8 100644 --- a/ElectronClient/app/gui/MainScreen.jsx +++ b/ElectronClient/app/gui/MainScreen.jsx @@ -132,15 +132,17 @@ class MainScreenComponent extends React.Component { } else if (command.name === 'setTags') { const tags = await Tag.tagsByNoteId(command.noteId); const tagTitles = tags.map((a) => { return a.title }).sort(); + const allTags = await Tag.allWithNotes(); this.setState({ promptOptions: { label: _('Add or remove tags:'), - description: _('Separate each tag by a comma.'), - value: tagTitles.join(', '), + inputType: 'tags', + value: tagTitles, + autocomplete: allTags, onClose: async (answer) => { if (answer !== null) { - const tagTitles = answer.split(',').map((a) => { return a.trim() }); + const tagTitles = answer.map((a) => { return a.trim() }); await Tag.setNoteTagsByTitles(command.noteId, tagTitles); } this.setState({ promptOptions: null }); diff --git a/ElectronClient/app/gui/PromptDialog.jsx b/ElectronClient/app/gui/PromptDialog.jsx index aa887eea66..793afbbeb0 100644 --- a/ElectronClient/app/gui/PromptDialog.jsx +++ b/ElectronClient/app/gui/PromptDialog.jsx @@ -5,13 +5,21 @@ const moment = require('moment'); const { themeStyle } = require('../theme.js'); const { time } = require('lib/time-utils.js'); const Datetime = require('react-datetime'); +const TagList = require('./TagList.min.js'); +const Tag = require('lib/models/Tag.js'); class PromptDialog extends React.Component { componentWillMount() { + let answer = '' + if (this.props.inputType !== 'tags' && this.props.defaultValue) { + answer = this.props.defaultValue; + } + this.setState({ visible: false, - answer: this.props.defaultValue ? this.props.defaultValue : '', + answer: answer, + tags: this.props.inputType === 'tags' ? this.props.defaultValue : null, }); this.focusInput_ = true; } @@ -23,7 +31,11 @@ class PromptDialog extends React.Component { } if ('defaultValue' in newProps && newProps.defaultValue !== this.props.defaultValue) { - this.setState({ answer: newProps.defaultValue }); + if ('inputType' in newProps && newProps.inputType === 'tags') { + this.setState({ answer: '', tags: newProps.defaultValue }); + } else { + this.setState({ answer: newProps.defaultValue }); + } } } @@ -62,6 +74,7 @@ class PromptDialog extends React.Component { backgroundColor: theme.backgroundColor, padding: 16, display: 'inline-block', + maxWidth: width * 0.5, boxShadow: '6px 6px 20px rgba(0,0,0,0.5)', }; @@ -92,6 +105,11 @@ class PromptDialog extends React.Component { borderColor: theme.dividerColor, }; + this.styles_.tagList = { + marginBottom: 10, + marginTop: 10, + }; + this.styles_.desc = Object.assign({}, theme.textStyle, { marginTop: 10, }); @@ -113,6 +131,9 @@ class PromptDialog extends React.Component { // outputAnswer = anythingToDate(outputAnswer); outputAnswer = time.anythingToDateTime(outputAnswer); } + else if (this.props.inputType === 'tags') { + outputAnswer = this.state.tags; + } this.props.onClose(accept ? outputAnswer : null, buttonType); } this.setState({ visible: false, answer: '' }); @@ -137,15 +158,35 @@ class PromptDialog extends React.Component { const onKeyDown = (event) => { if (event.key === 'Enter') { - onClose(true); + if (this.state.answer.trim() !== '') { + let newTags = this.state.tags; + if (newTags.indexOf(this.state.answer) === -1) { + newTags.push(this.state.answer); + } + this.setState({ + tags: newTags, + answer: '' + }); + } } else if (event.key === 'Escape') { onClose(false); } } + const onDeleteTag = (tag) => { + let newTags = this.state.tags; + var index = newTags.indexOf(tag); + if (index !== -1) newTags.splice(index, 1); + this.setState({ + tags: newTags, + }); + } + const descComp = this.props.description ?
{this.props.description}
: null; let inputComp = null; + let dataList = null; + let tagList = null; if (this.props.inputType === 'datetime') { inputComp = this.answerInput_ = input} value={this.state.answer} type="text" + list={this.props.inputType === "tags" ? "tags" : null} onChange={(event) => onChange(event)} onKeyDown={(event) => onKeyDown(event)} /> } + if (this.props.inputType === 'tags') { + tagList = { + return {title: a, id: a} + })} + />; + + dataList = + {this.props.autocomplete.map((a) => { + if (this.state.tags.indexOf(a.title) === -1) { + return + } + const buttonComps = []; if (buttonTypes.indexOf('ok') >= 0) buttonComps.push(); if (buttonTypes.indexOf('cancel') >= 0) buttonComps.push(); @@ -177,7 +238,9 @@ class PromptDialog extends React.Component {
{inputComp} + {dataList} {descComp} + {tagList}
{buttonComps} diff --git a/ElectronClient/app/gui/TagItem.jsx b/ElectronClient/app/gui/TagItem.jsx index a321cdef5b..7a88b96d5b 100644 --- a/ElectronClient/app/gui/TagItem.jsx +++ b/ElectronClient/app/gui/TagItem.jsx @@ -1,14 +1,68 @@ const React = require('react'); const { connect } = require('react-redux'); const { themeStyle } = require('../theme.js'); +const { _ } = require('lib/locale.js'); class TagItemComponent extends React.Component { + constructor(props) { + super(props); + + this.clickAway = this.clickAway.bind(this); + } + + componentWillMount() { + this.setState({ + title: this.props.title, + toDelete: false, + }); + } + + clickAway(event) { + if (this.span_ && !this.span_.contains(event.target)) { + this.setState({ + title: this.props.title, + toDelete: false, + }); + } + } + + componentDidMount() { + document.addEventListener('mousedown', this.clickAway); + } + + componentWillUnmount() { + document.removeEventListener('mousedown', this.clickAway); + } + render() { const theme = themeStyle(this.props.theme); - const style = Object.assign({}, theme.tagStyle); - const title = this.props.title; + let style = Object.assign({textAlign: 'center'}, theme.tagStyle); - return {title}; + if (this.state.toDelete) { + style = Object.assign({}, style, {backgroundColor: theme.removeColor}); + } + + const onClick = (event) => { + if (this.state.toDelete) { + this.props.onDelete(this.props.title); + } + else if (this.props.onDelete) { + this.setState({ + title: _("Remove?"), + toDelete: true, + }); + } + } + + return ( + this.span_ = span} + onClick={onClick} + style={style} + value={this.state.title}> + {this.state.title} + + ); } } diff --git a/ElectronClient/app/gui/TagList.jsx b/ElectronClient/app/gui/TagList.jsx index 6475c2c486..b5a5d3a75c 100644 --- a/ElectronClient/app/gui/TagList.jsx +++ b/ElectronClient/app/gui/TagList.jsx @@ -10,7 +10,7 @@ class TagListComponent extends React.Component { const tags = this.props.items; style.display = 'flex'; - style.flexDirection = 'row'; + style.flexWrap = 'wrap'; style.borderBottom = '1px solid ' + theme.dividerColor; style.boxSizing = 'border-box'; style.fontSize = theme.fontSize; @@ -23,7 +23,8 @@ class TagListComponent extends React.Component { for (let i = 0; i < tags.length; i++) { const props = { title: tags[i].title, - key: tags[i].id + key: tags[i].id, + onDelete: this.props.onDeleteItem, }; tagItems.push(); } diff --git a/ElectronClient/app/style.css b/ElectronClient/app/style.css index e40e93305a..3f38f25941 100644 --- a/ElectronClient/app/style.css +++ b/ElectronClient/app/style.css @@ -59,6 +59,14 @@ table td, table th { background-color: rgba(0,160,255,0.1) !important; } +input::-webkit-calendar-picker-indicator { + background-color: rgba(0, 0, 0, 0); +} + +input::-webkit-calendar-picker-indicator:hover { + background-color: rgba(100, 100, 100, 0.2); +} + /*.side-bar .list-item:hover, .side-bar .synchronize-button:hover { background-color: #01427B; diff --git a/ElectronClient/app/theme.js b/ElectronClient/app/theme.js index 346ff39337..b852d630e6 100644 --- a/ElectronClient/app/theme.js +++ b/ElectronClient/app/theme.js @@ -70,6 +70,7 @@ const lightStyle = { dividerColor: "#dddddd", selectedColor: '#e5e5e5', urlColor: '#155BDA', + removeColor: '#B01C2E', backgroundColor2: "#162B3D", color2: "#ffffff", @@ -106,6 +107,7 @@ const darkStyle = { dividerColor: '#555555', selectedColor: '#333333', urlColor: '#4E87EE', + removeColor: '#B01C2E', backgroundColor2: "#181A1D", color2: "#ffffff", @@ -142,6 +144,7 @@ function addExtraStyles(style) { paddingRight: style.tagItemPadding * 2, paddingLeft: style.tagItemPadding * 2, backgroundColor: style.raisedBackgroundColor, + borderRadius: '10%', color: style.raisedColor, };