Skip to content

Commit

Permalink
Merge pull request #1377 from FlowFuse/1144-configurable-pwa-icon
Browse files Browse the repository at this point in the history
1144 configurable pwa icon
  • Loading branch information
gayanSandamal authored Oct 10, 2024
2 parents 5e7f47f + 5ed9c0d commit bbe5ba2
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 7 deletions.
1 change: 1 addition & 0 deletions docs/nodes/config/ui-base.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
description: Configure the base UI settings of Node-RED Dashboard 2.0 to tailor the dashboard environment to your needs.
props:
Path: The endpoint proceeding the host of Node-RED where your UI will be accessible
App Icon: Allows you to set a custom icon for your application. Provide the URL to the App Icon, which will be displayed as the app icon and in the browser tab.
Include Page Path in Label: The side navigation lists all available Pages for the Dashboard. By default, this will just show the page name, but this option allows you to also show the page's path.
Side Navigation Style: The style the side navigation menu should use (default, fixed, icon, temporary, none)
---
Expand Down
7 changes: 7 additions & 0 deletions nodes/config/locales/en-US/ui_base.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,11 @@
</ul>
</dd>
</dl>
<dt>
App Icon
<span class="property-type">URL</span>
</dt>
<dd>
You can provide a custom URL for your application's icon, which will be displayed as the app icon and in the browser tab.
</dd>
</script>
1 change: 1 addition & 0 deletions nodes/config/locales/en-US/ui_base.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"label": {
"uiName": "UI Name",
"path": "Path",
"appIcon": "App Icon",
"category": "dashboard 2",
"dashboard2": "Dashboard 2.0",
"editSettings": "Edit Settings",
Expand Down
13 changes: 13 additions & 0 deletions nodes/config/ui_base.html
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,9 @@
value: '/dashboard',
required: true
},
appIcon: {
value: ''
},
includeClientData: {
value: true
},
Expand Down Expand Up @@ -364,6 +367,10 @@
// update the jquery checkbox
$('#node-config-input-showPageTitle').prop('checked', true)
}

if (this.appIcon) {
$('#node-config-input-appIcon').val(this.appIcon)
}
},
onpaletteadd: function () {
// add the Dashboard 2.0 sidebar
Expand Down Expand Up @@ -391,6 +398,7 @@
icon: null,
color: null,
isSubflowInstance: false,
appIcon: node.appIcon,
node
}
if (hasProperty(node, 'group')) { item.group = node.group }
Expand Down Expand Up @@ -1988,6 +1996,11 @@
<input type="text" id="node-config-input-path" disabled>
<span style="display: block; margin-left: 105px; margin-top: 0px; font-style: italic; color: #bbb; font-size: 8pt;">This option is currently disabled and still in-development.</span>
</div>
<div class="form-row">
<label for="node-config-input-appIcon"><i class="fa fa-cube"></i> <span data-i18n="ui-base.label.appIcon"></label>
<input type="text" id="node-config-input-appIcon">
<span style="display: block; margin-left: 105px; margin-top: 0px; font-style: italic; color: #bbb; font-size: 8pt;">Enter the url of your app icon here</span>
</div>
<div class="form-row" style="margin-bottom: 0;">
<label style="font-weight: 600; width: auto;" data-i18n="ui-base.label.header"></label>
</div>
Expand Down
22 changes: 22 additions & 0 deletions nodes/config/ui_base.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,28 @@ module.exports = function (RED) {
res.sendFile(path.join(__dirname, '../../dist/index.html'))
})

uiShared.app.get('/dashboard/manifest.webmanifest', (req, res) => {
const hasAppIcon = (config.appIcon && config.appIcon.trim() !== '')
const manifest = {
name: 'Node-RED Dashboard 2.0',
short_name: 'Dashboard',
start_url: './',
display: 'standalone',
background_color: '#ffffff',
lang: 'en',
scope: './',
description: 'Node-RED Dashboard 2.0',
theme_color: '#ffffff',
icons: [
{ src: hasAppIcon ? config.appIcon : 'pwa-64x64.png', sizes: '64x64', type: 'image/png' },
{ src: hasAppIcon ? config.appIcon : 'pwa-192x192.png', sizes: '192x192', type: 'image/png' },
{ src: hasAppIcon ? config.appIcon : 'pwa-512x512.png', sizes: '512x512', type: 'image/png' },
{ src: hasAppIcon ? config.appIcon : 'pwa-512x512.png', sizes: '512x512', type: 'image/png', purpose: 'maskable' }
]
}
return res.json(manifest)
})

