@flowfuse/nr-launcher 2.31.3 → 2.31.4-5ccdf52-202606300926.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/admin.js CHANGED
@@ -148,6 +148,7 @@ class AdminInterface {
148
148
  await launcher.start(request.body.safe ? States.SAFE : States.RUNNING)
149
149
  } catch (error) {
150
150
  await launcher.logAuditEvent('start-failed', { error })
151
+ return response.status(500).send({ error: error.message })
151
152
  }
152
153
  response.send({})
153
154
  }, 2000)
@@ -157,6 +158,7 @@ class AdminInterface {
157
158
  await launcher.start(request.body.safe ? States.SAFE : States.RUNNING)
158
159
  } catch (error) {
159
160
  await launcher.logAuditEvent('start-failed', { error })
161
+ return response.status(500).send({ error: error.message })
160
162
  }
161
163
  response.send({})
162
164
  } else {
@@ -265,6 +265,21 @@ function getSettingsFile (settings) {
265
265
  },`
266
266
  }
267
267
  }
268
+ // Load via page.scripts so they run regardless of active theme; branding is dropped for non-FlowFuse themes.
269
+ const requestedTheme = projectSettings.theme
270
+ const themeCommonDir = path.join(__dirname, 'theme', 'common')
271
+ const pageScripts = [path.join(themeCommonDir, 'forge-platform.js')]
272
+ if (!requestedTheme || ['forge', 'forge-light', 'forge-dark'].includes(requestedTheme)) {
273
+ pageScripts.push(path.join(themeCommonDir, 'forge-branding.js'))
274
+ }
275
+ projectSettings.page_scripts = `scripts: ${JSON.stringify(pageScripts)},`
276
+ // launcher values shared by the flowfuse-common plugin settings and the legacy theme settings
277
+ const ffSettings = {
278
+ launcherVersion: settings.launcherVersion,
279
+ projectURL: `${settings.forgeURL}/instance/${settings.projectID}`
280
+ }
281
+ projectSettings.flowfuseCommonSettings = `"flowfuse-common": ${JSON.stringify(ffSettings)},`
282
+
268
283
  if (projectSettings.theme) {
269
284
  const nrMajor = parseInt((settings.nodeRedVersion || '0.0.0').split('.')[0], 10) || 0
270
285
  let activeTheme = projectSettings.theme
@@ -279,12 +294,7 @@ function getSettingsFile (settings) {
279
294
  activeTheme = 'forge-light'
280
295
  }
281
296
 
282
- const themeSettings = {
283
- launcherVersion: settings.launcherVersion,
284
- forgeURL: settings.forgeURL,
285
- projectURL: `${settings.forgeURL}/instance/${settings.projectID}`
286
- }
287
- projectSettings.themeSettings = `"${activeTheme}": ${JSON.stringify(themeSettings)},`
297
+ projectSettings.themeSettings = `"${activeTheme}": ${JSON.stringify(ffSettings)},`
288
298
  projectSettings.theme = `theme: '${activeTheme}',`
289
299
  }
290
300
 
@@ -419,10 +429,12 @@ module.exports = {
419
429
  },
420
430
  ${nodesDir}
421
431
  ${projectSettings.themeSettings || ''}
432
+ ${projectSettings.flowfuseCommonSettings || ''}
422
433
  editorTheme: {
423
434
  ${projectSettings.theme}
424
435
  page: {
425
436
  title: '${projectSettings.page_title}',
437
+ ${projectSettings.page_scripts || ''}
426
438
  ${projectSettings.page_favicon}
427
439
  },
428
440
  header: {
@@ -0,0 +1,8 @@
1
+ module.exports = function (RED) {
2
+ RED.plugins.registerPlugin('flowfuse-common', {
3
+ settings: {
4
+ launcherVersion: { exportable: true },
5
+ projectURL: { exportable: true }
6
+ }
7
+ })
8
+ }
@@ -0,0 +1,69 @@
1
+ (function () {
2
+ /* global RED, $ */
3
+ // Favicon and menu items; runtimeSettings.js omits this script for non-FlowFuse (OEM) themes. Uses helpers from forge-platform.js.
4
+ function changeFavicon (src) {
5
+ const link = document.createElement('link')
6
+ const oldLink = $('link[href="favicon.ico"]')[0] || $('#dynamic-favicon')[0]
7
+ link.id = 'dynamic-favicon'
8
+ link.rel = 'shortcut icon'
9
+ link.href = src
10
+ if (oldLink) {
11
+ document.head.removeChild(oldLink)
12
+ }
13
+ document.head.appendChild(link)
14
+ }
15
+
16
+ window.addEventListener('load', (_event) => {
17
+ const favicon32 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAADzUlEQVR4AcXB206UVxzG4d+71jcbBmYGpjJIg2iHiNYEJaSKrQe4QeMNcOaR11BP2wuwV2JvQSMkxFAiGk1Q8UCjAoJsAkgRZuZb/wJtk6YtuEN4Ht3o7f0B+AXoBhy7w4Bh4FoEXAe+Z3cJ6AauO+Ake+c7B0TsHe/YY44495thjjj0WsdvMMNaZsSHiC7MQwAwkXBSZT6eJ0ml8MmlyziK+ADODEPDJpNU0N1t9qUT94cPkWlvJNDaSzGbxqZTkHBE7yOIYeU/d/v2h2NVFc3c3De3tShcKOO/Ffynic5lhIRDV1FjhyBFr6emh+dQpMk1NTs7xPhGfyEJgQyqft6auLjt48SL7OjqUyGTER4j4CBYCmOGiyOpaWqz59GlrPXdO9W1tcomEeB8z4nLZqqurhEoFC4HI4pgtSWyQc/h02moKBWtob2f/yZNW7OxUTWOjl8R24nLZlicnbe7RI+bHxmx5fFxri4vE5bIsBKJvr1wxzLAQYlsnkLx38t58MkmUTiuZy1Hb1ERtc7PSDQ3Ie8d2zHg3N2dTd++GicFBzY+NaW1hQRbHbJLYJBF1XL3q+FPEZ7I4tsUXL+xVf79NDA7q7fi4s2pVcg4k5D3/FrEDQqVibx48CC9v3tTUyIhW5+cd6+Qc8p7tOHZAqFRs8flz5p8+ZW1hQZghiQ8RsQOiTMYd6evj4IUL9np4OLy8dYu5x49VXVkRziGJrUTjAwO2KYQYEOvknJP35hMJ+XSaVD5PuqFByWwWeS+2kC4U9M3lyzrQ02Ozo6M2PjAQpu/d08rMjKxaFRKS2CSxQb9euhTMTJgZGyQEQjIknPf4VIp0Q4PlDh2i8cQJKx4/rrqWFrkoEtuwEOz36WmbffjQ3ty/r4Vnz1hbWFB1dZVQrWIhoBu9vQEQ2zHDzMAMOUe6ULB9HR124OxZip2dSmaz4j0sBKssL7O2tGSVt2+prKwQl8tEfAgJSfzt3fy8Xt2+rck7d6y+rc1az5+3r8+cobZYdEj8HzmnZC5HMpcT/+D7SqWfAPERJCHnsBC0MjOj6ZERvR4asnczMyFRV0cqn0feiw/g+0qlnwHxiSSxYW1xUbOjo25icJD5J08slMuWqKsjUVODnBNbiNghco4N5aUlTQwOanJoyGqLRQpHj9pXx46FfKmkTLFIMpuVT6Vw3oNzROw0CXkPZlqemmJ5clIv+/uJUilLZrOk8nlL5nJEmQw+kSACYsDxBUgC79kQl8tamZ1lZWZGmPGX2AHD7BJJyDnkPfIeeX/XAdeAO0Bg9wTgN+DHPwAIHJAeMJ00fgAAAABJRU5ErkJggg=='
18
+ changeFavicon(favicon32)
19
+
20
+ const ff = window.FlowFuse || {}
21
+
22
+ const addMenuItems = function () {
23
+ if (!RED || document.querySelector('#usermenu-item-ffsite')) { return }
24
+ const settings = (RED.settings && RED.settings['flowfuse-common']) || {}
25
+
26
+ RED.menu.addItem('red-ui-header-button-sidemenu', null) // menu seperator
27
+ RED.menu.addItem('red-ui-header-button-sidemenu', {
28
+ id: 'usermenu-item-ffsite',
29
+ label: 'About FlowFuse',
30
+ onselect: function () {
31
+ ff.navigateTo('https://flowfuse.com/')
32
+ }
33
+ })
34
+ const projectURL = ff.getProjectURL ? ff.getProjectURL() : ''
35
+ if (projectURL) {
36
+ RED.menu.addItem('red-ui-header-button-sidemenu', {
37
+ id: 'usermenu-item-ffmain',
38
+ label: 'FlowFuse Application',
39
+ onselect: function () {
40
+ ff.navigateTo(projectURL)
41
+ }
42
+ })
43
+ }
44
+ if (settings.launcherVersion) {
45
+ RED.menu.addItem('red-ui-header-button-sidemenu', {
46
+ id: 'usermenu-item-fflv',
47
+ label: 'FlowFuse Launcher v' + settings.launcherVersion,
48
+ onselect: function () {
49
+ // do nothing
50
+ }
51
+ })
52
+ }
53
+ }
54
+
55
+ // Add now if the menu button already exists; otherwise add it once Node-RED inserts it.
56
+ if (document.querySelector('#red-ui-header-button-sidemenu')) {
57
+ addMenuItems()
58
+ } else {
59
+ const observer = new MutationObserver(function () {
60
+ if (document.querySelector('#red-ui-header-button-sidemenu')) {
61
+ observer.disconnect()
62
+ addMenuItems()
63
+ }
64
+ })
65
+ observer.observe(document.querySelector('#red-ui-header') || document.body, { childList: true, subtree: true })
66
+ setTimeout(function () { observer.disconnect() }, 30000)
67
+ }
68
+ })
69
+ })()
@@ -0,0 +1,59 @@
1
+ (function () {
2
+ /* global RED, $ */
3
+ // Navigation interception and iframe messaging; loaded via editorTheme.page.scripts so it runs regardless of active theme.
4
+ const context = {
5
+ isEmbedded: window.parent !== window.self,
6
+ shouldEmitInsteadOfRedirect: false
7
+ }
8
+ const navigateTo = (url) => {
9
+ if (context.shouldEmitInsteadOfRedirect) {
10
+ window.parent.postMessage({
11
+ type: 'navigate',
12
+ payload: url
13
+ }, '*')
14
+ } else {
15
+ window.location = url
16
+ }
17
+ }
18
+ // falls back to the project link in the header when flowfuse-common settings are absent
19
+ const getProjectURL = () => {
20
+ const settings = (typeof RED !== 'undefined' && RED.settings && RED.settings['flowfuse-common']) || {}
21
+ if (settings.projectURL) {
22
+ return settings.projectURL
23
+ }
24
+ const img = $('#red-ui-header > span > a > img')
25
+ const ownerHref = img.parent().prop('href')
26
+ if (ownerHref && /http[s]*:\/\/.*\/project\/\w+-\w+-\w+-\w+-\w+.*/.test(ownerHref)) {
27
+ return ownerHref
28
+ }
29
+ return ''
30
+ }
31
+ document.addEventListener('click', (e) => {
32
+ if (e.target.closest('#usermenu-item-logout')) {
33
+ e.preventDefault()
34
+ e.stopPropagation()
35
+ if (context.shouldEmitInsteadOfRedirect) {
36
+ window.parent.postMessage({ type: 'logout' }, '*')
37
+ }
38
+ return
39
+ }
40
+ if (e.target.closest('.red-ui-header-logo a')) {
41
+ e.preventDefault()
42
+ navigateTo(getProjectURL())
43
+ }
44
+ }, true)
45
+
46
+ function handleMessage (event) {
47
+ if (event.data && event.data.type === 'prevent-redirect') {
48
+ context.shouldEmitInsteadOfRedirect = event.data.payload
49
+ }
50
+ }
51
+
52
+ if (context.isEmbedded) {
53
+ window.parent.postMessage({ type: 'load', payload: true }, '*')
54
+ window.addEventListener('message', handleMessage)
55
+ }
56
+
57
+ // shared with forge-branding.js
58
+ window.FlowFuse = Object.assign(window.FlowFuse || {}, { navigateTo, getProjectURL })
59
+ })()
@@ -13,9 +13,6 @@ module.exports = function (RED) {
13
13
  RED.plugins.registerPlugin('forge', {
14
14
  type: 'node-red-theme',
15
15
  schemes: ['light', 'dark'],
16
- scripts: [
17
- 'lib/theme/forge/forge-common.js'
18
- ],
19
16
  css: [
20
17
  'lib/theme/forge/forge.css'
21
18
  ],
@@ -12,10 +12,6 @@ module.exports = function (RED) {
12
12
 
13
13
  RED.plugins.registerPlugin('forge-dark', {
14
14
  type: 'node-red-theme',
15
- scripts: [
16
- 'lib/theme/common/forge-common.js'
17
- // /* optional */ 'lib/theme/forge-dark/forge-dark-custom.js'
18
- ],
19
15
  css: [
20
16
  'lib/theme/common/forge-common.css',
21
17
  'lib/theme/forge-dark/forge-dark-theme.css'
@@ -12,10 +12,6 @@ module.exports = function (RED) {
12
12
 
13
13
  RED.plugins.registerPlugin('forge-light', {
14
14
  type: 'node-red-theme',
15
- scripts: [
16
- 'lib/theme/common/forge-common.js'
17
- // /* optional */ 'lib/theme/forge-light/forge-light-custom.js'
18
- ],
19
15
  css: [
20
16
  'lib/theme/common/forge-common.css',
21
17
  'lib/theme/forge-light/forge-light-theme.css'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowfuse/nr-launcher",
3
- "version": "2.31.3",
3
+ "version": "2.31.4-5ccdf52-202606300926.0",
4
4
  "description": "FlowFuse Launcher for running Node-RED",
5
5
  "exports": {
6
6
  "./auditLogger": "./lib/auditLogger/index.js",
@@ -13,6 +13,7 @@
13
13
  "node-red": {
14
14
  "plugins": {
15
15
  "flowfuse-auth": "lib/auth/httpAuthPlugin.js",
16
+ "flowfuse-common": "lib/theme/common/flowfuse-common.js",
16
17
  "flowfuse-library": "lib/storage/libraryPlugin.js",
17
18
  "flowfuse-blueprint-library": "lib/storage/blueprintPlugin.js",
18
19
  "forge-light": "lib/theme/forge-light/forge-light.js",
@@ -47,12 +48,12 @@
47
48
  },
48
49
  "homepage": "https://github.com/FlowFuse/nr-launcher#readme",
49
50
  "dependencies": {
50
- "@flowfuse/nr-assistant": "^0.16.0",
51
- "@flowfuse/nr-file-nodes": "^0.0.10",
52
- "@flowfuse/nr-mqtt-nodes": "^0.3.0",
53
- "@flowfuse/nr-project-nodes": "^0.7.8",
51
+ "@flowfuse/nr-assistant": "0.16.1-4db3964-202606261214.0",
52
+ "@flowfuse/nr-file-nodes": "0.0.11-cc0b339-202606241506.0",
53
+ "@flowfuse/nr-mqtt-nodes": "0.3.1-69e7b9c-202606220918.0",
54
+ "@flowfuse/nr-project-nodes": "0.7.9-783c2eb-202606241244.0",
54
55
  "@flowfuse/nr-subflow-export": "^1.0.2",
55
- "@flowfuse/nr-tables-nodes": "^0.2.2",
56
+ "@flowfuse/nr-tables-nodes": "0.2.3-fe7d48f-202606220919.0",
56
57
  "@node-red/util": "^3.1.0",
57
58
  "body-parser": "^1.20.2",
58
59
  "bytes": "^3.1.2",
@@ -1,125 +0,0 @@
1
- (function () {
2
- /* global RED, $ */
3
- // monitorInsertion: derived from https://github.com/naugtur/insertionQuery/blob/master/insQ.min.js (MIT License Copyright (c) 2014-present Zbyszek Tenerowicz <naugtur@gmail.com>)
4
- // eslint-disable-next-line
5
- document.head = (document.head || document.getElementsByTagName('head')[0]);
6
- // eslint-disable-next-line
7
- const monitorInsertion = (function () { 'use strict'; let m = 100; let t = !1; let u = 'animationName'; let d = ''; const n = 'Webkit Moz O ms Khtml'.split(' '); let e = ''; const i = document.createElement('div'); const s = { strictlyNew: !0, timeout: 20, addImportant: !1 }; if (i.style.animationName && (t = !0), !1 === t) for (let o = 0; o < n.length; o++) if (void 0 !== i.style[n[o] + 'AnimationName']) { e = n[o], u = e + 'AnimationName', d = '-' + e.toLowerCase() + '-', t = !0; break } function c (t) { return s.strictlyNew && !0 === t.QinsQ } function r (t, n) { function e (t) { t.animationName !== o && t[u] !== o || c(t.target) || n(t.target) } let i; var o = 'insQ_' + m++; const r = s.addImportant ? ' !important' : ''; (i = document.createElement('style')).innerHTML = '@' + d + 'keyframes ' + o + ' { from { outline: 1px solid transparent } to { outline: 0px solid transparent } }\n' + t + ' { animation-duration: 0.001s' + r + '; animation-name: ' + o + r + '; ' + d + 'animation-duration: 0.001s' + r + '; ' + d + 'animation-name: ' + o + r + '; } ', document.head.appendChild(i); const a = setTimeout(function () { document.addEventListener('animationstart', e, !1), document.addEventListener('MSAnimationStart', e, !1), document.addEventListener('webkitAnimationStart', e, !1) }, s.timeout); return { destroy: function () { clearTimeout(a), i && (document.head.removeChild(i), i = null), document.removeEventListener('animationstart', e), document.removeEventListener('MSAnimationStart', e), document.removeEventListener('webkitAnimationStart', e) } } } function a (t) { t.QinsQ = !0 } function f (t) { if (t) for (a(t), t = t.firstChild; t; t = t.nextSibling) void 0 !== t && t.nodeType === 1 && f(t) } function l (t, n) { let e; let i = []; const o = function () { clearTimeout(e), e = setTimeout(function () { i.forEach(f), n(i), i = [] }, 10) }; return r(t, function (t) { if (!c(t)) { a(t); const n = (function t (n) { return c(n.parentNode) || n.nodeName === 'BODY' ? n : t(n.parentNode) }(t)); i.indexOf(n) < 0 && i.push(n), o() } }) } function v (n) { return !(!t || !n.match(/[^{}]/)) && (s.strictlyNew && f(document.body), { every: function (t) { return r(n, t) }, summary: function (t) { return l(n, t) } }) } return v.config = function (t) { for (const n in t)t.hasOwnProperty(n) && (s[n] = t[n]) }, v }()); typeof module !== 'undefined' && void 0 !== module.exports && (module.exports = monitorInsertion)
8
- const context = {
9
- isEmbedded: window.parent !== window.self,
10
- shouldEmitInsteadOfRedirect: false
11
- }
12
- const navigateTo = (url) => {
13
- if (context.shouldEmitInsteadOfRedirect) {
14
- window.parent.postMessage({
15
- type: 'navigate',
16
- payload: url
17
- }, '*')
18
- } else {
19
- window.location = url
20
- }
21
- }
22
- const interceptLogoClick = (url) => {
23
- const logoAnchor = document.querySelector('#red-ui-header > .red-ui-header-logo > a')
24
-
25
- if (logoAnchor) {
26
- logoAnchor.addEventListener(
27
- 'click',
28
- (e) => {
29
- e.preventDefault()
30
- navigateTo(url)
31
- })
32
- }
33
- }
34
- const interceptLogOutClick = (url) => {
35
- document.querySelector('#usermenu-item-logout')
36
- .addEventListener('click', (e) => {
37
- e.preventDefault()
38
- e.stopPropagation()
39
- if (context.shouldEmitInsteadOfRedirect) {
40
- window.parent.postMessage({
41
- type: 'logout'
42
- }, '*')
43
- }
44
- })
45
- }
46
-
47
- function changeFavicon (src) {
48
- const link = document.createElement('link')
49
- const oldLink = $('link[href="favicon.ico"]')[0] || $('#dynamic-favicon"]')[0]
50
- link.id = 'dynamic-favicon'
51
- link.rel = 'shortcut icon'
52
- link.href = src
53
- if (oldLink) {
54
- document.head.removeChild(oldLink)
55
- }
56
- document.head.appendChild(link)
57
- }
58
-
59
- function handleMessage (event) {
60
- if (event.data.type === 'prevent-redirect') {
61
- context.shouldEmitInsteadOfRedirect = event.data.payload
62
- }
63
- }
64
-
65
- if (context.isEmbedded) {
66
- window.parent.postMessage({ type: 'load', payload: true }, '*')
67
- window.addEventListener('message', handleMessage)
68
- }
69
-
70
- window.addEventListener('load', (_event) => {
71
- // set favicon
72
- const favicon32 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAADzUlEQVR4AcXB206UVxzG4d+71jcbBmYGpjJIg2iHiNYEJaSKrQe4QeMNcOaR11BP2wuwV2JvQSMkxFAiGk1Q8UCjAoJsAkgRZuZb/wJtk6YtuEN4Ht3o7f0B+AXoBhy7w4Bh4FoEXAe+Z3cJ6AauO+Ake+c7B0TsHe/YY4495thjjj0WsdvMMNaZsSHiC7MQwAwkXBSZT6eJ0ml8MmlyziK+ADODEPDJpNU0N1t9qUT94cPkWlvJNDaSzGbxqZTkHBE7yOIYeU/d/v2h2NVFc3c3De3tShcKOO/Ffynic5lhIRDV1FjhyBFr6emh+dQpMk1NTs7xPhGfyEJgQyqft6auLjt48SL7OjqUyGTER4j4CBYCmOGiyOpaWqz59GlrPXdO9W1tcomEeB8z4nLZqqurhEoFC4HI4pgtSWyQc/h02moKBWtob2f/yZNW7OxUTWOjl8R24nLZlicnbe7RI+bHxmx5fFxri4vE5bIsBKJvr1wxzLAQYlsnkLx38t58MkmUTiuZy1Hb1ERtc7PSDQ3Ie8d2zHg3N2dTd++GicFBzY+NaW1hQRbHbJLYJBF1XL3q+FPEZ7I4tsUXL+xVf79NDA7q7fi4s2pVcg4k5D3/FrEDQqVibx48CC9v3tTUyIhW5+cd6+Qc8p7tOHZAqFRs8flz5p8+ZW1hQZghiQ8RsQOiTMYd6evj4IUL9np4OLy8dYu5x49VXVkRziGJrUTjAwO2KYQYEOvknJP35hMJ+XSaVD5PuqFByWwWeS+2kC4U9M3lyzrQ02Ozo6M2PjAQpu/d08rMjKxaFRKS2CSxQb9euhTMTJgZGyQEQjIknPf4VIp0Q4PlDh2i8cQJKx4/rrqWFrkoEtuwEOz36WmbffjQ3ty/r4Vnz1hbWFB1dZVQrWIhoBu9vQEQ2zHDzMAMOUe6ULB9HR124OxZip2dSmaz4j0sBKssL7O2tGSVt2+prKwQl8tEfAgJSfzt3fy8Xt2+rck7d6y+rc1az5+3r8+cobZYdEj8HzmnZC5HMpcT/+D7SqWfAPERJCHnsBC0MjOj6ZERvR4asnczMyFRV0cqn0feiw/g+0qlnwHxiSSxYW1xUbOjo25icJD5J08slMuWqKsjUVODnBNbiNghco4N5aUlTQwOanJoyGqLRQpHj9pXx46FfKmkTLFIMpuVT6Vw3oNzROw0CXkPZlqemmJ5clIv+/uJUilLZrOk8nlL5nJEmQw+kSACYsDxBUgC79kQl8tamZ1lZWZGmPGX2AHD7BJJyDnkPfIeeX/XAdeAO0Bg9wTgN+DHPwAIHJAeMJ00fgAAAABJRU5ErkJggg=='
73
- changeFavicon(favicon32)
74
-
75
- // monitor #red-ui-header-button-sidemenu & add main menu entries
76
- monitorInsertion('#red-ui-header-button-sidemenu').summary(function (_arrayOfInsertedNodes) {
77
- if (!RED) { return }
78
-
79
- RED.menu.addItem('red-ui-header-button-sidemenu', null) // menu seperator
80
- // add main menu item "About FlowFuse"
81
- RED.menu.addItem('red-ui-header-button-sidemenu', {
82
- id: 'usermenu-item-ffsite',
83
- label: 'About FlowFuse',
84
- onselect: function () {
85
- navigateTo('https://flowfuse.com/')
86
- }
87
- })
88
- // gather info from settings and page - prep for next 2 menu items
89
- const ffThemeSettings = RED.settings['forge-light'] || RED.settings['forge-dark']
90
- let projectURL = ''
91
- if (ffThemeSettings && ffThemeSettings.projectURL) {
92
- projectURL = ffThemeSettings.projectURL
93
- } else {
94
- const img = $('#red-ui-header > span > a > img')
95
- const ownerHref = img.parent().prop('href')
96
- // Test the URL is FlowFuse Project alike
97
- if (ownerHref && /http[s]*:\/\/.*\/project\/\w+-\w+-\w+-\w+-\w+.*/.test(ownerHref)) {
98
- projectURL = ownerHref
99
- }
100
- }
101
- // if projectURL is present, show link to project in main menu
102
- if (projectURL) {
103
- RED.menu.addItem('red-ui-header-button-sidemenu', {
104
- id: 'usermenu-item-ffmain',
105
- label: 'FlowFuse Application',
106
- onselect: function () {
107
- navigateTo(projectURL)
108
- }
109
- })
110
- }
111
- // if theme settings are present, add launcher version entry in main menu
112
- if (ffThemeSettings && ffThemeSettings.launcherVersion) {
113
- RED.menu.addItem('red-ui-header-button-sidemenu', {
114
- id: 'usermenu-item-fflv',
115
- label: 'FlowFuse Launcher v' + ffThemeSettings.launcherVersion,
116
- onselect: function () {
117
- // do nothing
118
- }
119
- })
120
- }
121
- interceptLogOutClick()
122
- interceptLogoClick(projectURL)
123
- })
124
- })
125
- })()
@@ -1,125 +0,0 @@
1
- (function () {
2
- /* global RED, $ */
3
- // monitorInsertion: derived from https://github.com/naugtur/insertionQuery/blob/master/insQ.min.js (MIT License Copyright (c) 2014-present Zbyszek Tenerowicz <naugtur@gmail.com>)
4
- // eslint-disable-next-line
5
- document.head = (document.head || document.getElementsByTagName('head')[0]);
6
- // eslint-disable-next-line
7
- const monitorInsertion = (function () { 'use strict'; let m = 100; let t = !1; let u = 'animationName'; let d = ''; const n = 'Webkit Moz O ms Khtml'.split(' '); let e = ''; const i = document.createElement('div'); const s = { strictlyNew: !0, timeout: 20, addImportant: !1 }; if (i.style.animationName && (t = !0), !1 === t) for (let o = 0; o < n.length; o++) if (void 0 !== i.style[n[o] + 'AnimationName']) { e = n[o], u = e + 'AnimationName', d = '-' + e.toLowerCase() + '-', t = !0; break } function c (t) { return s.strictlyNew && !0 === t.QinsQ } function r (t, n) { function e (t) { t.animationName !== o && t[u] !== o || c(t.target) || n(t.target) } let i; var o = 'insQ_' + m++; const r = s.addImportant ? ' !important' : ''; (i = document.createElement('style')).innerHTML = '@' + d + 'keyframes ' + o + ' { from { outline: 1px solid transparent } to { outline: 0px solid transparent } }\n' + t + ' { animation-duration: 0.001s' + r + '; animation-name: ' + o + r + '; ' + d + 'animation-duration: 0.001s' + r + '; ' + d + 'animation-name: ' + o + r + '; } ', document.head.appendChild(i); const a = setTimeout(function () { document.addEventListener('animationstart', e, !1), document.addEventListener('MSAnimationStart', e, !1), document.addEventListener('webkitAnimationStart', e, !1) }, s.timeout); return { destroy: function () { clearTimeout(a), i && (document.head.removeChild(i), i = null), document.removeEventListener('animationstart', e), document.removeEventListener('MSAnimationStart', e), document.removeEventListener('webkitAnimationStart', e) } } } function a (t) { t.QinsQ = !0 } function f (t) { if (t) for (a(t), t = t.firstChild; t; t = t.nextSibling) void 0 !== t && t.nodeType === 1 && f(t) } function l (t, n) { let e; let i = []; const o = function () { clearTimeout(e), e = setTimeout(function () { i.forEach(f), n(i), i = [] }, 10) }; return r(t, function (t) { if (!c(t)) { a(t); const n = (function t (n) { return c(n.parentNode) || n.nodeName === 'BODY' ? n : t(n.parentNode) }(t)); i.indexOf(n) < 0 && i.push(n), o() } }) } function v (n) { return !(!t || !n.match(/[^{}]/)) && (s.strictlyNew && f(document.body), { every: function (t) { return r(n, t) }, summary: function (t) { return l(n, t) } }) } return v.config = function (t) { for (const n in t)t.hasOwnProperty(n) && (s[n] = t[n]) }, v }()); typeof module !== 'undefined' && void 0 !== module.exports && (module.exports = monitorInsertion)
8
- const context = {
9
- isEmbedded: window.parent !== window.self,
10
- shouldEmitInsteadOfRedirect: false
11
- }
12
- const navigateTo = (url) => {
13
- if (context.isEmbedded) {
14
- window.parent.postMessage({
15
- type: 'navigate',
16
- payload: url
17
- }, '*')
18
- } else {
19
- window.location = url
20
- }
21
- }
22
- const interceptLogoClick = (url) => {
23
- const logoAnchor = document.querySelector('.red-ui-header-logo a')
24
-
25
- if (logoAnchor) {
26
- logoAnchor.addEventListener(
27
- 'click',
28
- (e) => {
29
- e.preventDefault()
30
- navigateTo(url)
31
- })
32
- }
33
- }
34
- const interceptLogOutClick = (url) => {
35
- document.querySelector('#usermenu-item-logout')
36
- .addEventListener('click', (e) => {
37
- e.preventDefault()
38
- e.stopPropagation()
39
- if (context.shouldEmitInsteadOfRedirect) {
40
- window.parent.postMessage({
41
- type: 'logout'
42
- }, '*')
43
- }
44
- })
45
- }
46
-
47
- function changeFavicon (src) {
48
- const link = document.createElement('link')
49
- const oldLink = $('link[href="favicon.ico"]')[0] || $('#dynamic-favicon"]')[0]
50
- link.id = 'dynamic-favicon'
51
- link.rel = 'shortcut icon'
52
- link.href = src
53
- if (oldLink) {
54
- document.head.removeChild(oldLink)
55
- }
56
- document.head.appendChild(link)
57
- }
58
-
59
- function handleMessage (event) {
60
- if (event.data.type === 'prevent-redirect') {
61
- context.shouldEmitInsteadOfRedirect = event.data.payload
62
- }
63
- }
64
-
65
- if (context.isEmbedded) {
66
- window.parent.postMessage({ type: 'load', payload: true }, '*')
67
- window.addEventListener('message', handleMessage)
68
- }
69
-
70
- window.addEventListener('load', (_event) => {
71
- // set favicon
72
- const favicon32 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAADzUlEQVR4AcXB206UVxzG4d+71jcbBmYGpjJIg2iHiNYEJaSKrQe4QeMNcOaR11BP2wuwV2JvQSMkxFAiGk1Q8UCjAoJsAkgRZuZb/wJtk6YtuEN4Ht3o7f0B+AXoBhy7w4Bh4FoEXAe+Z3cJ6AauO+Ake+c7B0TsHe/YY4495thjjj0WsdvMMNaZsSHiC7MQwAwkXBSZT6eJ0ml8MmlyziK+ADODEPDJpNU0N1t9qUT94cPkWlvJNDaSzGbxqZTkHBE7yOIYeU/d/v2h2NVFc3c3De3tShcKOO/Ffynic5lhIRDV1FjhyBFr6emh+dQpMk1NTs7xPhGfyEJgQyqft6auLjt48SL7OjqUyGTER4j4CBYCmOGiyOpaWqz59GlrPXdO9W1tcomEeB8z4nLZqqurhEoFC4HI4pgtSWyQc/h02moKBWtob2f/yZNW7OxUTWOjl8R24nLZlicnbe7RI+bHxmx5fFxri4vE5bIsBKJvr1wxzLAQYlsnkLx38t58MkmUTiuZy1Hb1ERtc7PSDQ3Ie8d2zHg3N2dTd++GicFBzY+NaW1hQRbHbJLYJBF1XL3q+FPEZ7I4tsUXL+xVf79NDA7q7fi4s2pVcg4k5D3/FrEDQqVibx48CC9v3tTUyIhW5+cd6+Qc8p7tOHZAqFRs8flz5p8+ZW1hQZghiQ8RsQOiTMYd6evj4IUL9np4OLy8dYu5x49VXVkRziGJrUTjAwO2KYQYEOvknJP35hMJ+XSaVD5PuqFByWwWeS+2kC4U9M3lyzrQ02Ozo6M2PjAQpu/d08rMjKxaFRKS2CSxQb9euhTMTJgZGyQEQjIknPf4VIp0Q4PlDh2i8cQJKx4/rrqWFrkoEtuwEOz36WmbffjQ3ty/r4Vnz1hbWFB1dZVQrWIhoBu9vQEQ2zHDzMAMOUe6ULB9HR124OxZip2dSmaz4j0sBKssL7O2tGSVt2+prKwQl8tEfAgJSfzt3fy8Xt2+rck7d6y+rc1az5+3r8+cobZYdEj8HzmnZC5HMpcT/+D7SqWfAPERJCHnsBC0MjOj6ZERvR4asnczMyFRV0cqn0feiw/g+0qlnwHxiSSxYW1xUbOjo25icJD5J08slMuWqKsjUVODnBNbiNghco4N5aUlTQwOanJoyGqLRQpHj9pXx46FfKmkTLFIMpuVT6Vw3oNzROw0CXkPZlqemmJ5clIv+/uJUilLZrOk8nlL5nJEmQw+kSACYsDxBUgC79kQl8tamZ1lZWZGmPGX2AHD7BJJyDnkPfIeeX/XAdeAO0Bg9wTgN+DHPwAIHJAeMJ00fgAAAABJRU5ErkJggg=='
73
- changeFavicon(favicon32)
74
-
75
- // monitor #red-ui-header-button-sidemenu & add main menu entries
76
- monitorInsertion('#red-ui-header-button-sidemenu').summary(function (_arrayOfInsertedNodes) {
77
- if (!RED) { return }
78
-
79
- RED.menu.addItem('red-ui-header-button-sidemenu', null) // menu seperator
80
- // add main menu item "About FlowFuse"
81
- RED.menu.addItem('red-ui-header-button-sidemenu', {
82
- id: 'usermenu-item-ffsite',
83
- label: 'About FlowFuse',
84
- onselect: function () {
85
- navigateTo('https://flowfuse.com/')
86
- }
87
- })
88
- // gather info from settings and page - prep for next 2 menu items
89
- const ffThemeSettings = RED.settings['forge']
90
- let projectURL = ''
91
- if (ffThemeSettings && ffThemeSettings.projectURL) {
92
- projectURL = ffThemeSettings.projectURL
93
- } else {
94
- const img = $('#red-ui-header > span > a > img')
95
- const ownerHref = img.parent().prop('href')
96
- // Test the URL is FlowFuse Project alike
97
- if (ownerHref && /http[s]*:\/\/.*\/project\/\w+-\w+-\w+-\w+-\w+.*/.test(ownerHref)) {
98
- projectURL = ownerHref
99
- }
100
- }
101
- // if projectURL is present, show link to project in main menu
102
- if (projectURL) {
103
- RED.menu.addItem('red-ui-header-button-sidemenu', {
104
- id: 'usermenu-item-ffmain',
105
- label: 'FlowFuse Application',
106
- onselect: function () {
107
- navigateTo(projectURL)
108
- }
109
- })
110
- }
111
- // if theme settings are present, add launcher version entry in main menu
112
- if (ffThemeSettings && ffThemeSettings.launcherVersion) {
113
- RED.menu.addItem('red-ui-header-button-sidemenu', {
114
- id: 'usermenu-item-fflv',
115
- label: 'FlowFuse Launcher v' + ffThemeSettings.launcherVersion,
116
- onselect: function () {
117
- // do nothing
118
- }
119
- })
120
- }
121
- interceptLogOutClick()
122
- interceptLogoClick(projectURL)
123
- })
124
- })
125
- })()