Hey, first post here so let me know if there’s a better way to share scripts, but I figured since I was working on something like this I could share what I’ve done so far. Definitely still WIP but it’s good enough to be usable so maybe some ideas will be useful.
First, the widget. It accepts text that it simply displays, but it also optionally allows for the addition of json at the beginning to change settings. So you could have the parameter be
Display this text
or it could be
{“fontSize”:8}Display lots of text
or more complicated
{“fontSize”:11,“fontColor”:“#c2c2c2”,“bgColor”:“#004d65”,“bgGradient”:“37”}Multiple\nLine\nText
let text = args.widgetParameter
let widgetConfig = {
fontSize:12,
fontColor:"#ffffff",
bgColor:"#111111",
bgGradient:"88"
}
if (text == null) {
text = "Change the widget parameter to change this text. Use the TextWidgetSetup script to change additional settings"
}
let paramConfig = text.match("^\\{.*\\}")
console.log(paramConfig)
if (paramConfig != null) {
paramConfig = paramConfig[0]
let paramConfigJ = JSON.parse(paramConfig)
copyIfNotNull(widgetConfig, paramConfigJ, "fontSize")
copyIfNotNull(widgetConfig, paramConfigJ, "fontColor")
copyIfNotNull(widgetConfig, paramConfigJ, "align")
copyIfNotNull(widgetConfig, paramConfigJ, "bgColor")
copyIfNotNull(widgetConfig, paramConfigJ, "bgGradient")
text = text.substring(paramConfig.length)
}
text = text.replaceAll("\\n", "\n")
let widget = await createWidget()
// Check if the script is running in
// a widget. If not, show a preview of
// the widget to easier debug it.
if (!config.runsInWidget) {
await widget.presentSmall()
}
// Tell the system to show the widget.
Script.setWidget(widget)
Script.complete()
async function createWidget() {
let gradient = new LinearGradient()
gradient.locations = [0, 1]
let cTop = widgetConfig.bgColor
let cBottom = cTop+widgetConfig.bgGradient
gradient.colors = [
new Color(cTop),
new Color(cBottom)
]
let w = new ListWidget()
w.backgroundGradient = gradient
w.addSpacer()
let contents= w.addText(text)
contents.font = Font.lightRoundedSystemFont(widgetConfig.fontSize)
contents.textColor = new Color(widgetConfig.fontColor)
w.addSpacer()
//top, leading, bottom, trailing
w.setPadding(0, 8, 0, 8)
return w
}
function copyIfNotNull(to,from,prop) {
let fromProp = from[prop]
if (fromProp != null) {
to[prop] = fromProp
}
}
Then I made this (still very rough) UI script that allows generating the whole parameter with all configurable values
let webView = new WebView()
webView.loadHTML(getHtml())
webView.present(true)
function getHtml() {
return `
<!DOCTYPE html>
<html>
<head>
<title>Color Picker</title>
<style>
body {
background-color: #000000;
font-family: HelveticaNeue-Bold;
color: white;
}
label {
font-size: 40pt;
}
input {
font-size: 40pt;
}
#backgroundgradient {
background-color: blue;
}
.textinput {
font-size: 40pt;
background-color: #555555;
color: #ffffff;
}
.colorinput {
height: 40pt;
width: 40pt;
}
.rangewrapper {
background-color: #555555;
display: inline-block;
}
.rangeinput {
height: 40pt;
}
.colorwrapper {
background-color: #555555;
display: inline-block;
}
</style>
</head>
<body>
<div>
<label for="contentsinput">Contents:</label><br>
<textarea id="contentsinput" class="textinput" oninput="update()" onpropertychange="update()"></textarea>
</div>
<div>
<label for="fontsize">Font size:</label>
<input type="number" class="textinput" min="1" max="1000" id="fontsize" oninput="updateFontSize()" onpropertychange="updateFontSize()">
</div>
<div>
<label for="fontcolor">Font color: </label>
<span class=colorwrapper>
<input type="color" class="colorinput" id="fontcolor" onchange="updateFontColor()" value="#ffffff">
</span>
</div>
<div>
<label for="backgroundcolor">Background color: </label>
<span class=colorwrapper>
<input type="color" class="colorinput" id="backgroundcolor" onchange="updateBackgroundColor()" value="#000000">
</span>
</div>
<div>
<label for="backgroundgradient">Background gradient: </label>
<span class=rangewrapper>
<input type="range" class="rangeinput" id="backgroundgradientrange" min="0" max="255" oninput="updateBackgroundGradientRange()" value="0">
</span>
<input type="number" class="textinput" min="0" max="255" id="backgroundgradientdisplay" oninput="updateBackgroundGradientDisplay()" onpropertychange="updateBackgroundGradientDisplay()">
</div>
<div>copy the entire text below and paste into the widget parameter for the TextWidget:</div>
<div class="result" id="result"></div>
</body>
<script>
function updateFontSize() {
document.getElementById('fontsize').classList.add('edited')
update()
}
function updateFontColor() {
document.getElementById('fontcolor').classList.add('edited')
update()
}
function updateBackgroundColor() {
document.getElementById('backgroundcolor').classList.add('edited')
update()
}
function updateBackgroundGradientRange() {
document.getElementById('backgroundgradientdisplay').classList.add('edited')
document.getElementById('backgroundgradientdisplay').value = document.getElementById('backgroundgradientrange').value
update()
}
function updateBackgroundGradientDisplay() {
let display = document.getElementById('backgroundgradientdisplay')
let range = document.getElementById('backgroundgradientrange')
display.classList.add('edited')
if (display.value == ""){
range.value = 0
} else {
range.value = display.value
}
update()
}
function update() {
let valToCopy = "";
valToCopy = addPropertyToJson(valToCopy, 'fontsize', 'fontSize')
valToCopy = addPropertyToJson(valToCopy, 'fontcolor', 'fontColor', addQuotes)
valToCopy = addPropertyToJson(valToCopy, 'backgroundcolor', 'bgColor', addQuotes)
valToCopy = addPropertyToJson(valToCopy, 'backgroundgradientdisplay', 'bgGradient', convertGradient)
if (valToCopy != "") {
valToCopy +='}'
}
valToCopy += document.getElementById('contentsinput').value.replaceAll("\\n", "\\\\n")
document.getElementById('result').innerHTML = valToCopy
}
function convertGradient(gradient) {
gradient = parseInt(gradient)
gradient = 255 - gradient
return addQuotes(gradient.toString(16))
}
function addQuotes(value) {
return '"' + value + '"'
}
function addPropertyToJson(currentJson, tagId, propertyName, modification) {
let htmlTag = document.getElementById(tagId)
if (!htmlTag.classList.contains('edited')) {
return currentJson
}
let value = htmlTag.value
if (value == "") {
return currentJson
}
if (modification != null) {
value = modification(value)
}
let toAdd = '"'+ propertyName + '":' + value
if (currentJson == "") {
toAdd = "{" + toAdd
} else {
toAdd = "," + toAdd
}
return currentJson + toAdd
}
</script>
</html>
`
}
It’s similar to the previous suggestion, using one script to create a configure some settings and another script to display the widget, but this uses copy/paste instead of saving/retrieving a file. But I think the principle is similar and adaptable and hopefully this gives some practical ideas for getting through the main hurdles of setting things up to parse json from the widget parameter, have default values, etc. But yeah a built-in way to specify that a widget accepts multiple parameters for the user to input would be nice for sure.