Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Blockly: Upgrade to v9, add JSScripting (GraalVM) implementations, UoM block types #1617

Merged
merged 40 commits into from
Jan 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
0a50a7b
[MainUI] Script editor: Use JS Scripting for new Blocklies if available
florian-h05 Nov 27, 2022
96e8572
[MainUI] Script editor: Switch existing Blocklies to JS Scripting if …
florian-h05 Nov 27, 2022
2002fb9
[MainUI] Blockly: Add compatibility code for JS Scripting
florian-h05 Nov 27, 2022
2d31012
[MainUI] Blockly: Use native JS Scripting code in logging blocks
florian-h05 Nov 27, 2022
4402364
add jsscripting support for blockly colors, items, logging, text
stefan-hoehn Dec 4, 2022
d8ea088
[MainUI] Blockly: Add JS Scripting support for dicts, eventbus, list,…
florian-h05 Dec 19, 2022
fc0681e
[MainUI] Blockly: Extend JS Scripting support for items
florian-h05 Dec 19, 2022
eba66cd
add units of measuremt block
stefan-hoehn-nfon Dec 20, 2022
fe243e5
add units of measuremt block
stefan-hoehn-nfon Dec 20, 2022
9fb27b1
add units of measurement compare
stefan-hoehn-nfon Dec 20, 2022
0ad888d
[MainUI] Blockly: Update code that is prepended
florian-h05 Dec 22, 2022
2f89b5b
[MainUI] Blockly: Add JS Scripting support for scripts, things, value…
florian-h05 Dec 22, 2022
4993d4d
[MainUI] Blockly: Add JS Scripting support for ephemeris, timers
florian-h05 Dec 22, 2022
40dce87
jsscriÃpting audio blocks
stefan-hoehn-nfon Dec 22, 2022
7feb051
jsscripting audio blocks - better implementation
stefan-hoehn-nfon Dec 22, 2022
548bc37
[MainUI] Improve Blockly MIME type logic
florian-h05 Dec 23, 2022
771fbbf
[MainUI] Fix bug from previous commit
florian-h05 Dec 23, 2022
2fe3e9f
store value in private/global cache
stefan-hoehn-nfon Dec 23, 2022
5b0f2af
fix: store value in private/global cache
stefan-hoehn-nfon Dec 24, 2022
160db39
[MainUI] Blockly: Pass in `isGraalJs` instead of adding to the worksp…
florian-h05 Dec 24, 2022
29ec2b2
[MainUI] Blockly: Complete dynamic block TODO in scripts
florian-h05 Dec 24, 2022
82df5be
[MainUI] Blockly:Add JS Scripting support for dateoffsets
florian-h05 Dec 24, 2022
7dff497
[MainUI] Blockly: Add TODO comment in notifications
florian-h05 Dec 24, 2022
083cec0
[MainUI] Blockly: Add JS Scripting support for persistence
florian-h05 Dec 24, 2022
090e853
[MainUI] Migrate Blockly to v9
florian-h05 Dec 27, 2022
6c9a9e3
stricter type checking on oh_item, fix datetimeaddDateComparisonSupport
stefan-hoehn-nfon Dec 28, 2022
18e1aac
fix micros for graalvm
stefan-hoehn-nfon Dec 29, 2022
5d4ed96
[MainUI] Blockly: Fix millis for the oh_zdt_create block
florian-h05 Dec 29, 2022
9b2910d
add cross-copy-and-paste, fix linting
stefan-hoehn-nfon Dec 29, 2022
add8862
[MainUI] Remove two unnecessary blank lines from Blockly definitions
florian-h05 Dec 29, 2022
910cfa2
[MainUI] Blockly: Update UoM Blocks for the finished Quantity API in …
florian-h05 Dec 29, 2022
751ea51
[blockly] finalize graalvm implementation, add tests
stefan-hoehn-nfon Dec 29, 2022
f195f73
Extend Blockly Quantities Test
florian-h05 Dec 30, 2022
4f8d5b7
Update MIME types
florian-h05 Jan 1, 2023
7a186dd
add README to blockly tests
stefan-hoehn-nfon Jan 2, 2023
70814c0
fix persistence blocks, optimize copy/paste registration detection
stefan-hoehn Jan 8, 2023
6cbf985
[MainUI] Fix Blockly not reacting to changed MIME type
florian-h05 Jan 8, 2023
375fb0f
[MainUI] Lint code
florian-h05 Jan 8, 2023
0d40b8d
fix jsscripting persistence breaking change, add historic state
stefan-hoehn Jan 14, 2023
1f06efe
add tooltip hint that notifications requires OH cloud
stefan-hoehn Jan 17, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,051 changes: 813 additions & 238 deletions bundles/org.openhab.ui/web/package-lock.json

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions bundles/org.openhab.ui/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,15 @@
"Samsung >= 5"
],
"dependencies": {
"@blockly/field-slider": "^2.1.10",
"@blockly/zoom-to-fit": "^2.0.24",
"@blockly/field-slider": "^4.0.3",
"@blockly/plugin-cross-tab-copy-paste": "^2.0.4",
"@blockly/theme-dark": "^4.0.1",
"@blockly/zoom-to-fit": "^3.0.3",
"@jsep-plugin/arrow": "^1.0.5",
"@jsep-plugin/object": "^1.2.1",
"@jsep-plugin/regex": "^1.0.3",
"@jsep-plugin/template": "^1.0.2",
"blockly": "^6.20210701.0",
"blockly": "^9.2.0",
"cronstrue": "^1.100.0",
"dayjs": "^1.9.6",
"dom7": "^2.1.5",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Blockly from 'blockly'
import { javascriptGenerator } from 'blockly/javascript'
import { FieldItemModelPicker } from './ohitemfield'

export default function defineOHBlocks_Exec (f7) {
Expand All @@ -22,9 +23,9 @@ export default function defineOHBlocks_Exec (f7) {
}
}

Blockly.JavaScript['oh_exec'] = function (block) {
javascriptGenerator['oh_exec'] = function (block) {
let runCommand = block.getFieldValue('execCommand')
const itemName = Blockly.JavaScript.valueToCode(block, 'sendTo', Blockly.JavaScript.ORDER_ATOMIC)
const itemName = javascriptGenerator.valueToCode(block, 'sendTo', javascriptGenerator.ORDER_ATOMIC)
let code = 'var exec = Java.type("org.openhab.core.model.script.actions.Exec");\n'
code += 'var duration = Java.type("java.time.Duration");\n'
code += 'var results = exec.executeCommandLine(duration.ofSeconds(1), "' + runCommand + '", "")\n'
Expand All @@ -48,17 +49,17 @@ export default function defineOHBlocks_Exec (f7) {
}
}

Blockly.JavaScript['oh_exec2'] = function (block) {
const exec = Blockly.JavaScript.provideFunction_(
javascriptGenerator['oh_exec2'] = function (block) {
const exec = javascriptGenerator.provideFunction_(
'exec',
['var ' + Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_ + ' = Java.type("org.openhab.core.model.script.actions.Exec");'])
const duration = Blockly.JavaScript.provideFunction_(
['var ' + javascriptGenerator.FUNCTION_NAME_PLACEHOLDER_ + ' = Java.type("org.openhab.core.model.script.actions.Exec");'])
const duration = javascriptGenerator.provideFunction_(
'duration',
['var ' + Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_ + ' = Java.type("java.time.Duration");'])
['var ' + javascriptGenerator.FUNCTION_NAME_PLACEHOLDER_ + ' = Java.type("java.time.Duration");'])
let runCommand = block.getFieldValue('cmdExecute').replace(/ /g, '","')
let timeout = block.getFieldValue('timeout')
let code = exec + '.executeCommandLine(' + duration + '.ofSeconds(' + timeout + '),"' + runCommand + '")\n'
return [code, Blockly.JavaScript.ORDER_NONE]
return [code, javascriptGenerator.ORDER_NONE]
}

Blockly.Blocks['oh_exec3'] = {
Expand All @@ -78,16 +79,16 @@ export default function defineOHBlocks_Exec (f7) {
}
}

Blockly.JavaScript['oh_exec3'] = function (block) {
const exec = Blockly.JavaScript.provideFunction_(
javascriptGenerator['oh_exec3'] = function (block) {
const exec = javascriptGenerator.provideFunction_(
'exec',
['var ' + Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_ + ' = Java.type(\'org.openhab.core.model.script.actions.Exec\');'])
const duration = Blockly.JavaScript.provideFunction_(
['var ' + javascriptGenerator.FUNCTION_NAME_PLACEHOLDER_ + ' = Java.type(\'org.openhab.core.model.script.actions.Exec\');'])
const duration = javascriptGenerator.provideFunction_(
'duration',
['var ' + Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_ + ' = Java.type(\'java.time.Duration\');'])
['var ' + javascriptGenerator.FUNCTION_NAME_PLACEHOLDER_ + ' = Java.type(\'java.time.Duration\');'])
let runCommand = block.getFieldValue('cmdExecute').replace(/ /g, '","')
let timeout = block.getFieldValue('timeout')
let code = exec + '.executeCommandLine(' + duration + '.ofSeconds(' + timeout + '),"' + runCommand + '")\n'
return [code, Blockly.JavaScript.ORDER_NONE]
return [code, javascriptGenerator.ORDER_NONE]
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Blockly from 'blockly'
import { javascriptGenerator } from 'blockly/javascript'
import { FieldItemModelPicker } from './ohitemfield'

export default function defineOHBlocks_HTTP (f7, scripts) {
Expand All @@ -22,21 +23,21 @@ export default function defineOHBlocks_HTTP (f7, scripts) {
}
}

Blockly.JavaScript['oh_httprequest'] = function (block) {
const http = Blockly.JavaScript.provideFunction_(
javascriptGenerator['oh_httprequest'] = function (block) {
const http = javascriptGenerator.provideFunction_(
'http',
['var ' + Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_ + ' = Java.type(\'org.openhab.core.model.script.actions.HTTP\');'])
['var ' + javascriptGenerator.FUNCTION_NAME_PLACEHOLDER_ + ' = Java.type(\'org.openhab.core.model.script.actions.HTTP\');'])
let requesttype = block.getFieldValue('requestType')
let contenttype = block.getFieldValue('contentType')
let url = Blockly.JavaScript.valueToCode(block, 'url', Blockly.JavaScript.ORDER_ATOMIC)
let payload = Blockly.JavaScript.valueToCode(block, 'payload', Blockly.JavaScript.ORDER_ATOMIC)
let url = javascriptGenerator.valueToCode(block, 'url', javascriptGenerator.ORDER_ATOMIC)
let payload = javascriptGenerator.valueToCode(block, 'payload', javascriptGenerator.ORDER_ATOMIC)
let code = ''
if (contenttype === 'none') {
code = http + '.' + requesttype + '("' + url + '",60)'
} else {
code = http + '.' + requesttype + '(' + url + ',"' + contenttype + '","' + payload + '")'
}
return [code, Blockly.JavaScript.ORDER_NONE]
return [code, javascriptGenerator.ORDER_NONE]
}

Blockly.Blocks['oh_script_dropdown'] = {
Expand All @@ -58,10 +59,10 @@ export default function defineOHBlocks_HTTP (f7, scripts) {
}
}

Blockly.JavaScript['oh_script_dropdown'] = function (block) {
javascriptGenerator['oh_script_dropdown'] = function (block) {
let scriptName = block.getFieldValue('script')
let code = scriptName
return [code, Blockly.JavaScript.ORDER_NONE]
return [code, javascriptGenerator.ORDER_NONE]
}

Blockly.Blocks['oh_ping'] = {
Expand All @@ -76,12 +77,12 @@ export default function defineOHBlocks_HTTP (f7, scripts) {
}
}

Blockly.JavaScript['oh_ping'] = function (block) {
const actions = Blockly.JavaScript.provideFunction_(
javascriptGenerator['oh_ping'] = function (block) {
const actions = javascriptGenerator.provideFunction_(
'actions',
['var ' + Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_ + ' = Java.type("org.openhab.core.model.script.actions.Ping");'])
let hostname = Blockly.JavaScript.valueToCode(block, 'hostName', Blockly.JavaScript.ORDER_ATOMIC)
['var ' + javascriptGenerator.FUNCTION_NAME_PLACEHOLDER_ + ' = Java.type("org.openhab.core.model.script.actions.Ping");'])
let hostname = javascriptGenerator.valueToCode(block, 'hostName', javascriptGenerator.ORDER_ATOMIC)
let code = actions + '.checkVitality(' + hostname + ',0,10)'
return [code, Blockly.JavaScript.ORDER_NONE]
return [code, javascriptGenerator.ORDER_NONE]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
* - Even though "enhancedjavasound" is provided as a sink, currently it is not clear what the intention is
*
* See more background info on openHAB multimedia here: https://www.openhab.org/docs/configuration/multimedia.html
*
* supports jsscripting
*/

import Blockly from 'blockly'
import { javascriptGenerator } from 'blockly/javascript'
import { FieldSlider } from '@blockly/field-slider'

export default function (f7, sinks, voices) {
export default function (f7, isGraalJs, sinks, voices) {
Blockly.Blocks['oh_volumeslider'] = {
init: function () {
this.appendDummyInput()
Expand All @@ -22,7 +25,7 @@ export default function (f7, sinks, voices) {
}
}

Blockly.JavaScript['oh_volumeslider'] = function (block) {
javascriptGenerator['oh_volumeslider'] = function (block) {
const fieldName = block.getFieldValue('volume')
let code = `'${fieldName}'`
return [code, 0]
Expand Down Expand Up @@ -53,13 +56,12 @@ export default function (f7, sinks, voices) {
* Plays a file (like mp3) which resides in conf/sounds to the given sink
* Code part
*/
Blockly.JavaScript['oh_playmedia_sink'] = function (block) {
const audio = addAudio()
let fileName = Blockly.JavaScript.valueToCode(block, 'fileName', Blockly.JavaScript.ORDER_ATOMIC)
let sinkName = Blockly.JavaScript.valueToCode(block, 'sinkName', Blockly.JavaScript.ORDER_ATOMIC).replace('(', '').replace(/[()]/g, '')
javascriptGenerator['oh_playmedia_sink'] = function (block) {
let fileName = javascriptGenerator.valueToCode(block, 'fileName', javascriptGenerator.ORDER_ATOMIC)
let sinkName = javascriptGenerator.valueToCode(block, 'sinkName', javascriptGenerator.ORDER_ATOMIC).replace('(', '').replace(/[()]/g, '')

let code = `${audio}.playSound(${sinkName}, ${fileName});\n`
return code
const audio = (isGraalJs) ? 'actions.Audio' : addAudio()
return `${audio}.playSound(${sinkName}, ${fileName});\n`
}

/*
Expand Down Expand Up @@ -91,14 +93,17 @@ export default function (f7, sinks, voices) {
* Note: In general, though more complex, rather create a volume item for that device and set the volume first as it is more reliable.
* Code part
*/
Blockly.JavaScript['oh_playmedia_sink_volume'] = function (block) {
const audio = addAudio()
let fileName = Blockly.JavaScript.valueToCode(block, 'fileName', Blockly.JavaScript.ORDER_ATOMIC)
let sinkName = Blockly.JavaScript.valueToCode(block, 'sinkName', Blockly.JavaScript.ORDER_ATOMIC).replace('(', '').replace(/[()]/g, '')
let volume = Blockly.JavaScript.valueToCode(block, 'volume', Blockly.JavaScript.ORDER_ATOMIC).replace(/'/g, '')
javascriptGenerator['oh_playmedia_sink_volume'] = function (block) {
let fileName = javascriptGenerator.valueToCode(block, 'fileName', javascriptGenerator.ORDER_ATOMIC)
let sinkName = javascriptGenerator.valueToCode(block, 'sinkName', javascriptGenerator.ORDER_ATOMIC).replace('(', '').replace(/[()]/g, '')
let volume = javascriptGenerator.valueToCode(block, 'volume', javascriptGenerator.ORDER_ATOMIC).replace(/'/g, '')

let code = `${audio}.playSound(${sinkName}, ${fileName}, new PercentType(${volume}));\n`
return code
if (isGraalJs) {
return `actions.Audio.playSound(${sinkName}, ${fileName}, new runtime.PercentType(${volume}));\n`
} else {
const audio = addAudio()
return `${audio}.playSound(${sinkName}, ${fileName}, new PercentType(${volume}));\n`
}
}

/*
Expand Down Expand Up @@ -126,12 +131,12 @@ export default function (f7, sinks, voices) {
* Plays a stream from a URL on a specific sink
* Blockly part
*/
Blockly.JavaScript['oh_playstream_sink'] = function (block) {
const audio = addAudio()
let url = Blockly.JavaScript.valueToCode(block, 'url', Blockly.JavaScript.ORDER_ATOMIC)
let sinkName = Blockly.JavaScript.valueToCode(block, 'sinkName', Blockly.JavaScript.ORDER_ATOMIC).replace('(', '').replace(/[()]/g, '')
let code = `${audio}.playStream(${sinkName}, ${url});\n`
return code
javascriptGenerator['oh_playstream_sink'] = function (block) {
let url = javascriptGenerator.valueToCode(block, 'url', javascriptGenerator.ORDER_ATOMIC)
let sinkName = javascriptGenerator.valueToCode(block, 'sinkName', javascriptGenerator.ORDER_ATOMIC).replace('(', '').replace(/[()]/g, '')

const audio = (isGraalJs) ? 'actions.Audio' : addAudio()
return `${audio}.playStream(${sinkName}, ${url});\n`
}

/*
Expand All @@ -156,12 +161,12 @@ export default function (f7, sinks, voices) {
* Stops a stream on a specific sink
* Blockly part
*/
Blockly.JavaScript['oh_stopstream_sink'] = function (block) {
const audio = addAudio()
javascriptGenerator['oh_stopstream_sink'] = function (block) {
let url = block.getFieldValue('url')
let sinkName = Blockly.JavaScript.valueToCode(block, 'sinkName', Blockly.JavaScript.ORDER_ATOMIC).replace('(', '').replace(/[()]/g, '')
let code = `${audio}.playStream(${sinkName}, null);\n`
return code
let sinkName = javascriptGenerator.valueToCode(block, 'sinkName', javascriptGenerator.ORDER_ATOMIC).replace('(', '').replace(/[()]/g, '')

const audio = (isGraalJs) ? 'actions.Audio' : addAudio()
return `${audio}.playStream(${sinkName}, null);\n`
}

/*
Expand Down Expand Up @@ -191,17 +196,13 @@ export default function (f7, sinks, voices) {
* Says some text via a device sink - TTS has to be installed for that
* Code part
*/
Blockly.JavaScript['oh_say'] = function (block) {
const voice = Blockly.JavaScript.provideFunction_(
'voice',
['var ' + Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_ + ' = Java.type(\'org.openhab.core.model.script.actions.Voice\');'])
javascriptGenerator['oh_say'] = function (block) {
const textToSay = javascriptGenerator.valueToCode(block, 'textToSay', javascriptGenerator.ORDER_ATOMIC)
const voiceName = javascriptGenerator.valueToCode(block, 'voice', javascriptGenerator.ORDER_ATOMIC).replace('(', '').replace(/[()]/g, '')
const deviceSink = javascriptGenerator.valueToCode(block, 'deviceSink', javascriptGenerator.ORDER_ATOMIC).replace('(', '').replace(/[()]/g, '')

const textToSay = Blockly.JavaScript.valueToCode(block, 'textToSay', Blockly.JavaScript.ORDER_ATOMIC)
const voiceName = Blockly.JavaScript.valueToCode(block, 'voice', Blockly.JavaScript.ORDER_ATOMIC).replace('(', '').replace(/[()]/g, '')
const deviceSink = Blockly.JavaScript.valueToCode(block, 'deviceSink', Blockly.JavaScript.ORDER_ATOMIC).replace('(', '').replace(/[()]/g, '')

const code = `${voice}.say(${textToSay}, ${voiceName}, ${deviceSink});\n`
return code
const voice = (isGraalJs) ? 'actions.Voice' : addVoice()
return `${voice}.say(${textToSay}, ${voiceName}, ${deviceSink});\n`
}

/*
Expand All @@ -228,9 +229,9 @@ export default function (f7, sinks, voices) {
}
}

Blockly.JavaScript['oh_audiosink_dropdown'] = function (block) {
javascriptGenerator['oh_audiosink_dropdown'] = function (block) {
let sinkName = block.getFieldValue('sinks')
return [`'${sinkName}'`, Blockly.JavaScript.ORDER_NONE]
return [`'${sinkName}'`, javascriptGenerator.ORDER_NONE]
}

/*
Expand Down Expand Up @@ -258,14 +259,20 @@ export default function (f7, sinks, voices) {
}
}

Blockly.JavaScript['oh_voices_dropdown'] = function (block) {
javascriptGenerator['oh_voices_dropdown'] = function (block) {
let voiceName = block.getFieldValue('voiceName')
return [`'${voiceName}'`, Blockly.JavaScript.ORDER_NONE]
return [`'${voiceName}'`, javascriptGenerator.ORDER_NONE]
}

function addAudio () {
return Blockly.JavaScript.provideFunction_(
return javascriptGenerator.provideFunction_(
'audio',
['var ' + Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_ + ' = Java.type(\'org.openhab.core.model.script.actions.Audio\');'])
['var ' + javascriptGenerator.FUNCTION_NAME_PLACEHOLDER_ + ' = Java.type(\'org.openhab.core.model.script.actions.Audio\');'])
}

function addVoice () {
return javascriptGenerator.provideFunction_(
'voice',
['var ' + javascriptGenerator.FUNCTION_NAME_PLACEHOLDER_ + ' = Java.type(\'org.openhab.core.model.script.actions.Voice\');'])
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
/*
* Adds new blocks to the colour section
* supports jsscripting
*/

import Blockly from 'blockly'
import { javascriptGenerator } from 'blockly/javascript'

export default function (f7) {
export default function (f7, isGraalJs) {
/*
* converts a hex color string in to an openHAB hue-saturation-brightness string
* Block
Expand All @@ -26,9 +28,9 @@ export default function (f7) {
* converts a hex color string in to an openHAB hue-saturation-brightness string
* Code generation
*/
Blockly.JavaScript['oh_color_to_hsb'] = function (block) {
javascriptGenerator['oh_color_to_hsb'] = function (block) {
let conversionFunction = addConvertColourHexToHSB()
const hexColor = Blockly.JavaScript.valueToCode(block, 'hexColor', Blockly.JavaScript.ORDER_ATOMIC)
const hexColor = javascriptGenerator.valueToCode(block, 'hexColor', javascriptGenerator.ORDER_ATOMIC)
let code = `${conversionFunction}(${hexColor})`
return [code, 0]
}
Expand All @@ -37,10 +39,10 @@ export default function (f7) {
* converts rgb to hsb (thanks to https://www.30secondsofcode.org/js/s/rgb-to-hsb)
*/
function addConvertColourHexToHSB () {
const hsbConversion = Blockly.JavaScript.provideFunction_(
const hsbConversion = javascriptGenerator.provideFunction_(
'colorHexToHSB',
[
'function ' + Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_ + ' (hexColor) {',
'function ' + javascriptGenerator.FUNCTION_NAME_PLACEHOLDER_ + ' (hexColor) {',
' var rgb = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hexColor);',
' if (!rgb) return \'\';',
' var r = parseInt(rgb[1], 16) / 255, g = parseInt(rgb[2], 16) / 255, b = parseInt(rgb[3], 16) / 255;',
Expand Down
Loading