Interactive SVG background: Allow processing whole SVG group for state (#2872)

Allows to flash and handle state on whole SVG groups without a proxy by
using the group's path elements instead, e.g. this whole group can be used directly

---------

Signed-off-by: Stefan Höhn <mail@stefanhoehn.com>
pull/2878/head
stefan-hoehn 2024-11-16 18:43:42 +01:00 committed by GitHub
parent e3daf94577
commit e1990669e0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 118 additions and 99 deletions

View File

@ -145,6 +145,22 @@ export default {
* @param {HTMLElement} el * @param {HTMLElement} el
*/ */
svgOnMouseOver (el) { svgOnMouseOver (el) {
function flashElement (el, fillColor) {
if (el && !el.flashing) {
const attributeName = (el.style.fill !== 'none') ? 'fill' : 'stroke'
const oldFill = el.style.getPropertyValue(attributeName)
const oldOpacity = el.style.opacity
el.style.setProperty(attributeName, fillColor)
el.style.opacity = 1
el.flashing = true
setTimeout(() => {
el.style.setProperty(attributeName, oldFill)
el.style.opacity = oldOpacity
el.flashing = false
}, 200)
}
}
if (this.context.editmode || (!this.context.editmode && this.config.embedSvgFlashing)) { if (this.context.editmode || (!this.context.editmode && this.config.embedSvgFlashing)) {
const tagName = el.tagName const tagName = el.tagName
// fill green if item config is available, red if config is still missing // fill green if item config is available, red if config is still missing
@ -160,19 +176,15 @@ export default {
el.style.setProperty(attributeName, oldFill) el.style.setProperty(attributeName, oldFill)
}, 200) }, 200)
} else { // groups cannot be filled, so we need to fill special element marked as "flash" } else { // groups cannot be filled, so we need to fill special element marked as "flash"
const flashElement = el.querySelector('[flash]') const elementToFlash = el.querySelector('[flash]')
if (flashElement && !flashElement.flashing) { if (elementToFlash) {
const attributeName = (flashElement.style.fill !== 'none') ? 'fill' : 'stroke' flashElement(elementToFlash, fillColor)
const oldFill = flashElement.style.getPropertyValue(attributeName) } else {
const oldOpacity = flashElement.style.opacity // let's try flashing all path elements in the group
flashElement.style.setProperty(attributeName, fillColor) const flashElements = el.querySelectorAll('path')
flashElement.style.opacity = 1 for (const path of flashElements) {
flashElement.flashing = true flashElement(path, fillColor)
setTimeout(() => { }
flashElement.style.setProperty(attributeName, oldFill)
flashElement.style.opacity = oldOpacity
flashElement.flashing = false
}, 200)
} }
} }
} }
@ -292,6 +304,92 @@ export default {
svgElement.innerHTML = state svgElement.innerHTML = state
} }
function processState (useProxy, element) {
if (state === 'ON' || stateType === 'HSB') {
if (useProxy && svgElementConfig.stateAsOpacity) { // we use the flash element
let opacity = (state === 'ON') ? 1 : 0
opacity = (svgElementConfig.invertStateOpacity) ? 1 - opacity : opacity
opacity = (opacity < svgElementConfig.stateMinOpacity) ? svgElementConfig.stateMinOpacity : opacity
element.style.opacity = opacity
} else {
element.oldFill = element.style.fill
element.style.fill = stateOnColorRgbStyle
}
if (svgElementConfig.stateOnAsStyleClass) {
if (svgElementConfig.stateOffAsStyleClass) { // if offStates are provided add OffStates
let offStatesArray = svgElementConfig.stateOffAsStyleClass.split(',')
for (const offState of offStatesArray) {
const elementClassInfo = offState.split(':')
const offStateElement = document.getElementById(elementClassInfo[0].trim())
if (offStateElement) {
offStateElement.classList.remove(elementClassInfo[1].trim())
} else {
console.warn(`Target element ${elementClassInfo[0].trim()} not found. Please check style stateOffAsStyleClass expression of ${element.id}`)
}
}
}
let onStatesArray = svgElementConfig.stateOnAsStyleClass.split(',')
for (const onState of onStatesArray) {
const elementClassInfo = onState.split(':')
const onStateElement = document.getElementById(elementClassInfo[0].trim())
if (onStateElement) {
onStateElement.classList.add(elementClassInfo[1].trim())
} else {
console.warn(`Target element ${elementClassInfo[0].trim()} not found. Please check style stateOnAsStyleClass expression of ${element.id}`)
}
}
}
} else if (state === 'OFF') {
const updateColor = (stateOffColorRgbStyle) || ((element?.oldFill !== 'undefined') ? element?.oldFill : 'undefined')
if (updateColor !== 'undefined') {
element.style.fill = updateColor
}
if (svgElementConfig.stateAsOpacity) { // we use the flash element
let opacity = (svgElementConfig.invertStateOpacity) ? 1 : 0
opacity = (opacity < svgElementConfig.stateMinOpacity) ? svgElementConfig.stateMinOpacity : opacity
element.style.opacity = opacity
}
if (svgElementConfig.stateOnAsStyleClass) {
// remove OnState-Styles first
let onStatesArray = svgElementConfig.stateOnAsStyleClass.split(',')
for (const onState of onStatesArray) {
const elementClassInfo = onState.split(':')
const onStateElement = document.getElementById(elementClassInfo[0].trim())
if (onStateElement) {
onStateElement.classList.remove(elementClassInfo[1].trim())
} else {
console.warn(`Target element ${elementClassInfo[0].trim()} not found. Please check style stateOnAsStyleClass expression of ${element.id}`)
}
}
if (svgElementConfig.stateOffAsStyleClass) { // if offStates are provided add OffStates
let offStatesArray = svgElementConfig.stateOffAsStyleClass.split(',')
for (const offState of offStatesArray) {
const elementClassInfo = offState.split(':')
const offStateElement = document.getElementById(elementClassInfo[0].trim())
if (offStateElement) {
offStateElement.classList.add(elementClassInfo[1].trim())
} else {
console.warn(`Target element ${elementClassInfo[0].trim()} not found. Please check style stateOffAsStyleClass expression of ${element.id}`)
}
}
}
}
} else { // Percent, OpenClosed
if (svgElementConfig.stateAsOpacity && state) {
// we expect that number between 0 - 100
let opacity
if (stateType === 'OpenClosed') {
opacity = (state === 'OPEN') ? 1 : 0
} else if (stateType === 'Percent' && !isNaN(state)) {
opacity = parseFloat(state) / 100.0
}
opacity = (svgElementConfig.invertStateOpacity) ? 1 - opacity : opacity
opacity = (opacity < svgElementConfig.stateMinOpacity) ? svgElementConfig.stateMinOpacity : opacity
element.style.opacity = opacity
}
}
}
switch (stateType) { switch (stateType) {
// currently no distinction is made regarding different state-types, only the following are supported (yet) // currently no distinction is made regarding different state-types, only the following are supported (yet)
case 'OpenClosed': case 'OpenClosed':
@ -300,92 +398,13 @@ export default {
case 'OnOff': case 'OnOff':
const useProxy = tagName === 'g' && svgElementConfig.useProxyElementForState // if proxy should be used and element is of type group const useProxy = tagName === 'g' && svgElementConfig.useProxyElementForState // if proxy should be used and element is of type group
const element = (useProxy) ? svgElement.querySelector('[flash]') : svgElement const element = (useProxy) ? svgElement.querySelector('[flash]') : svgElement
if (!element) { if (element) {
console.warn(`Element ${svgElement} is a group element but has no containing element with the attribute "flash"`) processState(useProxy, element)
return } else {
} // let's try processing all paths within the group instead
if (state === 'ON' || stateType === 'HSB') { const pathElements = svgElement.querySelectorAll('path')
if (useProxy && svgElementConfig.stateAsOpacity) { // we use the flash element for (const path of pathElements) {
let opacity = (state === 'ON') ? 1 : 0 processState(useProxy, path)
opacity = (svgElementConfig.invertStateOpacity) ? 1 - opacity : opacity
opacity = (opacity < svgElementConfig.stateMinOpacity) ? svgElementConfig.stateMinOpacity : opacity
// TODO: use fill-opacity if fill not available
element.style.opacity = opacity
} else {
element.oldFill = element.style.fill
element.style.fill = stateOnColorRgbStyle
}
if (svgElementConfig.stateOnAsStyleClass) {
if (svgElementConfig.stateOffAsStyleClass) { // if offStates are provided add OffStates
let offStatesArray = svgElementConfig.stateOffAsStyleClass.split(',')
for (const offState of offStatesArray) {
const elementClassInfo = offState.split(':')
const offStateElement = document.getElementById(elementClassInfo[0].trim())
if (offStateElement) {
offStateElement.classList.remove(elementClassInfo[1].trim())
} else {
console.warn(`Target element ${elementClassInfo[0].trim()} not found. Please check style stateOffAsStyleClass expression of ${element.id}`)
}
}
}
let onStatesArray = svgElementConfig.stateOnAsStyleClass.split(',')
for (const onState of onStatesArray) {
const elementClassInfo = onState.split(':')
const onStateElement = document.getElementById(elementClassInfo[0].trim())
if (onStateElement) {
onStateElement.classList.add(elementClassInfo[1].trim())
} else {
console.warn(`Target element ${elementClassInfo[0].trim()} not found. Please check style stateOnAsStyleClass expression of ${element.id}`)
}
}
}
} else if (state === 'OFF') {
const updateColor = (stateOffColorRgbStyle) || ((element?.oldFill !== 'undefined') ? element?.oldFill : 'undefined')
if (updateColor !== 'undefined') {
element.style.fill = updateColor
}
if (svgElementConfig.stateAsOpacity) { // we use the flash element
let opacity = (svgElementConfig.invertStateOpacity) ? 1 : 0
opacity = (opacity < svgElementConfig.stateMinOpacity) ? svgElementConfig.stateMinOpacity : opacity
element.style.opacity = opacity
}
if (svgElementConfig.stateOnAsStyleClass) {
// remove OnState-Styles first
let onStatesArray = svgElementConfig.stateOnAsStyleClass.split(',')
for (const onState of onStatesArray) {
const elementClassInfo = onState.split(':')
const onStateElement = document.getElementById(elementClassInfo[0].trim())
if (onStateElement) {
onStateElement.classList.remove(elementClassInfo[1].trim())
} else {
console.warn(`Target element ${elementClassInfo[0].trim()} not found. Please check style stateOnAsStyleClass expression of ${element.id}`)
}
}
if (svgElementConfig.stateOffAsStyleClass) { // if offStates are provided add OffStates
let offStatesArray = svgElementConfig.stateOffAsStyleClass.split(',')
for (const offState of offStatesArray) {
const elementClassInfo = offState.split(':')
const offStateElement = document.getElementById(elementClassInfo[0].trim())
if (offStateElement) {
offStateElement.classList.add(elementClassInfo[1].trim())
} else {
console.warn(`Target element ${elementClassInfo[0].trim()} not found. Please check style stateOffAsStyleClass expression of ${element.id}`)
}
}
}
}
} else { // Percent, OpenClosed
if (svgElementConfig.stateAsOpacity && state) {
// we expect that number between 0 - 100
let opacity
if (stateType === 'OpenClosed') {
opacity = (state === 'OPEN') ? 1 : 0
} else if (stateType === 'Percent' && !isNaN(state)) {
opacity = parseFloat(state) / 100.0
}
opacity = (svgElementConfig.invertStateOpacity) ? 1 - opacity : opacity
opacity = (opacity < svgElementConfig.stateMinOpacity) ? svgElementConfig.stateMinOpacity : opacity
element.style.opacity = opacity
} }
} }
break break