Skip to content

Commit 6432516

Browse files
authored
Merge pull request #707 from NotePlan:React-Root-MessageBanner
BannerMessage + Root changes
2 parents 1132a6d + f07c4cc commit 6432516

24 files changed

Lines changed: 404 additions & 99 deletions

File tree

‎dwertheimer.Forms/src/NPTemplateForm.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ export async function onFormSubmitFromHTMLView(actionType: string, data: any = n
249249
returnValue = await handleSubmitButtonClick(data, reactWindowData) //update the data to send it back to the React Window
250250
break
251251
default:
252-
await sendBannerMessage(WEBVIEW_WINDOW_ID, `Plugin received an unknown actionType: "${actionType}" command with data:\n${JSON.stringify(data)}`)
252+
await sendBannerMessage(WEBVIEW_WINDOW_ID, `Plugin received an unknown actionType: "${actionType}" command with data:\n${JSON.stringify(data)}`, 'ERROR')
253253
break
254254
}
255255
if (returnValue && returnValue !== reactWindowData) {

‎dwertheimer.ReactSkeleton/readme.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -360,9 +360,10 @@ TODO:Insert picture here
360360
import { sendBannerMessage } from '@helpers/HTMLView'
361361

362362
await sendBannerMessage(
363+
windowID,
363364
`this will display at the top of the screen`,
364-
color: string = 'w3-pale-red', /* background color */
365-
border: string = 'w3-border-red'
365+
type: 'INFO', /* or WARN or ERROR */,
366+
2000 /* optional; remove after 2,000ms */
366367
)
367368

368369

‎dwertheimer.ReactSkeleton/src/reactMain.js‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ export async function onMessageFromHTMLView(actionType: string, data: any = null
9595
reactWindowData = await handleSubmitButtonClick(data, reactWindowData) //update the data to send it back to the React Window
9696
break
9797
default:
98-
await sendBannerMessage(WEBVIEW_WINDOW_ID, `Plugin received an unknown actionType: "${actionType}" command with data:\n${JSON.stringify(data)}`)
98+
await sendBannerMessage(WEBVIEW_WINDOW_ID, `Plugin received an unknown actionType: "${actionType}" command with data:\n${JSON.stringify(data)}`, 'ERROR')
9999
break
100100
}
101101
if (reactWindowData) {
@@ -127,9 +127,9 @@ export async function updateReactWindowData(actionType: string, data: any = null
127127
}
128128

129129
/**
130-
* An example handler function that is called when someone clicks a button in the React Window
130+
* An example handler function that is called when someone clicks a button in the React Window.
131131
* When someone clicks a "Submit" button in the React Window, it calls the router (onMessageFromHTMLView)
132-
* which sees the actionType === "onSubmitClick" so it routes to this function for processing
132+
* which sees the actionType === "onSubmitClick" so it routes to this function for processing.
133133
* @param {any} data - the data sent from the React Window for the action 'onSubmitClick'
134134
* @param {any} reactWindowData - the current data in the React Window
135135
* @returns {any} - the updated data to send back to the React Window
@@ -141,6 +141,8 @@ async function handleSubmitButtonClick(data: any, reactWindowData: PassedData):
141141
`Plugin received an actionType: "onSubmitClick" command with data:<br/>${JSON.stringify(
142142
data,
143143
)}.<br/>Plugin then fired this message over the bridge to the React window and changed the data in the React window.`,
144+
'INFO',
145+
2000,
144146
)
145147
clo(reactWindowData, `handleSubmitButtonClick: reactWindowData BEFORE update`)
146148
// change the data in the React window for the row that was clicked (just an example)

‎dwertheimer.TaskAutomations/src/NPOverdueReact.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ export async function dropdownChangeReceived(data: { rows: Array<any>, choice: s
269269
`NotePlan plugin TaskAutomations could not find the paragraph you were editing. This may be a bug. Or perhaps you edited the content in the note before making a change in the popup window? We need to be able to match lines of text, so you should generally do your editing in the popup window when if it is open. If you still think this is a bug, please report it to the developer.\nSearching for: ${JSON.stringify(
270270
row,
271271
)}`,
272+
'WARN',
272273
)
273274
}
274275
}

‎helpers/HTMLView.js‎

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -769,14 +769,24 @@ export function replaceMarkdownLinkWithHTMLLink(str: string): string {
769769
* @return {any} - the result of the runJavaScript call (should be unimportant in this case -- undefined is ok)
770770
* @author @dwertheimer
771771
*/
772-
export async function sendToHTMLWindow(windowId: string, actionType: string, data: any = {}, updateInfo: string = ''): any {
772+
export async function sendToHTMLWindow(
773+
windowId: string,
774+
actionType: string,
775+
data: any = {},
776+
updateInfo: string = '',
777+
): Promise<any> {
773778
try {
774779
const windowExists = isHTMLWindowOpen(windowId)
775780
if (!windowExists) logWarn(`sendToHTMLWindow`, `Window ${windowId} does not exist; setting NPWindowID = undefined`)
776781
const windowIdToSend = windowExists ? windowId : undefined // for iphone/ipad you have to send undefined
777782
const dataWithUpdated = {
778783
...data,
779-
...{ lastUpdated: { msg: `${actionType}${updateInfo ? ` ${updateInfo}` : ''}`, date: new Date().toLocaleString() } },
784+
...{
785+
lastUpdated: {
786+
msg: `${actionType}${updateInfo ? ` ${updateInfo}` : ''}`,
787+
date: new Date().toLocaleString()
788+
},
789+
},
780790
NPWindowID: windowExists ? windowId : undefined,
781791
}
782792
// logDebug(`Bridge::sendToHTMLWindow`, `sending type:"${actionType}" payload=${JSON.stringify(data, null, 2)}`)
@@ -879,14 +889,35 @@ export async function updateGlobalSharedData(windowId: string, data: any, mergeD
879889
}
880890

881891
/**
882-
* Send a warning message to the HTML window (displays a warning message at the top of page)
892+
* Send a warning message to the HTML window (displays a warning message at the top of page). Takes various parameters. Newer version that allows for more easier specifiation of message severity level.
883893
* @param {string} windowId - the id of the window to send the message to (should be the same as the window's id attribute)
884894
* @param {string} message - the message to be displayed
885-
* @param {string} color https://www.w3schools.com/w3css/w3css_colors.asp
886-
* @param {string} border (left vertical stripe border of box) https://www.w3schools.com/w3css/w3css_colors.asp
895+
* @param {string} type - the type of the message: 'INFO', 'WARN', 'ERROR', or 'REMOVE'
896+
* @param {number} timeout (optional) - the number of milliseconds to wait before the message disappears
887897
*/
888-
export async function sendBannerMessage(windowId: string, message: string, color: string = 'w3-pale-red', border: string = 'w3-border-red'): Promise<any> {
889-
return await sendToHTMLWindow(windowId, 'SHOW_BANNER', { warn: true, msg: message, color, border })
898+
export async function sendBannerMessage(windowId: string, message: string, type: string, timeout: number = NaN): Promise<any> {
899+
logDebug(`sendBannerMessage`, `message: ${message}, type: ${type}, timeout: ${timeout}`)
900+
if (type === 'REMOVE') {
901+
return await sendToHTMLWindow(windowId, 'REMOVE_BANNER', {}, '')
902+
}
903+
904+
let colorClass = 'color-error'
905+
let borderClass = 'border-error'
906+
let icon = 'fa-regular fa-circle-exclamation'
907+
switch (type) {
908+
case 'INFO':
909+
colorClass = 'color-info'
910+
borderClass = 'border-info'
911+
icon = 'fa-regular fa-circle-info'
912+
break
913+
case 'WARN':
914+
colorClass = 'color-warn'
915+
borderClass = 'border-warn'
916+
icon = 'fa-regular fa-triangle-exclamation'
917+
break
918+
}
919+
logDebug(`sendBannerMessage`, `colorClass: ${colorClass}, borderClass: ${borderClass}, icon: ${icon}`)
920+
return await sendToHTMLWindow(windowId, 'SHOW_BANNER', { type, msg: message, color: colorClass, border: borderClass, icon, timeout }, '')
890921
}
891922

892923
/**

‎helpers/paragraph.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ function caseInsensitiveSubstringMatch(searchTerm: string, textToSearch: string)
3131
// First need to escape any special characters in the search term
3232
const escapedSearchTerm = searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
3333
const re = new RegExp(`${escapedSearchTerm}`, 'i') // = case insensitive match
34-
logDebug('caseInsensitiveSubstringMatch', `re: ${re.toString()} / textToSearch: ${textToSearch} / ${String(re.test(textToSearch))}`)
3534
return re.test(textToSearch)
3635
} catch (error) {
3736
logError('paragraph/caseInsensitiveSubstringMatch', `Error matching '${searchTerm}' to '${textToSearch}': ${error.message}`)
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// @flow
2+
//-----------------------------------------------------------------------------
3+
// bannerClickHandlers.js
4+
// Handler functions for banner test clicks that come over the bridge.
5+
// The routing is in pluginToHTMLBridge.js/bridgeClickDashboardItem()
6+
// Last updated 2025-12-16 for v2.4.0.b2 by @jgclark
7+
//-----------------------------------------------------------------------------
8+
9+
import { WEBVIEW_WINDOW_ID } from './constants'
10+
import { handlerResult, setPluginData } from './dashboardHelpers'
11+
import type { MessageDataObject, TBridgeClickHandlerResult } from './types'
12+
import { clo, JSP, logDebug, logError, logInfo, logTimer, logWarn, timer } from '@helpers/dev'
13+
import { sendBannerMessage } from '@helpers/HTMLView'
14+
15+
/********************************************************************************
16+
* HANDLERS
17+
- Handlers should use the standard return type of TBridgeClickHandlerResult
18+
- handlerResult() can be used to create the result object
19+
- Types are defined in types.js
20+
- type TActionOnReturn = 'UPDATE_CONTENT' | 'REMOVE_LINE' | 'REFRESH_JSON' | 'START_DELAYED_REFRESH_TIMER' etc.
21+
*********************************************************************************/
22+
23+
/**
24+
* Handle a banner test click
25+
* @param {MessageDataObject} data
26+
* @returns {TBridgeClickHandlerResult}
27+
*/
28+
export async function handleBannerTestClick(
29+
data: MessageDataObject,
30+
): Promise<TBridgeClickHandlerResult> {
31+
try {
32+
const { actionType, sectionCodes } = data
33+
logInfo('handleBannerTestClick', `actionType: ${actionType}`)
34+
switch (actionType) {
35+
case 'testBannerInfo':
36+
await sendBannerMessage(WEBVIEW_WINDOW_ID, `Showing info banner: current sections are ${String(sectionCodes)}`, 'INFO', 5000)
37+
break
38+
case 'testBannerError':
39+
await sendBannerMessage(WEBVIEW_WINDOW_ID, `Test error banner`, 'ERROR')
40+
break
41+
case 'testBannerWarning':
42+
await sendBannerMessage(WEBVIEW_WINDOW_ID, `Test warning banner`, 'WARN')
43+
break
44+
case 'testRemoveBanner':
45+
await sendBannerMessage(WEBVIEW_WINDOW_ID, ``, 'REMOVE')
46+
break
47+
default:
48+
logError('handleBannerTestClick', `Unknown actionType: ${actionType}`)
49+
return handlerResult(false)
50+
}
51+
return handlerResult(true)
52+
}
53+
catch (error) {
54+
logError('handleBannerTestClick', error.message)
55+
await sendBannerMessage(WEBVIEW_WINDOW_ID, `Error in handleBannerTestClick: ${error.message}`, 'ERROR')
56+
return handlerResult(false)
57+
}
58+
}

‎jgclark.Dashboard/src/dataGeneration.js‎

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ export async function getSomeSectionsData(
8080
// logDebug('getSomeSectionsData', `🔹 Starting with ${sectionCodesToGet.toString()} ...`)
8181
const config: TDashboardSettings = await getDashboardSettings()
8282

83+
// TODO: change generation order to suit the new custom section display order
84+
8385
let sections: Array<TSection> = []
8486
if (sectionCodesToGet.includes('INFO')) sections.push(...getInfoSectionData(config, useDemoData))
8587
// v2: for Timeblocks, now done inside getTodaySectionData()
@@ -91,20 +93,18 @@ export async function getSomeSectionsData(
9193
if (sectionCodesToGet.includes('M') && config.showMonthSection) sections.push(...getThisMonthSectionData(config, useDemoData, useEditorWherePossible))
9294
if (sectionCodesToGet.includes('Q') && config.showQuarterSection) sections.push(...getThisQuarterSectionData(config, useDemoData, useEditorWherePossible))
9395
if (sectionCodesToGet.includes('Y') && config.showYearSection) sections.push(...getThisYearSectionData(config, useDemoData, useEditorWherePossible))
96+
9497
// moderately quick to generate
9598
if (sectionCodesToGet.includes('PROJ') && config.showProjectSection) {
9699
logInfo('getSomeSectionsData', `🔹 Getting Project section data as part of ${sectionCodesToGet.toString()}`)
97100
const projectSection = await getProjectSectionData(config, useDemoData)
98101
if (projectSection) sections.push(projectSection)
99102
}
103+
100104
// The rest can all be slow to generate
101105
if (sectionCodesToGet.includes('SAVEDSEARCH')) sections.push(...(await getSavedSearchResults(config, useDemoData)))
102106
if (sectionCodesToGet.includes('TAG') && config.tagsToShow) {
103-
// v1:
104-
// const tagSections = getTaggedSections(config, useDemoData).filter((s) => s) //get rid of any nulls
105-
// sections = tagSections.length ? sections.concat(tagSections) : sections
106-
107-
// v2:
107+
// TODO: change so that tags can be generated separately from each other, letting them be specified in the section order component.
108108
const tagSections = getTagSectionDetails(removeInvalidTagSections(config))
109109
// clo(tagSections, 'getSomeSectionsData tagSections')
110110
let index = 0
@@ -168,6 +168,37 @@ export function getInfoSectionData(_config: TDashboardSettings, _useDemoData: bo
168168
sectionTitleColorPart: 'sidebarInfo',
169169
sectionItems: items,
170170
isReferenced: false,
171+
// TODO(later): remove this once we have a proper banner system
172+
actionButtons: (_config.FFlag_ShowBannerTestButtons ? [
173+
{
174+
actionName: 'testBannerInfo',
175+
actionParam: 'jgclark.Dashboard.main',
176+
actionPluginID: `${pluginJson['plugin.id']}`,
177+
display: '<i class= "fa-regular fa-info-circle sidebarInfo" ></i> ',
178+
tooltip: 'Show an info banner',
179+
},
180+
{
181+
actionName: 'testBannerWarning',
182+
actionParam: 'jgclark.Dashboard.main',
183+
actionPluginID: `${pluginJson['plugin.id']}`,
184+
display: '<i class= "fa-regular fa-triangle-exclamation sidebarInfo" ></i> ',
185+
tooltip: 'Show a warning banner',
186+
},
187+
{
188+
actionName: 'testBannerError',
189+
actionParam: 'jgclark.Dashboard.main',
190+
actionPluginID: `${pluginJson['plugin.id']}`,
191+
display: '<i class= "fa-regular fa-circle-exclamation sidebarInfo" ></i> ',
192+
tooltip: 'Show an error banner',
193+
},
194+
{
195+
actionName: 'testRemoveBanner',
196+
actionParam: 'jgclark.Dashboard.main',
197+
actionPluginID: `${pluginJson['plugin.id']}`,
198+
display: '<i class= "fa-regular fa-xmark sidebarInfo" ></i> ',
199+
tooltip: 'Remove the banner',
200+
},
201+
] : []),
171202
})
172203
return sections
173204
}

‎jgclark.Dashboard/src/dataGenerationDays.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ export function getTodaySectionData(config: TDashboardSettings, useDemoData: boo
186186
tooltip: config.rescheduleNotMove
187187
? '(Re)Schedule all open items from today to tomorrow. (Press ⌘-click to move instead.)'
188188
: 'Move all open items from today to tomorrow. (Press ⌘-click to (re)schedule instead.)',
189+
// TODO: TEST: does this need a refresh? Will it be caught in other ways?
189190
actionParam: 'true' /* refresh afterwards */,
190191
postActionRefresh: ['DT', 'DO'], // refresh 2 sections afterwards
191192
},

‎jgclark.Dashboard/src/pluginToHTMLBridge.js‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
//-----------------------------------------------------------------------------
66

77
import pluginJson from '../plugin.json'
8+
import { handleBannerTestClick } from './bannerClickHandlers'
89
import {
910
doAddItem,
1011
doAddItemToFuture,
@@ -367,6 +368,25 @@ export async function bridgeClickDashboardItem(data: MessageDataObject) {
367368
}
368369
break
369370
}
371+
372+
// TODO(later): remove these once we have a proper banner system
373+
case 'testBannerInfo': {
374+
result = await handleBannerTestClick({ actionType: 'testBannerInfo', sectionCodes: data.sectionCodes })
375+
break
376+
}
377+
case 'testBannerError': {
378+
result = await handleBannerTestClick({ actionType: 'testBannerError' })
379+
break
380+
}
381+
case 'testBannerWarning': {
382+
result = await handleBannerTestClick({ actionType: 'testBannerWarning' })
383+
break
384+
}
385+
case 'testRemoveBanner': {
386+
result = await handleBannerTestClick({ actionType: 'testRemoveBanner' })
387+
break
388+
}
389+
370390
default: {
371391
result = {
372392
success: false,
@@ -410,7 +430,8 @@ async function processActionOnReturn(handlerResultIn: TBridgeClickHandlerResult,
410430
logDebug('processActionOnReturn', `-> failed handlerResult(false) ${handlerResult.errorMsg || ''}`)
411431
await sendBannerMessage(
412432
WEBVIEW_WINDOW_ID,
413-
`Sorry; something's gone wrong for "${data.actionType}" ${handlerResult.errorMsg || ''}. This is normally caused by a changing a task in NotePlan since the last time the Dashboard was refreshed. There will be more details in the app's console log.`,
433+
`Sorry; something's gone wrong for "${data.actionType}" ${handlerResult.errorMsg || ''}. This is most often caused by changing a task in NotePlan since the last time the Dashboard was refreshed. If it persists, please report it to the developer.`,
434+
'WARN',
414435
)
415436
return
416437
}

0 commit comments

Comments
 (0)