uiShared.app.get(config.path + '/*', uiShared.httpMiddleware, (req, res) => {
res.sendFile(path.join(__dirname, '../../dist/index.html'))
})
Expand Down
2 changes: 1 addition & 1 deletion ui/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Node-RED Dashboard 2.0</title>
<meta name="description" content="Node-RED Dashboard 2.0">
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
<link href="data:image/x-icon;base64,AAABAAEAEBAQAAAAAAAoAQAAFgAAACgAAAAQAAAAIAAAAAEABAAAAAAAgAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAsC8qAP+EAACzh1cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAACAAAAAAAAACAAAAAAAAEiAAAAADAAAiAAAAAAMzAiAAAAAAAAMzAAAAAAAAAiMzMAAAAAAAADAzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA" rel="icon" type="image/x-icon">
<link rel="alternate icon" href="/favicon.ico" type="image/png" sizes="16x16">
<link rel="apple-touch-icon" href="/apple-touch-icon.png" sizes="180x180">
<link rel="manifest" crossorigin="use-credentials" href="./manifest.webmanifest">
Expand Down
47 changes: 45 additions & 2 deletions ui/src/layouts/Baseline.vue
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ function getContrast (bg) {
// http://www.w3.org/TR/AERT#color-contrast
const brightness = Math.round(((parseInt(bgRgb[0]) * 299) +
(parseInt(bgRgb[1]) * 587) +
(parseInt(bgRgb[2]) * 114)) / 1000)
(parseInt(bgRgb[1]) * 587) +
(parseInt(bgRgb[2]) * 114)) / 1000)
const textColor = (brightness > 125) ? '#000000' : '#ffffff'
return textColor
Expand Down Expand Up @@ -154,6 +154,9 @@ export default {
appBarStyle: function () {
return this.dashboard.titleBarStyle || 'default'
},
appIcon () {
return this.dashboard.appIcon
},
navigationStyle: function () {
const style = this.dashboard.navigationStyle
if (![null, 'default', 'fixed', 'icon', 'temporary', 'none'].includes(style)) {
Expand Down Expand Up @@ -184,6 +187,46 @@ export default {
this.rail = true
}
}
},
appIcon: {
immediate: true,
handler (url) {
// extract the file extension from the URL
const getFileTypeFromURL = (url) => {
const segments = url.split('.')
const extension = segments[segments.length - 1]
return extension.toLowerCase()
}
// The existing rel types in the index.html
const relTypes = ['icon', 'alternate icon', 'apple-touch-icon']
if (url) {
const fileType = getFileTypeFromURL(url)
relTypes.forEach((relType) => {
// iterate through the rel types and update the link tag
const link = document.querySelector(`link[rel="${relType}"]`)
if (link) {
// set the type and href attributes
link.setAttribute('type', `image/${fileType}`)
link.setAttribute('href', url)
}
})
} else {
relTypes.forEach((relType) => {
// iterate through the rel types and update the link tag
const link = document.querySelector(`link[rel="${relType}"]`)
if (relType === 'icon') {
link.setAttribute('type', 'image/x-icon')
link.setAttribute('href', '/dashboard/favicon.ico')
} else if (relType === 'alternate icon') {
link.setAttribute('type', 'image/svg+xml')
link.setAttribute('href', '/dashboard/favicon.svg')
} else if (relType === 'apple-touch-icon') {
link.setAttribute('type', 'image/png')
link.setAttribute('href', '/dashboard/apple-touch-icon.png')
}
})
}
}
}
},
mounted () {
Expand Down
5 changes: 1 addition & 4 deletions vite.config.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'
import { VitePWA } from 'vite-plugin-pwa'

import manifest from './manifest.json'

/**
* Vite is used to build the UI for the dashboard,
* is is not used for the nodes themselves.
Expand All @@ -24,7 +21,7 @@ export default defineConfig({
registerType: 'autoUpdate',
injectRegister: false,

manifest,
manifest: false,

injectManifest: {
maximumFileSizeToCacheInBytes: 3000000,
Expand Down

0 comments on commit bbe5ba2

Please sign in to comment.