Skip to content

Commit

Permalink
Canvas layout: Add interactive SVG support (openhab#2787)
Browse files Browse the repository at this point in the history
This is major contribution to the fixed canvas layout which allows
an SVG background to become an active part of the page. Through this
feature the SVG itself can become an active component of the page.

Keep in mind: it is not a replacement for widgets ... widgets can and
should still be used but it allows the background itself to be as
interactive as a widget.

---------

Also-by: Florian Hotze <[email protected]>
Signed-off-by: Stefan Höhn <[email protected]>
  • Loading branch information
stefan-hoehn authored Nov 9, 2024
1 parent 3d0ac0b commit 3dfe023
Show file tree
Hide file tree
Showing 7 changed files with 470 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ export function OhCanvasLayoutDefinition () {
pt('imageUrl', 'Image URL', 'The URL of the image to display as background').c('url'),
pt('imageSrcSet', 'Image Source Set', 'The src-set attribute of background image element to take into account multiple device resolutions. For example: "/static/floorplans/floor-0.jpg, /static/floorplans/[email protected] 2x"')
])
.paramGroup(pg('svgEmbedding', 'SVG Embedding'), [
pb('embedSvg', 'Embed SVG', 'Embed SVG image directly into the page (default false)'),
pb('embedSvgFlashing', 'Embed SVG Flashing in Run-Mode', 'Flashes SVG elements on hovering in run-mode as well (default false)')
])
.paramGroup(pg('appearance', 'Appearance'), [
pb('hideNavbar', 'Hide Navigation bar', 'Hide navigation bar on top when page is displayed (You can additionally hide the sidebar using its pin icon) (default false)')
.v((value, configuration, configDescription, parameters) => { return configuration.layoutType === 'fixed' }),
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
:icon-f7="grid.enable ? 'circle_grid_3x3_fill' : 'scircle_grid_3x3'"
style="margin-left: auto"
text="Grid" />
<f7-menu-item
v-if="config.embedSvg"
@click="flashEmbeddedSvgComponents()"
icon-f7="bolt" />
<f7-menu-item
dropdown
icon-f7="rectangle_3_offgrid">
Expand Down Expand Up @@ -91,14 +95,18 @@
}">
<div
v-if="config.imageUrl || config.imageSrcSet"
v-show="!config.embedSvg || embeddedSvgReady"
ref="canvasBackground"
style="
height: inherit;
width: inherit;
position: absolute;
top: 0;
left: 0;
overflow: hidden;
">
<img
v-if="!config.embedSvg"
class="oh-canvas-background disable-user-drag"
:src="config.imageUrl"
:srcset="config.imageSrcSet">
Expand Down Expand Up @@ -162,11 +170,12 @@

<script>
import mixin from '../widget-mixin'
import embeddedSvgMixin from '@/components/widgets/layout/oh-canvas-embedded-svg-mixin'
import OhCanvasLayer from './oh-canvas-layer'
import { OhCanvasLayoutDefinition } from '@/assets/definitions/widgets/layout'
export default {
mixins: [mixin],
mixins: [mixin, embeddedSvgMixin],
widget: OhCanvasLayoutDefinition,
components: {
OhCanvasLayer
Expand Down Expand Up @@ -215,12 +224,30 @@ export default {
this.$fullscreen.support = true
this.canvasLayoutStyle()
this.computeLayout()
if (this.config.embedSvg) {
this.embedSvg().then(() => {
this.subscribeEmbeddedSvgListeners()
this.setupEmbeddedSvgStateTracking()
this.embeddedSvgReady = true
})
}
},
mounted () {
// Chrome reports a wrong size in fullscreen, store initial resolution and use non-dynamically.
this.windowWidth = window.screen.width
this.windowHeight = window.screen.height
},
beforeDestroy () {
if (!this.context.editmode) {
window.removeEventListener('resize', this.setDimensions)
}
if (this.config.embedSvg && this.embeddedSvgReady) {
this.embeddedSvgReady = false
this.unsubscribeEmbeddedSvgListeners()
this.unsubscribeEmbeddedSvgStateTracking()
}
},
methods: {
isRetina () {
return window.devicePixelRatio > 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
<oh-grid-layout :context="context" />
</template>
<template v-else-if="config.layoutType === 'fixed' && config.fixedType === 'canvas'">
<oh-canvas-layout :context="context" />
<oh-canvas-layout :context="context" @action="$emit('action', $event)" />
</template>
</div>
</template>
Expand All @@ -49,6 +49,7 @@ import OhGridLayout from './oh-grid-layout.vue'
import OhCanvasLayout from './oh-canvas-layout.vue'
export default {
emits: ['action'],
mixins: [mixin],
components: {
OhBlock,
Expand Down
12 changes: 12 additions & 0 deletions bundles/org.openhab.ui/web/src/js/openhab/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,17 @@ import diacritic from 'diacritic'
export default {
normalizeLabel: (label) => {
return diacritic.clean(label.normalize('NFKD')).replace(/\s+/g, '_').replace(/[^0-9a-z_]/gi, '')
},
/**
* Convert a color from HSB to RGB.
*
* @param h hue value (0-360)
* @param s saturation value (0-1)
* @param b brightness value (0-1)
* @returns {number[]} [r, g, b] array
*/
hsbToRgb (h, s, b) {
const hsl = this.$f7.utils.colorHsbToHsl(h, s, b)
return this.$f7.utils.colorHslToRgb(hsl[0], hsl[1], hsl[2])
}
}
5 changes: 3 additions & 2 deletions bundles/org.openhab.ui/web/src/pages/page/page-view.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
</f7-tab>
</f7-tabs>

<component :is="page.component" v-else-if="page && visibleToCurrentUser" :context="context" @command="onCommand" />
<component :is="page.component" v-else-if="page && visibleToCurrentUser" :context="context" @command="onCommand" @action="performAction($event.ev, $event.prefix, $event.config, $event.context)" />

<empty-state-placeholder v-if="!visibleToCurrentUser" icon="multiply_circle_fill" title="page.unavailable.title" text="page.unavailable.text" />
</f7-page>
Expand All @@ -49,9 +49,10 @@
<script>
import OhLayoutPage from '@/components/widgets/layout/oh-layout-page.vue'
import WidgetExpressionMixin from '@/components/widgets/widget-expression-mixin'
import { actionsMixin } from '@/components/widgets/widget-actions'
export default {
mixins: [WidgetExpressionMixin],
mixins: [WidgetExpressionMixin, actionsMixin],
components: {
'oh-layout-page': OhLayoutPage,
'empty-state-placeholder': () => import('@/components/empty-state-placeholder.vue'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
</f7-block>

<oh-layout-page class="layout-page" v-if="ready" :context="context" :key="pageKey" :style="page.config.style"
@action="performAction($event.ev, $event.prefix, $event.config, $event.context)"
@add-block="addBlock"
@add-masonry="addMasonry"
@add-grid-item="addGridItem"
Expand All @@ -94,7 +95,7 @@
<editor v-if="currentTab === 'code'" :style="{ opacity: previewMode ? '0' : '' }" class="page-code-editor" mode="application/vnd.openhab.uicomponent+yaml?type=layout" :value="pageYaml" @input="onEditorInput" />
<!-- <pre class="yaml-message padding-horizontal" :class="[yamlError === 'OK' ? 'text-color-green' : 'text-color-red']">{{yamlError}}</pre> -->

<oh-layout-page class="layout-page" v-if="ready && previewMode" :context="context" :key="pageKey" :style="page.config.style" />
<oh-layout-page class="layout-page" v-if="ready && previewMode" :context="context" :key="pageKey" :style="page.config.style" @action="performAction($event.ev, $event.prefix, $event.config, $event.context)" />
</f7-tab>
</f7-tabs>
</f7-page>
Expand Down Expand Up @@ -130,10 +131,11 @@
</style>

<script>
import PageDesigner from '../pagedesigner-mixin'
import YAML from 'yaml'
import PageDesigner from '../pagedesigner-mixin'
import { actionsMixin } from '@/components/widgets/widget-actions'
import OhLayoutPage from '@/components/widgets/layout/oh-layout-page.vue'
import * as SystemWidgets from '@/components/widgets/system'
import * as StandardWidgets from '@/components/widgets/standard'
Expand All @@ -151,7 +153,7 @@ import itemDefaultCellComponent from '@/components/widgets/standard/cell/default
import { compareItems } from '@/components/widgets/widget-order'
export default {
mixins: [PageDesigner],
mixins: [PageDesigner, actionsMixin],
components: {
'editor': () => import(/* webpackChunkName: "script-editor" */ '@/components/config/controls/script-editor.vue'),
OhLayoutPage,
Expand All @@ -178,6 +180,12 @@ export default {
fullscreen: this.$fullscreen.getState()
}
},
created () {
this.$f7.on('svgOnClickConfigUpdate', this.onSvgOnClickConfigUpdate)
},
beforeDestroy () {
this.$f7.off('svgOnClickConfigUpdate', this.onSvgOnClickConfigUpdate)
},
methods: {
addWidget (component, widgetType, parentContext, slot) {
const isList = component.component.indexOf('oh-list') === 0
Expand Down Expand Up @@ -388,6 +396,12 @@ export default {
this.forceUpdate()
}
})
},
onSvgOnClickConfigUpdate (event) {
if (!this.page.config.embeddedSvgActions) {
this.$set(this.page.config, 'embeddedSvgActions', {})
}
this.page.config.embeddedSvgActions[event.id] = event.config
}
}
}
Expand Down

0 comments on commit 3dfe023

Please sign in to comment.