Skip to content

Commit

Permalink
Replace Add-on Management UI with Add-on Store (#1158)
Browse files Browse the repository at this point in the history
Depends on openhab/openhab-core#2405.

This replaces the current section of the UI for managing
add-ons with a completely new "store-like" interface.
External data, like logos and detailed descriptions for
distribution add-ons are fetched from well-known locations
on the website or GitHub to provide a "store" look & feel.

The add-on store is orgnanized around 4 categories: bindings,
automation, UI, and others, with a separate search tab.
Each category is splitted into subsections. The availability
of these sections depend on the add-on services that are
registered. The sections currently supports add-ons provided
by the "karaf" & "marketplace" services, as provided by
openhab/openhab-core#2405.
Other add-on services should be supported as well.

Signed-off-by: Yannick Schaus <github@schaus.net>
  • Loading branch information
ghys committed Sep 24, 2021
1 parent 738c399 commit 11b9bbe
Show file tree
Hide file tree
Showing 18 changed files with 1,437 additions and 79 deletions.
19 changes: 19 additions & 0 deletions bundles/org.openhab.ui/web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions bundles/org.openhab.ui/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@
"jest-serializer-vue": "^2.0.2",
"jest-transform-stub": "^2.0.0",
"jest-vue-preprocessor": "^1.7.1",
"marked": "^3.0.2",
"mini-css-extract-plugin": "^0.5.0",
"nearley-loader": "^2.0.0",
"optimize-css-assets-webpack-plugin": "^5.0.4",
Expand Down
77 changes: 77 additions & 0 deletions bundles/org.openhab.ui/web/src/assets/addon-store.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
export const AddonIcons = {
automation: 'wand_stars',
binding: 'circle_grid_hex_fill',
persistence: 'download_circle',
transformation: 'function',
misc: 'rectangle_3_offgrid',
ui: 'play_rectangle',
voice: 'chat_bubble_2_fill'
}

export const ContentTypes = {
'application/java-archive': 'Java Archive',
'application/vnd.openhab.bundle': 'OSGi Bundle',
'application/vnd.openhab.feature;type=karaf': 'Karaf Feature',
'application/vnd.openhab.feature;type=karfile': 'Karaf KAR Archive',
'application/vnd.openhab.ruletemplate': 'Rule Template',
'application/vnd.openhab.uicomponent;type=widget': 'UI Component - Widget'
}

export const Formats = {
'yaml_content': 'Inline YAML Code',
'json_content': 'Inline JSON Code',
'yaml_download_url': 'Linked YAML File',
'json_download_url': 'Linked JSON File',
'jar_download_url': 'Linked JAR file',
'kar_download_url': 'Linked KAR file',
'karaf': 'Karaf'
}

export const AddonStoreTabShortcuts = [
{
id: 'bindings',
label: 'Bindings',
icon: 'circle_grid_hex',
subtitle: 'Connect and control hardware and online services'
},
{
id: 'automation',
label: 'Automation',
icon: 'sparkles',
subtitle: 'Scripting languages, templates and module types'
},
{
id: 'ui',
label: 'User Interfaces',
icon: 'play_rectangle',
subtitle: 'Community widgets & alternative frontends'
},
{
id: 'other',
label: 'Other Add-ons',
icon: 'ellipsis',
subtitle: 'System integrations, persistence, voice & more'
}
]

export function compareAddons (a1, a2) {
if (a1.installed && !a2.installed) return -1
if (a2.installed && !a1.installed) return 1
if (a1.verifiedAuthor && !a2.verifiedAuthor) return -1
if (a2.verifiedAuthor && !a1.verifiedAuthor) return 1
if (a2.properties && a2.properties) {
if (a1.properties.like_count >= 0 && a2.properties.like_count >= 0 &&
a1.properties.like_count !== a2.properties.like_count) {
return (a1.properties.like_count > a2.properties.like_count) ? -1 : 1
}

if (a1.properties.views >= 0 && a2.properties.views >= 0 &&
a1.properties.views !== a2.properties.views) {
return (a1.properties.views > a2.properties.views) ? -1 : 1
}
}

const nameOrId1 = a1.label || a1.id
const nameOrId2 = a2.label || a2.name
return nameOrId1.localeCompare(nameOrId2)
}
154 changes: 154 additions & 0 deletions bundles/org.openhab.ui/web/src/components/addons/addon-card.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
<template>
<f7-link
v-if="addon"
class="addon-card padding-right-half" :href="addon.id">
<div class="addon-card-headline">
<div>{{ headline || autoHeadline }}</div>
</div>
<div class="addon-card-title">
<div class="addon-card-title-after">
<f7-preloader v-if="addon.pending" color="blue" />
<f7-button v-else-if="addon.installed" class="install-button prevent-active-state-propagation" text="Remove" color="red" round small @click="buttonClicked" />
<f7-button v-else class="install-button prevent-active-state-propagation" :text="installActionText || 'Install'" color="blue" round small @click="buttonClicked" />
</div>
<div class="addon-card-label">
{{ addon.label }}
</div>
<div v-if="addon.verifiedAuthor" class="addon-card-subtitle">
{{ addon.author }}
<f7-icon v-if="addon.verifiedAuthor" size="15" :color="$f7.data.themeOptions.dark === 'dark' ? 'white' : 'blue'" f7="checkmark_seal_fill" style="margin-top: -3px;" />
</div>
<div v-else-if="addon.properties && addon.properties.views" class="addon-card-subtitle">
<addon-stats-line :addon="addon" :iconSize="15" />
</div>
</div>
<div class="logo-square">
<f7-icon v-show="!logoLoaded" size="150" color="gray" :f7="addonIcon" class="card-default-icon" />
<img v-if="!logoError" class="lazy logo" :style="{ visibility: logoLoaded ? 'visible': 'hidden' }" ref="logo" :data-src="imageUrl">
</div>
</f7-link>
</template>

<style lang="stylus">
.addon-card
display flex
flex-direction column
align-items flex-start
align-content flex-end
color inherit
scroll-snap-align center center
padding 5px 0px 0px
position relative
margin-bottom 1rem
.install-button
// --f7-button-bg-color var(--f7-color-gray)
--f7-button-text-transform uppercase
--f7-button-bg-color var(--f7-list-item-border-color)
.addon-card-headline
text-transform uppercase
color var(--f7-theme-color)
font-size 11px
font-weight 500
.addon-card-title
height 3.4rem
width 100%
/* display flex
justify-content space-between
box-sizing border-box */
font-size var(--f7-timeline-item-title-font-size) // 21px
font-weight var(--f7-list-media-item-title-font-weight)
line-height 1.75
.addon-card-label
text-overflow ellipsis
overflow hidden
white-space nowrap
width calc(100% - 5rem)
.addon-card-title-after
.preloader-inner .preloader-inner-left, .preloader-inner .preloader-inner-right, .preloader-inner .preloader-inner-line
margin-left inherit !important
display flex
float right
align-self top
min-width 70px
justify-content center
height var(--f7-button-small-height)
--f7-preloader-size var(--f7-button-small-height)
.addon-card-subtitle
color var(--f7-list-item-after-text-color)
font-size var(--f7-list-item-subtitle-font-size) // 21px
font-weight var(--f7-list-item-subtitle-font-weight)
line-height 1.75
&:after
content ' '
display inline-block
.logo-square
position relative
background #fff
width 100%
margin-top 5px
// height 220px
border 1px solid var(--f7-list-item-border-color)
border-radius 5px
display flex
justify-content center
align-items center
&:after
content ' '
display block
padding-bottom 100%
.card-default-icon
opacity 0.2
position absolute
.logo
position absolute
top 3px
left 3px
width calc(100% - 6px)
height calc(100% - 6px)
object-fit contain
</style>

<script>
import { AddonIcons } from '@/assets/addon-store'
import AddonStatsLine from './addon-stats-line.vue'
export default {
props: ['addon', 'headline', 'installActionText'],
components: {
AddonStatsLine
},
data () {
return {
logoLoaded: false,
logoError: false,
addonIcon: null
}
},
computed: {
autoHeadline () {
if (this.addon.properties && this.addon.properties.like_count && this.addon.properties.like_count >= 20) return 'Top'
if (this.addon.properties && this.addon.properties.views && this.addon.properties.views >= 1000) return 'Popular'
if (this.addon.properties && this.addon.properties.posts_count && this.addon.properties.posts_count >= 15) return 'Hot'
return ''
},
imageUrl () {
if (this.addon.imageLink) return this.addon.imageLink.replace(/^\/\//, 'https://')
return 'https://www.openhab.org/logos/' + this.addon.id.substring(this.addon.id.indexOf('-') + 1) + '.png'
}
},
mounted () {
this.addonIcon = AddonIcons[this.addon.type]
this.$$(this.$refs.logo).once('lazy:loaded', (e) => {
this.logoLoaded = true
})
this.$$(this.$refs.logo).once('lazy:error', (e) => {
this.logoError = true
})
},
methods: {
buttonClicked () {
this.$emit('addonButtonClick', this.addon)
}
}
}
</script>
Loading

0 comments on commit 11b9bbe

Please sign in to comment.