diff --git a/src/context/ContextGetHelp.jsx b/src/context/ContextGetHelp.jsx index 2e3d473011..6b8abb31d7 100644 --- a/src/context/ContextGetHelp.jsx +++ b/src/context/ContextGetHelp.jsx @@ -108,85 +108,109 @@ class ContextGetHelp extends React.Component { } } - renderOptions() { - // if getHelp is not selected, don't show the additional options - if (this.state.selectedIndex === -1) return null; + renderOptionsWithGetHelp = () => { + // Get name of the shortcut for the getHelp text + const initiatingTrigger = this.props.shortcut.getDisplayText(); - return ( - - {this.state.getHelpOptions.map((option, index) => { - // the parent 'get help' option is not included in the getHelpOptions array - // but it is included as a selectedIndex, so there is an off by one that needs - // to be calculated, hence the updatedIndex + 1 from the index of the getHelpOptions - const updatedIndex = index + 1; - return ( -
  • { this.setSelectedIndex(updatedIndex); }} - > - {option.text} -
  • - ); - })} -
    - ); - } + // Determine if we should display anything other than the getHelp option + const isGetHelpClosed = this.state.selectedIndex === -1; + // For any informational flags, define them here and chain them together into a single variable + // This variable will determine if we display a horizontal bar, separating information from actions + const isMissingParent = this.props.shortcut.isMissingParent; + const isInformationAvailable = isMissingParent || false; + + // Create our icon class to signal the expanding/collapsing getHelp option, based on open/closedness + let iconClass = 'fa fa-angle-'; + isGetHelpClosed ? iconClass += 'down' : iconClass += 'up'; - renderIsCompleteMessage() { - const initiatingTrigger = this.props.shortcut.getDisplayText(); return ( - + + + {!isGetHelpClosed && this.state.getHelpOptions.map((option, index) => { + // the parent 'get help' option is not included in the getHelpOptions array + // but it is included as a selectedIndex, so there is an off by one that needs + // to be calculated, hence the updatedIndex + 1 from the index of the getHelpOptions + const updatedIndex = index + 1; + return ( +
  • { this.setSelectedIndex(updatedIndex); }} + > + {option.text} +
  • + ); + })} +
    + {(!isGetHelpClosed && isInformationAvailable) && this.renderHorizontalLine()} + {(!isGetHelpClosed && isMissingParent) && this.renderIsMissingParent()} + ); } - renderIsMissingParent() { + renderIsCompleteMessage() { const initiatingTrigger = this.props.shortcut.getDisplayText(); + const infoIconiconClass = "fa fa-info-circle"; return ( - +
  • + + + {initiatingTrigger} is already complete + +
  • ); } + renderIsMissingParent() { + const shortcut = this.props.shortcut; + const initiatingTrigger = shortcut.getDisplayText(); + const potentialParentText = shortcut.potentialParents.map(parentID => this.props.shortcutManager.getShortcutLabel(parentID)).join(", or"); + const infoIconiconClass = "fa fa-info-circle"; + return ( +
  • + + + {initiatingTrigger} needs more context, try mentioning {potentialParentText} beforehand + +
  • + ); + } + renderHorizontalLine() { + return ( +
    + ); + } render() { - // If the shortcut we're responsible for is missing a parent, display a message to the user to avoid confusion - if (!this.props.shortcut.hasParentContext() && this.props.shortcut.hasChildren()) return this.renderIsMissingParent(); - // If the shortcut we're responsible for is complete, display a message to the user to avoid confusion - if (this.props.shortcut.isComplete) return this.renderIsCompleteMessage(); - // Else we should display all our getHelp message - const initiatingTrigger = this.props.shortcut.getDisplayText(); - let iconClass = 'fa fa-angle-'; - this.state.selectedIndex === -1 ? iconClass += 'down' : iconClass += 'up'; + // Decide the list content and render whatever it is in the UL element + let listContent = null; + if (this.props.shortcut.isMissingParent && this.props.shortcut.hasChildren()) { + // If the shortcut we're responsible for is missing a parent but is already expanded, display a message to the user to avoid confusion + listContent = this.renderIsMissingParent(); + } else if (this.props.shortcut.isComplete) { + // Else, if the shortcut we're responsible for is complete, display a message to the user to avoid confusion + listContent = this.renderIsCompleteMessage(); + } else { + // Else we should display all our getHelp message + listContent = this.renderOptionsWithGetHelp(); + } + return ( ); } diff --git a/src/context/ContextGetHelp.scss b/src/context/ContextGetHelp.scss index 76f51120e9..da074eee13 100644 --- a/src/context/ContextGetHelp.scss +++ b/src/context/ContextGetHelp.scss @@ -28,17 +28,19 @@ ul.context-get-help { margin-bottom: 5px; .context-get-help-text { - span { + span.fa-angle-up, span.fa-angle-down { + padding: 0; padding-left: 15px; } } + .context-information-text { + span.fa-info-circle { + padding: 0 5px 0 0; + } + } } .context-get-help-options { - li:first-child { - border-top: 1px solid $line-gray; - } - li:last-child { margin-bottom: 5px; } diff --git a/src/notes/FluxNotesEditor.jsx b/src/notes/FluxNotesEditor.jsx index 7eb07be909..29dcb8589d 100644 --- a/src/notes/FluxNotesEditor.jsx +++ b/src/notes/FluxNotesEditor.jsx @@ -1787,6 +1787,7 @@ class FluxNotesEditor extends React.Component { onSelected={this.onCompletionComponentValueSelection} closePortal={this.closeCompletionPortal} shortcut={this.state.completionComponentShortcut} + shortcutManager={this.props.shortcutManager} state={this.state.state} insertShortcut={this.insertShortcut} /> diff --git a/src/notes/SuggestionPortalPlaceholderSearchIndex.jsx b/src/notes/SuggestionPortalPlaceholderSearchIndex.jsx index f7e27b432f..24d84e2173 100644 --- a/src/notes/SuggestionPortalPlaceholderSearchIndex.jsx +++ b/src/notes/SuggestionPortalPlaceholderSearchIndex.jsx @@ -15,7 +15,7 @@ class SuggestionPortalPlaceholderSearchIndex extends SuggestionPortalSearchIndex const relevantShortcuts = []; placeholders.forEach((placeholder) => { - const triggers = this.shortcutManager.getTriggersForShortcut(placeholder.id); + const triggers = this.shortcutManager.getTriggersWithoutLabelForShortcut(placeholder.id); triggers.forEach((trigger) => { const triggerNoPrefix = trigger.name.substring(1); relevantShortcuts.push({ diff --git a/src/notes/SuggestionPortalShortcutSearchIndex.jsx b/src/notes/SuggestionPortalShortcutSearchIndex.jsx index 19e185b63f..e8943e5b35 100644 --- a/src/notes/SuggestionPortalShortcutSearchIndex.jsx +++ b/src/notes/SuggestionPortalShortcutSearchIndex.jsx @@ -20,7 +20,7 @@ class SuggestionPortalShortcutSearchIndex extends SuggestionPortalSearchIndex { allShortcutObjs.forEach((shortcutObj) => { const shortcutId = shortcutObj.id; const shortcutMetadata = this.shortcutManager.getShortcutMetadata(shortcutId); - const triggers = this.shortcutManager.getTriggersForShortcut(shortcutId); + const triggers = this.shortcutManager.getTriggersWithoutLabelForShortcut(shortcutId); // Scores get sorted from smallest to greatest // ActiveContexts is sorted from most recent to least recent // We want shortcuts for the most recent shortcuts to have the smallest bonus score, so as to appear earlier diff --git a/src/shortcuts/CreatorBase.jsx b/src/shortcuts/CreatorBase.jsx index a366e745e5..9a7fe39f7e 100644 --- a/src/shortcuts/CreatorBase.jsx +++ b/src/shortcuts/CreatorBase.jsx @@ -1,4 +1,5 @@ import EntryShortcut from './EntryShortcut'; +import _ from 'lodash'; export default class CreatorBase extends EntryShortcut { constructor(onUpdate, metadata, patient, shortcutData) { @@ -22,4 +23,21 @@ export default class CreatorBase extends EntryShortcut { get isComplete() { return this.hasParentContext() && this.hasChildren(); } + + get isMissingParent() { + return !this.hasParentContext(); + } + + get potentialParents() { + const knownParent = this.metadata["knownParentContexts"]; + if (knownParent === 'Patient' || knownParent === undefined) return []; + if (_.isArray(knownParent)) { + return knownParent; + } else if (_.isString(knownParent)) { + return [knownParent]; + } else { + console.warn("unknown type for knownParent: element looks like ", knownParent); + return []; + } + } } diff --git a/src/shortcuts/CreatorIntermediary.jsx b/src/shortcuts/CreatorIntermediary.jsx index 188601ab54..e1ef4ee005 100644 --- a/src/shortcuts/CreatorIntermediary.jsx +++ b/src/shortcuts/CreatorIntermediary.jsx @@ -1,5 +1,5 @@ import Shortcut from './Shortcut'; -import Lang from 'lodash'; +import _ from 'lodash'; export default class CreatorIntermediary extends Shortcut { constructor(onUpdate, metadata) { @@ -15,11 +15,11 @@ export default class CreatorIntermediary extends Shortcut { initialize(contextManager, trigger = undefined, updatePatient = true) { super.initialize(contextManager, trigger, updatePatient); - if (Lang.isUndefined(this.parentContext)) { + if (_.isUndefined(this.parentContext)) { super.determineParentContext(contextManager, this.metadata["knownParentContexts"], this.metadata["parentAttribute"]); } - if (!Lang.isUndefined(this.parentContext) && this.parentContext.children.indexOf(this) === -1) { + if (!_.isUndefined(this.parentContext) && this.parentContext.children.indexOf(this) === -1) { this.parentContext.setAttributeValue(this.metadata["parentAttribute"], true, false, updatePatient); this.parentContext.addChild(this); } @@ -115,10 +115,26 @@ export default class CreatorIntermediary extends Shortcut { } hasValueObjectAttributes() { - return !Lang.isEmpty(this.metadata["valueObjectAttributes"]); + return !_.isEmpty(this.metadata["valueObjectAttributes"]); } get isComplete() { return this.hasParentContext() && this.hasChildren(); } + + get isMissingParent() { + return !this.hasParentContext(); + } + get potentialParents() { + const knownParent = this.metadata["knownParentContexts"]; + if (knownParent === 'Patient' || knownParent === undefined) return []; + if (_.isArray(knownParent)) { + return knownParent; + } else if (_.isString(knownParent)) { + return [knownParent]; + } else { + console.warn("unknown type for knownParent: element looks like ", knownParent); + return []; + } + } } diff --git a/src/shortcuts/ShortcutManager.js b/src/shortcuts/ShortcutManager.js index 0a910e209c..7e199ae735 100644 --- a/src/shortcuts/ShortcutManager.js +++ b/src/shortcuts/ShortcutManager.js @@ -268,6 +268,7 @@ class ShortcutManager { } else { addTriggerForCurrentShortcut.bind(this)(triggers, item); } + // If this shortcut has a label, then add the label as a shortcut. if (item.label) { // Add a string trigger for incomplete placeholder addTriggerForCurrentShortcut.bind(this)({ @@ -341,7 +342,7 @@ class ShortcutManager { } let numberOfValidTriggers; if (this.triggersPerShortcut[shortcutId]) { - // If the shortcut has a label defined, don't include in in the list of valid triggers per shortcut + // If the shortcut has a label defined, don't include in in the list of valid triggers per shortcut numberOfValidTriggers = this.triggersPerShortcut[shortcutId].length - (shortcut.label ? 1 : 0); } else { numberOfValidTriggers = shortcut.label ? 1 : 0; @@ -402,10 +403,20 @@ class ShortcutManager { // stringTriggers is directly from shortcut metadata const stringTriggers = this.shortcuts[shortcutId].stringTriggers; // triggers is the computed options based on the valueset defined in metadata - const triggers = this.getTriggersForShortcut(shortcutId, context); - - // Filter out the label from triggers if there are defined stringTriggers that can be added - return triggers.filter(t => stringTriggers.length === 0 || t.name !== label); + // Clone this information so we aren't changing anything by reference + const triggers = [...this.getTriggersForShortcut(shortcutId, context)]; + + // Get the index of the label in the triggers list + const indexOfLabel = _.findIndex(triggers, t => t.name === label); + // If there is an instance of the label, and the stringTriggers isn't empty then we want to remove this instance of the label + // When stringTriggers doesn't have a length, we might be using the label as a shorthand for writing down the string trigger as well + if (indexOfLabel !== -1 && stringTriggers.length !== 0) { + // We only splice out this one instance of it in case the string trigger itself happens to match the label + // In this case there will be two instances of the label in our list and we want to remove one of them + // In the case where there is just one instance, then we can remove it safely + triggers.splice(indexOfLabel, 1); + } + return triggers; } getKeywordsForShortcut(shortcutId, context) { @@ -439,6 +450,10 @@ class ShortcutManager { return this.shortcuts[shortcutId]["shortcutGroupName"]; } + getShortcutLabel(shortcutId) { + return this.shortcuts[shortcutId].label; + } + getShortcutMetadata(shortcutId) { return this.shortcuts[shortcutId]; } diff --git a/src/shortcuts/Shortcuts.json b/src/shortcuts/Shortcuts.json index ce752774e1..6e8215297b 100644 --- a/src/shortcuts/Shortcuts.json +++ b/src/shortcuts/Shortcuts.json @@ -48,6 +48,7 @@ }, { "type": "CreatorBase", "id": "StagingCreator", + "label": "#staging", "name": "staging", "subtype": "menu", "getData": null, @@ -113,6 +114,7 @@ }, { "type": "CreatorBase", "id": "ProgressionCreator", + "label": "#disease status", "name": "disease status", "subtype": "menu", "getData": null, @@ -193,6 +195,7 @@ }, { "type": "CreatorBase", "id": "ToxicityCreator", + "label": "#toxicity", "name": "toxicity", "subtype": "menu", "getData": null, @@ -354,6 +357,7 @@ { "type": "InsertValue", "subtype": "choice", "id": "ConditionInserter", + "label": "@condition", "getData": {"object": "patient", "method": "getConditions", "itemKey": "entryInfo.entryId.id", "itemContext":"type", "dateLabel": "diagnosisDate"}, "isContext": true, "isGlobalContext": true, @@ -562,6 +566,7 @@ }, { "type": "CreatorBase", "id": "MedicationChangeReduceCreator", + "label": "#reduce medication", "name": "reduce medication", "subtype": "menu", "getData": null,