Skip to content

Commit

Permalink
Setup wizard: Refactor add-on selection & Add descriptions for JS Scr…
Browse files Browse the repository at this point in the history
…ipting & Astro (#2556)

- Move the selection of additional add-ons into the addons-setup-wizard.vue component.
- Add description texts for suggested add-ons JS Scripting and Astro.

---------

Signed-off-by: Florian Hotze <[email protected]>
  • Loading branch information
florian-h05 authored May 1, 2024
1 parent faa4018 commit 14a6c9e
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 94 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
"setupwizard.persistence.title": "Choose Persistence Add-ons",
"setupwizard.persistence.header1": "openHAB relies on persistence add-ons to store and retrieve historic states.",
"setupwizard.persistence.header2": "Select persistence add-ons to match the functionality you require.",
"setupwizard.addon.automation-jsscripting.line1": "Allows JavaScript (ECMAScript) to be used as a script language within automation languages.",
"setupwizard.addon.automation-jsscripting.line2": "Recommended as Blockly depends on it.",
"setupwizard.addon.binding-astro.line1": "Calculates many positional and DateTime values for sun and moon as well as sun radiation levels.",
"setupwizard.addon.binding-astro.line2": "Useful for automation.",
"setupwizard.addon.persistence-rrd4j.line1": "Stores state of numerical items. Does not grow in size by storing historical data on different levels of granularity.",
"setupwizard.addon.persistence-rrd4j.line2": "Recommended if you want to have charts of the historic state of numerical items.",
"setupwizard.addon.persistence-mapdb.line1": "Always stores the last state of an item. Allows restoring the state of all items on openHAB startup.",
Expand Down
158 changes: 117 additions & 41 deletions bundles/org.openhab.ui/web/src/components/addons/addons-setup-wizard.vue
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
<template>
<f7-list media-list>
<f7-list-item v-for="addon in addons" :key="addon.uid" class="addons-setup-wizard">
<f7-block class="addon display-flex flex-direction-column">
<f7-row no-gap class="item-title">
<f7-col width="10">
<f7-checkbox :checked="selectedAddon(addon)" :disabled="addon.installed" @change="changeAddonSelection(addon, $event)" />
</f7-col>
<f7-col width="70">
{{ addon.label }}
</f7-col>
<f7-col v-if="addon.link" width="20" style="text-align: right">
<f7-link icon-f7="doc_text_search" :external="true" color="gray" target="_blank" :href="addon.link" />
</f7-col>
</f7-row>
<f7-row no-gap style="margin-top: 0.5rem; margin-bottom: 0">
<div class="addon-description">
<addon-logo class="logo-square" :addon="addon" size="54" />
<span class="text" v-html="description(addon)" />
</div>
</f7-row>
</f7-block>
</f7-list-item>/>
</f7-list>
<div style="width: 100%">
<f7-row v-if="enableAddonSelection">
<f7-col width="100">
<f7-button ref="selectAddons" large icon-f7="bag_fill_badge_plus" icon-size="24" @click="selectAddons" :text="$t('setupwizard.addons.selectAddons')" />
</f7-col>
</f7-row>
<f7-list media-list>
<f7-list-item v-for="addon in shownAddons" :key="addon.uid" class="addons-setup-wizard">
<f7-block class="addon display-flex flex-direction-column">
<f7-row no-gap>
<div style="width: 100%">
<f7-checkbox style="margin-right: 0.5rem" :checked="selectedAddon(addon)" :disabled="addon.installed" @change="toggleAddonSelection(addon, $event)" />
{{ addon.label }}
<f7-link style="float: right" icon-f7="doc_text_search" :external="true" color="gray" target="_blank" :href="addon.link" />
</div>
</f7-row>
<f7-row no-gap style="margin-top: 0.5rem; margin-bottom: 0">
<div class="addon-description">
<addon-logo class="logo-square" :addon="addon" size="54" />
<span class="text" v-html="addonDescription(addon)" />
</div>
</f7-row>
</f7-block>
</f7-list-item>/>
</f7-list>
</div>
</template>

<style lang="stylus">
Expand All @@ -38,7 +41,7 @@
border-radius 10%
width 64px
height 64px
margin-top 6.5px
margin-top 5.5px
display flex
justify-content center
align-items center
Expand All @@ -57,46 +60,119 @@ import AddonLogo from '@/components/addons/addon-logo.vue'
import { loadLocaleMessages } from '@/js/i18n'
export default {
model: {
prop: 'selected',
event: 'update'
},
props: ['addons', 'selectedAddons'],
props: ['addons', 'preSelectedAddons', 'enableAddonSelection'],
emits: ['update'],
components: {
AddonLogo
},
data () {
return {
selected: []
shownAddons: [],
selectedAddons: []
}
},
i18n: {
messages: loadLocaleMessages(require.context('@/assets/i18n/setup-wizard'))
},
methods: {
/**
* Whether the given add-on is currently selected.
* @param addon
* @returns {boolean}
*/
selectedAddon (addon) {
if (typeof this.selectedAddons === 'undefined') return true
return this.selectedAddons.includes(addon)
},
changeAddonSelection (addon, event) {
if (event.target.checked) {
this.$set(this, 'selected', [...new Set(this.selected.concat(addon))])
} else {
this.$set(this, 'selected', this.selected.filter(a => (a.uid !== addon.uid)))
}
this.$emit('update', this.selected)
/**
* Whether the given add-on is pre-selected.
* @param addon
* @returns {boolean}
*/
preSelectedAddon (addon) {
return this.preSelectedAddons.includes(addon)
},
description (addon) {
/**
* Returns the add-on description.
* @param addon
* @returns {string}
*/
addonDescription (addon) {
const line1 = this.$t('setupwizard.addon.' + addon.uid + '.line1')
const line2 = this.$t('setupwizard.addon.' + addon.uid + '.line2')
const hasLine1 = (line1 !== 'setupwizard.addon.' + addon.uid + '.line1')
const hasLine2 = (line2 !== 'setupwizard.addon.' + addon.uid + '.line2')
let descr = (hasLine1 ? line1 : '') + (hasLine2 ? ('<br>' + line2) : '')
const descr = (hasLine1 ? line1 : '') + (hasLine2 ? ('<br>' + line2) : '')
return descr || addon.description || (addon.uid + '<br>' + addon.version)
},
/**
* Toggles the selection of a single add-on.
* To be called by the change event of the <code>f7-checkbox</code> component.
* @param addon
* @param event
*/
toggleAddonSelection (addon, event) {
if (event.target.checked) {
this.$set(this, 'selectedAddons', [...new Set(this.selectedAddons.concat(addon))])
} else {
this.$set(this, 'selectedAddons', this.selectedAddons.filter(a => (a.uid !== addon.uid)))
}
this.$emit('update', this.selectedAddons)
},
/**
* Opens the add-on selection popup.
*/
selectAddons () {
if (this.autocompleteAddons) {
this.autocompleteAddons.value = this.selectedAddons.map(a => a.label)
this.autocompleteAddons.open()
}
},
/**
* Updates the list of selected add-ons.
* To be called by change event of the selection popup.
* @param newSelected
*/
updateAddonSelection (newSelected) {
this.selectedAddons = newSelected
this.$emit('update', this.selectedAddons)
}
},
mounted () {
this.selected = this.addons
// Update the list of shown and selected add-ons with the pre-selected add-ons.
// Exclude add-ons that are in the list of pre-selected add-ons, but are not meant to be shown here (usually because these add-ons are handled in a separate step).
if (Array.isArray(this.preSelectedAddons)) {
this.shownAddons = this.selectedAddons = this.preSelectedAddons.filter(a => this.addons.includes(a))
}
// Initialize the autocomplete, which provides the add-on selection popup, if add-on selection has been enabled.
if (!this.enableAddonSelection) return
const self = this
this.autocompleteAddons = this.$f7.autocomplete.create({
openIn: 'popup',
pageTitle: self.$t('setupwizard.addons.selectAddons'),
searchbarPlaceholder: self.$t('setupwizard.addons.selectAddons.placeholder'),
openerEl: self.$refs.selectAddons,
multiple: true,
requestSourceOnOpen: true,
source: (query, render) => {
// Exclude installed and pre-selected add-ons from the selection popup.
if (query.length === 0) {
render(self.addons.filter(a => !a.installed && !self.preSelectedAddon(a)).map((a) => a.label))
} else {
render(self.addons
.filter(a => (!a.installed && !self.preSelectedAddon(a) && (a.label.toLowerCase().indexOf(query.toLowerCase()) >= 0 || a.uid.toLowerCase().indexOf(query.toLowerCase()) >= 0)))
.map(a => a.label))
}
},
on: {
change (value) {
const selected = value.map(label => self.addons.find(a => (a.label === label)))
// If we added addons, keep them visible on the main list, even if we deselect them again later.
self.shownAddons = [...new Set(self.selectedAddons.concat(selected))]
self.updateAddonSelection(selected)
}
}
})
}
}
</script>
80 changes: 27 additions & 53 deletions bundles/org.openhab.ui/web/src/pages/wizards/setup-wizard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,10 @@
<f7-block strong>
{{ $t('setupwizard.persistence.header1') }} {{ $t('setupwizard.persistence.header2') }}
</f7-block>
<f7-block style="margin-top: 0em, margin-bottom: 2em">
<addons-setup-wizard v-if="recommendedAddonsByType('persistence').length"
<f7-block style="margin-top: 0; margin-bottom: 2em">
<addons-setup-wizard v-if="addonsReady && recommendedAddonsByType('persistence').length"
:addons="recommendedAddonsByType('persistence')"
:preSelectedAddons="selectedAddons"
@update="updateAddonSelection(recommendedAddonsByType('persistence'), $event)" />
<f7-block-footer class="margin-bottom">
<small v-t="'setupwizard.persistence.footer'" />
Expand Down Expand Up @@ -154,15 +155,11 @@
<a class="text-color-blue external" target="_blank" href="https://www.openhab.org/addons/" v-t="'setupwizard.addons.browseAddonsOnWebsite'" />
</f7-block>
<f7-block class="padding">
<f7-row>
<f7-col width="100">
<f7-button ref="selectAddons" large icon-f7="bag_fill_badge_plus" icon-size="24" @click="selectAddons" :text="$t('setupwizard.addons.selectAddons')" />
</f7-col>
</f7-row>
<addons-setup-wizard v-if="addonSelectionList.length"
:addons="addonSelectionList"
:selectedAddons="toInstallAddons.filter(a => addonSelectionList.includes(a))"
@update="updateAddonSelection(addonSelectionList, $event)" />
<addons-setup-wizard v-if="addonsReady"
:enableAddonSelection="true"
:addons="mainAddons"
:preSelectedAddons="selectedAddons"
@update="updateAddonSelection(mainAddonSelection, $event)" />
<f7-block-footer class="margin-bottom">
<small v-t="'setupwizard.addons.footer'" />
</f7-block-footer>
Expand Down Expand Up @@ -262,6 +259,7 @@ export default {
data () {
return {
i18nReady: false,
addonsReady: false,
availableLanguages: null,
availableRegions: null,
availableTimezones: null,
Expand Down Expand Up @@ -294,16 +292,19 @@ export default {
if (!this.region) return this.language
return this.language + '-' + this.region.toLowerCase()
},
selectableAddons () {
// addons that can be selected in wizard main selection step, with recommended or suggested ones first in list
return this.selectedAddons
.concat(this.addons.filter(a => !this.selectedAddons.includes(a)))
.filter(a => (!a.installed && !this.preSelectedAddon(a)))
/**
* Add-ons that can be selected in the main add-on selection step, with add-on types handled before the main selection step excluded.
* @returns {*[]}
*/
mainAddons () {
return this.addons.filter(a => !this.preSelectingAddonTypes.includes(a.type))
},
addonSelectionList () {
const addons = this.selectedAddons
.concat(this.toInstallAddons)
.filter(a => (!a.installed && !this.preSelectedAddon(a)))
/**
* Add-ons that are currently selected in the main add-on selection step, with installed and pre-selected add-ons excluded.
* @returns {*[]}
*/
mainAddonSelection () {
const addons = this.selectedAddons.concat(this.toInstallAddons).filter(a => (!a.installed && !this.preSelectedAddon(a)))
return [...new Set(addons)]
}
},
Expand Down Expand Up @@ -390,14 +391,10 @@ export default {
return this.addons.filter(a => ((a.type === type) && this.recommendedAddons.includes(a.uid)))
},
updateAddonSelection (oldSelected, newSelected) {
console.debug('Updating add-on selection:', oldSelected.map(a => a.uid), newSelected.map(a => a.uid))
const addons = this.toInstallAddons.filter(a => !oldSelected.includes(a)).concat(newSelected)
this.$set(this, 'toInstallAddons', [...new Set(addons)])
},
selectAddons () {
if (this.autocompleteAddons) {
this.autocompleteAddons.value = this.toInstallAddons.filter(a => !this.preSelectedAddon(a)).map(a => a.label)
this.autocompleteAddons.open()
}
console.log('Add-ons to install:', this.toInstallAddons.map(a => a.uid))
},
installAddons () {
const self = this
Expand Down Expand Up @@ -462,7 +459,7 @@ export default {
installNextAddon()
},
skipAddons () {
this.updateAddonSelection(this.selectableAddons, [])
this.updateAddonSelection(this.mainAddons, [])
if (this.toInstallAddons.filter(a => !a.installed).length) {
this.installAddons()
return
Expand All @@ -484,7 +481,7 @@ export default {
pageBeforeIn () {
this.$f7.panel.get('left').disableVisibleBreakpoint()
},
pageBeforeOut (e, page) {
pageBeforeOut () {
this.$f7.panel.get('left').enableVisibleBreakpoint()
// create the overview page to prevent this setup wizard from being launched again
this.$oh.api.post('/rest/ui/components/ui:page', {
Expand Down Expand Up @@ -521,35 +518,12 @@ export default {
}
})
this.$oh.api.get('/rest/addons/suggestions').then((suggestedAddons) => {
let suggestions = suggestedAddons.flatMap(s => s.id)
const suggestions = suggestedAddons.flatMap(s => s.id)
this.$oh.api.get('/rest/addons').then(data => {
this.addons = data.sort((a, b) => a.label.toUpperCase().localeCompare(b.label.toUpperCase()))
this.selectedAddons = this.addons.filter(a => (this.recommendedAddons.includes(a.uid) || suggestions.includes(a.id)))
.sort((a, b) => a.uid.toUpperCase().localeCompare(b.uid.toUpperCase()))
const self = this
this.autocompleteAddons = this.$f7.autocomplete.create({
openIn: 'popup',
pageTitle: self.$t('setupwizard.addons.selectAddons'),
searchbarPlaceholder: self.$t('setupwizard.addons.selectAddons.placeholder'),
openerEl: self.$refs.selectAddons,
multiple: true,
requestSourceOnOpen: true,
source: (query, render) => {
if (query.length === 0) {
render(self.selectableAddons.map((a) => a.label))
} else {
render(self.selectableAddons.filter(a => (a.label.toLowerCase().indexOf(query.toLowerCase()) >= 0 || a.uid.toLowerCase().indexOf(query.toLowerCase()) >= 0)).map(a => a.label))
}
},
on: {
change (value) {
const selected = value.map(label => self.addons.find(a => (a.label === label)))
// if we added addons, keep it visible on the main list, even if we deselect again later
self.selectedAddons = [...new Set(self.selectedAddons.concat(selected))]
self.updateAddonSelection(self.selectableAddons, selected)
}
}
})
this.addonsReady = true
})
})
}
Expand Down

0 comments on commit 14a6c9e

Please sign in to comment.