Last active
October 22, 2023 04:46
-
-
Save faiyaz7283/901046b7d902953eca34de0b797e8cca to your computer and use it in GitHub Desktop.
Watchlist heatmap for Trading View
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/ | |
// © faiyaz7283 | |
//@version=5 | |
indicator(title="Watchlist Heatmap", shorttitle="WL Heatmap", overlay=true) | |
// ::Imports:: { | |
import faiyaz7283/tools/9 as tools | |
import faiyaz7283/printer/6 as prnt | |
import faiyaz7283/multidata/7 as mltd | |
// } | |
// ::Inputs:: { | |
var section01 = 'POSITION, SIZE & COLORS' | |
//---------------------: | |
var displayLoc = input.string(defval = position.top_right, title = 'Display Location', options = [position.top_left, | |
position.top_center, position.top_right, position.middle_left, position.middle_center, position.middle_right, | |
position.bottom_left, position.bottom_center, position.bottom_right], group = section01) | |
var cellPad = input.int(defval = 1, title = 'Cell Spacing', minval = 0, maxval = 5, group = section01) | |
var orientation = input.string(defval = 'vertical', title = 'Orientation', options = ['horizontal', 'vertical'], | |
group = section01) == 'vertical' ? false : true | |
var grdUp = input.color(defval = #00FF00, title = '⬆ Max', inline = 'grdnt', group = section01) | |
var grdNeutral = input.color(defval = #fff9c4, title = '⮕ Neutral', inline = 'grdnt', group = section01) | |
var grdDown = input.color(defval = #FF0000, title = '⬇ Min', inline = 'grdnt', group = section01) | |
var headerColor = input.color(defval = #ffcc80, title = 'Header:\tColor', inline = 'hdrCl', group = section01) | |
var headerSize = input.string(defval=size.normal, title = 'Size', options = ['hide', size.auto, size.tiny, size.small, | |
size.normal, size.large, size.huge], inline = 'hdrCl', group = section01) | |
var headerAlign = input.string(defval=text.align_center, title = 'Align', | |
options = [text.align_left, text.align_center, text.align_right], inline = 'hdrCl', group = section01) | |
var titleColor = input.color(defval = #b2ebf2, title = 'Title:\tColor', inline = 'ttlCl', group = section01) | |
var titleSize = input.string(defval=size.small, title = 'Size', options = ['hide', size.auto, size.tiny, size.small, | |
size.normal, size.large, size.huge], inline = 'ttlCl', group = section01) | |
var titleAlign = input.string(defval=text.align_right, title = 'Align', | |
options = [text.align_left, text.align_center, text.align_right], inline = 'ttlCl', group = section01) | |
var keySize = input.string(defval=size.auto, title = 'Key:\tSize', options = [size.auto, size.tiny, size.small, | |
size.normal, size.large, size.huge], inline = 'keyCl', group = section01) | |
var keyAlign = input.string(defval=text.align_center, title = 'Align', | |
options = [text.align_left, text.align_center, text.align_right], inline = 'keyCl', group = section01) | |
var keyOffset = input.string(defval='text', title = 'Key:\tOffset Item', options = ['text', 'background'], | |
inline = 'keyCl2', group = section01) == 'background' ? 'bg' : 'text' | |
var keyOffsetColor = input.color(defval = #000000, title = 'Offset Color', inline = 'keyCl2', group = section01) | |
var textSize = input.string(defval=size.auto, title = 'Value:\tSize', options = [size.auto, size.tiny, size.small, | |
size.normal, size.large, size.huge], inline = 'valueCl', group = section01) | |
var textAlign = input.string(defval=text.align_right, title = 'Align', | |
options = [text.align_left, text.align_center, text.align_right], inline = 'valueCl', group = section01) | |
var textOffset = input.string(defval='background', title = 'Value:\tOffset Item', options = ['text', 'background'], | |
inline = 'valueCl2', group = section01) == 'background' ? 'bg' : 'text' | |
var textOffsetColor = input.color(defval = #00000000, title = 'Offset Color', inline = 'valueCl2', group = section01) | |
var section02 = 'DATA DISPLAY' | |
//----------------------------: | |
var prevTip = "Select this box to exhibit data from the previous bar on the chart's current or custom timeframe." | |
var prevBar = input.bool(defval = false, title = 'Previous Bar Data', tooltip = prevTip, group = section02) | |
var showVol = input.bool(defval = true, title = 'Show Volume', inline='vol', group = section02) | |
var displayValues = input.string(defval ='regular', title = 'Display Data Type', options = ['regular', 'change', | |
'change percent'], inline='/', group = section02) | |
var sortByOrder = input.string(defval ='descending', title = 'Sort Order', options = ['descending', 'ascending'], | |
inline = '-', group = section02) | |
var asc = sortByOrder == 'ascending' ? true : false | |
var section03 = 'CUSTOM TIMEFRAME' | |
//--------------------------------: | |
var tfNote1 = "Use a custom timeframe, instead of chart's timeframe.\n" | |
var tfNote2 = tfNote1 + '\nNote: Please ensure that the timeframe used is either equal to or higher than that of the ' | |
var tfNote3 = tfNote2 + 'active chart. Using a lower timeframe is not recommended and will run into memory limitation.' | |
var tfNote4 = tfNote3 + '\n\nValid Multipliers:\n- Seconds, ONLY use 1, 5, 10, 15 or 30.\n- Minutes, 1 to 1440.' | |
var tfNote5 = tfNote4 + '\n- Days, 1 to 365.\n- Weeks, 1 to 52.\n- Months, 1 to 12.\n\nCheck Apply box to use.' | |
var tfMultiplier = input.int(defval = 1, title = 'Multiplier', minval = 1, maxval = 1440, inline = 'tf', | |
group = section03) | |
var tfUnits = input.timeframe(defval = 'Days', title = 'Unit', options = ['Seconds', 'Minutes', 'Days', 'Weeks', | |
'Months'], inline = 'tf', group = section03) | |
var customTf = input.bool(defval = false, title = 'Apply', tooltip = tfNote5, inline = 'tf', group = section03) | |
var unit = switch tfUnits | |
'Seconds' => 'S' | |
'Minutes' => '' | |
'Days' => 'D' | |
'Weeks' => 'W' | |
'Months' => 'M' | |
var tf = str.tostring(tfMultiplier) + unit | |
var section04 = 'WATCHLIST SYMBOLS' | |
//--------------------------------: | |
var symbol01 = input.symbol(defval = '', title = '01. ', inline = 'r01', group = section04) | |
var symbol02 = input.symbol(defval = '', title = '02. ', inline = 'r01', group = section04) | |
var symbol03 = input.symbol(defval = '', title = '03. ', inline = 'r02', group = section04) | |
var symbol04 = input.symbol(defval = '', title = '04. ', inline = 'r02', group = section04) | |
var symbol05 = input.symbol(defval = '', title = '05. ', inline = 'r03', group = section04) | |
var symbol06 = input.symbol(defval = '', title = '06. ', inline = 'r03', group = section04) | |
var symbol07 = input.symbol(defval = '', title = '07. ', inline = 'r04', group = section04) | |
var symbol08 = input.symbol(defval = '', title = '08. ', inline = 'r04', group = section04) | |
var symbol09 = input.symbol(defval = '', title = '09. ', inline = 'r05', group = section04) | |
var symbol10 = input.symbol(defval = '', title = '10. ', inline = 'r05', group = section04) | |
var symbol11 = input.symbol(defval = '', title = '11. ', inline = 'r06', group = section04) | |
var symbol12 = input.symbol(defval = '', title = '12. ', inline = 'r06', group = section04) | |
var symbol13 = input.symbol(defval = '', title = '13. ', inline = 'r07', group = section04) | |
var symbol14 = input.symbol(defval = '', title = '14. ', inline = 'r07', group = section04) | |
var symbol15 = input.symbol(defval = '', title = '15. ', inline = 'r08', group = section04) | |
var symbol16 = input.symbol(defval = '', title = '16. ', inline = 'r08', group = section04) | |
var symbol17 = input.symbol(defval = '', title = '17. ', inline = 'r09', group = section04) | |
var symbol18 = input.symbol(defval = '', title = '18. ', inline = 'r09', group = section04) | |
var symbol19 = input.symbol(defval = '', title = '19. ', inline = 'r10', group = section04) | |
var symbol20 = input.symbol(defval = '', title = '20. ', inline = 'r10', group = section04) | |
var symbol21 = input.symbol(defval = '', title = '21. ', inline = 'r11', group = section04) | |
var symbol22 = input.symbol(defval = '', title = '22. ', inline = 'r11', group = section04) | |
var symbol23 = input.symbol(defval = '', title = '23. ', inline = 'r12', group = section04) | |
var symbol24 = input.symbol(defval = '', title = '24. ', inline = 'r12', group = section04) | |
var symbol25 = input.symbol(defval = '', title = '25. ', inline = 'r13', group = section04) | |
var symbol26 = input.symbol(defval = '', title = '26. ', inline = 'r13', group = section04) | |
var symbol27 = input.symbol(defval = '', title = '27. ', inline = 'r14', group = section04) | |
var symbol28 = input.symbol(defval = '', title = '28. ', inline = 'r14', group = section04) | |
var symbol29 = input.symbol(defval = '', title = '29. ', inline = 'r15', group = section04) | |
var symbol30 = input.symbol(defval = '', title = '30. ', inline = 'r15', group = section04) | |
var symbol31 = input.symbol(defval = '', title = '31. ', inline = 'r16', group = section04) | |
var symbol32 = input.symbol(defval = '', title = '32. ', inline = 'r16', group = section04) | |
var symbol33 = input.symbol(defval = '', title = '33. ', inline = 'r17', group = section04) | |
var symbol34 = input.symbol(defval = '', title = '34. ', inline = 'r17', group = section04) | |
var symbol35 = input.symbol(defval = '', title = '35. ', inline = 'r18', group = section04) | |
var symbol36 = input.symbol(defval = '', title = '36. ', inline = 'r18', group = section04) | |
var symbol37 = input.symbol(defval = '', title = '37. ', inline = 'r19', group = section04) | |
var symbol38 = input.symbol(defval = '', title = '38. ', inline = 'r19', group = section04) | |
var symbol39 = input.symbol(defval = '', title = '39. ', inline = 'r20', group = section04) | |
var symbol40 = input.symbol(defval = '', title = '40. ', inline = 'r20', group = section04) | |
var smblNote1 = 'Please note that once selected, symbols cannot be entirely removed from this form; ' | |
var smblNote2 = smblNote1 + 'they can only be replaced with other symbols. To remove one or more symbols, you have two ' | |
var smblNote3 = smblNote2 + 'options: either click \"reset settings\" or utilize this feature to exhibit the total ' | |
var smblNote4 = smblNote3 + 'desired number of symbols, regardless of the existing count on the watchlist form.' | |
var totSym = input.int(defval = 40, title = 'How many watchlist symbols to display?', minval = 0, maxval = 40, | |
tooltip = smblNote4, group = section04) | |
// } | |
// ::Functions:: { | |
displayTf() => | |
mp = customTf ? tfMultiplier : timeframe.multiplier | |
ut = customTf ? unit : timeframe.period | |
_multiplier = str.format('{0}{1} ', (prevBar ? 'Previous ' : ''), mp) | |
_unit = switch | |
str.endswith(ut, "S") => | |
mp == 1 ? 'Second' : 'Seconds' | |
str.endswith(ut, "D") => | |
mp == 1 ? 'Day' : 'Days' | |
str.endswith(ut, "W") => | |
mp == 1 ? 'Week' : 'Weeks' | |
str.endswith(ut, "M") => | |
mp == 1 ? 'Month' : 'Months' | |
=> | |
mp == 1 ? 'Minute' : 'Minutes' | |
_multiplier + _unit | |
displayValues() => | |
switch displayValues | |
'regular' => na | |
'change' => 'change_format' | |
'change percent' => 'change_percent_format' | |
getKv(simple string ticker) => | |
t = customTf ? tf : '' | |
p = prevBar ? 1 : 0 | |
key = syminfo.ticker(ticker) | |
tckr = ticker.new(syminfo.prefix(ticker), key, syminfo.session) | |
[cl, cl1, vl, vl1] = request.security(tckr, t,[close[p], close[p + 1], volume[p], volume[p + 1]], | |
ignore_invalid_symbol=true) | |
cls = mltd.kv(key, cl, cl1, format='{0,number,currency}') | |
vol = mltd.kv(key, vl, vl1, format=tools.numCompact(vl), changeFormat=tools.numCompact(vl-vl1)) | |
[cls, vol] | |
// } | |
if barstate.islast | |
// ::Data:: { | |
closeSymbols = array.new<mltd.kv>() | |
volumeSymbols = array.new<mltd.kv>() | |
if tools._bool(symbol01) and totSym >= 1 | |
[_close, _vol] = getKv(symbol01) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol02) and totSym >= 2 | |
[_close, _vol] = getKv(symbol02) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol03) and totSym >= 3 | |
[_close, _vol] = getKv(symbol03) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol04) and totSym >= 4 | |
[_close, _vol] = getKv(symbol04) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol05) and totSym >= 5 | |
[_close, _vol] = getKv(symbol05) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol06) and totSym >= 6 | |
[_close, _vol] = getKv(symbol06) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol07) and totSym >= 7 | |
[_close, _vol] = getKv(symbol07) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol08) and totSym >= 8 | |
[_close, _vol] = getKv(symbol08) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol09) and totSym >= 9 | |
[_close, _vol] = getKv(symbol09) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol10) and totSym >= 10 | |
[_close, _vol] = getKv(symbol10) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol11) and totSym >= 11 | |
[_close, _vol] = getKv(symbol11) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol12) and totSym >= 12 | |
[_close, _vol] = getKv(symbol12) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol13) and totSym >= 13 | |
[_close, _vol] = getKv(symbol13) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol14) and totSym >= 14 | |
[_close, _vol] = getKv(symbol14) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol15) and totSym >= 15 | |
[_close, _vol] = getKv(symbol15) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol16) and totSym >= 16 | |
[_close, _vol] = getKv(symbol16) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol17) and totSym >= 17 | |
[_close, _vol] = getKv(symbol17) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol18) and totSym >= 18 | |
[_close, _vol] = getKv(symbol18) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol19) and totSym >= 19 | |
[_close, _vol] = getKv(symbol19) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol20) and totSym >= 20 | |
[_close, _vol] = getKv(symbol20) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol21) and totSym >= 21 | |
[_close, _vol] = getKv(symbol21) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol22) and totSym >= 22 | |
[_close, _vol] = getKv(symbol22) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol23) and totSym >= 23 | |
[_close, _vol] = getKv(symbol23) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol24) and totSym >= 24 | |
[_close, _vol] = getKv(symbol24) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol25) and totSym >= 25 | |
[_close, _vol] = getKv(symbol25) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol26) and totSym >= 26 | |
[_close, _vol] = getKv(symbol26) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol27) and totSym >= 27 | |
[_close, _vol] = getKv(symbol27) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol28) and totSym >= 28 | |
[_close, _vol] = getKv(symbol28) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol29) and totSym >= 29 | |
[_close, _vol] = getKv(symbol29) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol30) and totSym >= 30 | |
[_close, _vol] = getKv(symbol30) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol31) and totSym >= 31 | |
[_close, _vol] = getKv(symbol31) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol32) and totSym >= 32 | |
[_close, _vol] = getKv(symbol32) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol33) and totSym >= 33 | |
[_close, _vol] = getKv(symbol33) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol34) and totSym >= 34 | |
[_close, _vol] = getKv(symbol34) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol35) and totSym >= 35 | |
[_close, _vol] = getKv(symbol35) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol36) and totSym >= 36 | |
[_close, _vol] = getKv(symbol36) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol37) and totSym >= 37 | |
[_close, _vol] = getKv(symbol37) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol38) and totSym >= 38 | |
[_close, _vol] = getKv(symbol38) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol39) and totSym >= 39 | |
[_close, _vol] = getKv(symbol39) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
if tools._bool(symbol40) and totSym >= 40 | |
[_close, _vol] = getKv(symbol40) | |
closeSymbols.push(_close) | |
volumeSymbols.push(_vol) | |
// } | |
if closeSymbols.size() > 0 | |
// Styles. | |
tbs = prnt.tableStyle.new(bgColor=na, frameColor=na, borderWidth = cellPad) | |
hs = prnt.headerStyle.new(bgColor=na, textHalign=headerAlign, textColor=headerColor, textSize=headerSize) | |
ts = prnt.titleStyle.new(top=true, textHalign=titleAlign, textColor=titleColor, textSize=titleSize, bgColor=na) | |
cs = prnt.cellStyle.new(horizontal=orientation, textHalign=textAlign, textSize=textSize) | |
kcs = prnt.cellStyle.new(textHalign=keyAlign, textSize=keySize) | |
// Initialize the printer. | |
hd = (asc ? ' ↑ ': ' ↓ ') + displayTf() | |
header = headerSize != 'hide' ? hd : na | |
printer = prnt.printer(header=header, tableStyle=tbs, headerStyle=hs, titleStyle=ts, keyCellStyle=kcs, | |
cellStyle=cs, stack=orientation, loc=displayLoc) | |
// Close. | |
d2dClose = mltd.data2d(closeSymbols, sort=true, asc=asc, sortByChange=true) | |
closeDv = d2dClose.changeValues(true) | |
closeMax = closeDv.max() | |
closeMin = closeDv.min() | |
d2dCloseStyles = map.new<string, prnt.cellStyle>() | |
d2dCloseDv = map.new<string, prnt.dvs>() | |
closeKeyStyle = prnt.cellStyle.copy(printer.keyCellStyle) | |
closeKeyStyle.dynamicColor := prnt.dynamicColor.new(numberUp=closeMax, numberDown=closeMin, | |
offsetItem=keyOffset, offsetColor=keyOffsetColor) | |
closeKeyStyle.gradient := true | |
closeValStyle = prnt.cellStyle.copy(cs) | |
closeValStyle.dynamicColor := prnt.dynamicColor.new(numberUp=closeMax, numberDown=closeMin, | |
offsetItem=textOffset, offsetColor=textOffsetColor) | |
closeValStyle.gradient := true | |
d2dCloseStyles.put('key', closeKeyStyle) | |
d2dCloseStyles.put('val', closeValStyle) | |
d2dCloseDv.put('val', closeDv.dvs()) | |
ctd = headerSize == 'hide' ? (asc ? '↑ ': '↓ ') + (prevBar ? ' Previous ' : na): na | |
ctd := ctd + 'Close' + (displayValues != 'regular' ? (displayValues == 'change' ? ' (-)' : ' (%)') : na) | |
closeTitle = titleSize != 'hide' ? ctd : na | |
printer.print(d2dClose, title=closeTitle, displayValues=displayValues(), dynamicValues=d2dCloseDv, | |
styles=d2dCloseStyles, dynamicKey=true) | |
// Volume. | |
if showVol | |
d2dVolume = mltd.data2d(volumeSymbols, sort=true, asc=asc, sortByChange=true) | |
volumeDv = d2dVolume.changeValues(true) | |
volumeMax = volumeDv.max() | |
volumeMin = volumeDv.min() | |
d2dVolumeStyles = map.new<string, prnt.cellStyle>() | |
d2dVolumeDv = map.new<string, prnt.dvs>() | |
volumeKeyStyle = prnt.cellStyle.copy(printer.keyCellStyle) | |
volumeKeyStyle.dynamicColor := prnt.dynamicColor.new(numberUp=volumeMax, numberDown=volumeMin, | |
offsetItem=keyOffset, offsetColor=keyOffsetColor) | |
volumeKeyStyle.gradient := true | |
volumeValStyle = prnt.cellStyle.copy(cs) | |
volumeValStyle.dynamicColor := prnt.dynamicColor.new(numberUp=volumeMax, numberDown=volumeMin, | |
offsetItem=textOffset, offsetColor=textOffsetColor) | |
volumeValStyle.gradient := true | |
d2dVolumeStyles.put('key', volumeKeyStyle) | |
d2dVolumeStyles.put('val', volumeValStyle) | |
d2dVolumeDv.put('val', volumeDv.dvs()) | |
vtd = headerSize == 'hide' ? (asc ? '↑ ': '↓ ') + (prevBar ? ' Previous ' : na): na | |
vtd := vtd + 'Volume' + (displayValues != 'regular' ? (displayValues == 'change' ? ' (-)' : ' (%)') : na) | |
volumeTitle = titleSize != 'hide' ? vtd : na | |
printer.print(d2dVolume, title=volumeTitle, displayValues=displayValues(), dynamicValues=d2dVolumeDv, | |
styles=d2dVolumeStyles, dynamicKey=true) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment