This simple script shows an image located in the Files app.
// Pass the full filename (including file extension) as a parameter when configuring the widget.
// The script assumes that the image is located in the root of Scriptable's folder in iCloud Drive.
let filename = args.widgetParameter
let img = getImage(filename)
let widget = createWidget(img)
if (!config.runsInWidget) {
widget.presentMedium()
}
Script.setWidget(widget)
Script.complete()
function createWidget(img) {
let w = new ListWidget()
w.setPadding(0, 0, 0, 0)
let wimg = w.addImage(img)
wimg.centerAlignImage()
return w
}
function getImage(filename) {
let fm = FileManager.iCloud()
let dir = fm.documentsDirectory()
let filePath = fm.joinPath(dir, filename)
return fm.readImage(filePath)
}
I have once noticed that. More often I have noticed a large widget showing it’s first line of text repeated at the bottom over another line. It doesn’t happen when run from the Scriptable app.
I noticed that apps like Maps and Weather now have new permission under privacy: While using the App or Widgets. Maybe Scriptable is missing this permission?
Update: Using non-beta versions of iOS 14.0 and Scriptable 1.5 (161)
Update 2: Here is a minimal script to demonstrate the issue:
let widget = await createWidget()
if (!config.runsInWidget) {
await widget.presentSmall()
}
Script.setWidget(widget)
Script.complete()
async function createWidget(items) {
let w = new ListWidget()
const latLong = await Location.current()
const loc = `${latLong.latitude}/${latLong.longitude}`
w.addText(loc)
return w
}
will there be a way to define multiple tap targets for the medium and large widgets?
maybe something like WidgetText.url property (for consistency with the ListWidget.url property)?
I was thinking it would be a fun project to create a widget that rotated through a Photo folder. Does scriptable allow for getting images from a folder in Photos?
I am trying to create a widget that displays my reminders for today (and tomorrow if there is space left) I have based it on a previous post from here. Everything is working fine except: I want the widget to change appearance the i am in dark mode. It works when running the script inside the app, but it dose not work outside of scriptable, when it is used as a widget.
The code I am using to switch between dark and light looks something like this:
if ( Device.isUsingDarkAppearance() ) {
// dark mode appearance settings
} else {
// normal settings
};
I have also included the full code which looks like this:
var MAX_TASKS_SHOWN = 8;
const NOW = new Date();
const TITLE_FONT = Font.boldSystemFont(11);
const BODY_FONT = Font.semiboldRoundedSystemFont(12);
if ( Device.isUsingDarkAppearance() ) {
var BG_COLOR = new Color("#1C1C1E");
var TITLE_COLOR = new Color("#9E9E9E");
var OVERDUE_COLOR = new Color("#FE4639");
var TASK_COLOR = new Color("#FFFFFF");
var NO_OVERDUE_COLOR = new Color("#2FD15D");
} else {
var BG_COLOR = Color.white();
var TITLE_COLOR = new Color("#8C8C8C");
var OVERDUE_COLOR = new Color("#FD3F32");
var TASK_COLOR = new Color("#000000");
var NO_OVERDUE_COLOR = new Color("#13C759");
};
const today = new Date()
const tomorrow = new Date(today)
tomorrow.setDate(tomorrow.getDate() + 1)
tomorrow.setHours(0,0,0,0)
const tomorrow2 = new Date(today)
tomorrow2.setDate(tomorrow2.getDate() + 2)
tomorrow2.setHours(0,0,0,0)
//
// Utils
//
const compareReminderDuedates = (reminderA, reminderB) =>
reminderA.dueDate - reminderB.dueDate;
const sortRemindersByDuedateAsc = reminders =>
reminders.sort(compareReminderDuedates);
/** Round date down to 00:00 */
const stripTime = date => new Date(new Date(date).setHours(0, 0, 0, 0));
/** d1 - d2: will be negative if d2 > d1 */
const daysBetween = (d1, d2) => {
const differenceMs = stripTime(d1).getTime() - stripTime(d2).getTime();
return Math.floor(differenceMs / 86400000);
};
const getOverdueTasks = async () => {
const all = await Reminder.allIncomplete();
return sortRemindersByDuedateAsc(
all.filter(task => task.dueDate && task.dueDate < today)
);
};
const getTodayTasks = async () => {
const all = await Reminder.allIncomplete();
return sortRemindersByDuedateAsc(
all.filter(task => today <= task.dueDate && task.dueDate < tomorrow)
);
};
const getTomorrowTasks = async () => {
const all = await Reminder.allIncomplete();
return sortRemindersByDuedateAsc(
all.filter(task => tomorrow <= task.dueDate && task.dueDate < tomorrow2)
);
};
// overdue tasks
const getWidget = async () => {
const overdueTasks = await getOverdueTasks();
const todayTasks = await getTodayTasks();
const tomorrowTasks = await getTomorrowTasks();
const widget = new ListWidget();
widget.backgroundColor = BG_COLOR;
widget.addSpacer(0);
const dueNum = overdueTasks.length + todayTasks.length
if (dueNum) {
var title = widget.addText(`HEUTE ${dueNum}`);
}else{
var title = widget.addText(`HEUTE`);
}
title.textColor = TITLE_COLOR;
title.font = TITLE_FONT;
if (overdueTasks.length) {
overdueTasks.slice(0, MAX_TASKS_SHOWN).forEach(({ title, dueDate }) => {
const task = widget.addText(`| ${title}`);
task.textColor = OVERDUE_COLOR;
task.font = BODY_FONT;
task.lineLimit = 1;
});
} else {
}
MAX_TASKS_SHOWN = MAX_TASKS_SHOWN - overdueTasks.length
if (MAX_TASKS_SHOWN > 0) {
if (todayTasks.length) {
todayTasks.slice(0, MAX_TASKS_SHOWN).forEach(({ title, dueDate }) => {
const task = widget.addText(`| ${title}`);
task.textColor = TASK_COLOR;
task.font = BODY_FONT;
task.lineLimit = 1;
});
} else {}
} else {}
if (dueNum) {
} else {
const noTasks = widget.addText("Alles erledigt.");
noTasks.textColor = NO_OVERDUE_COLOR;
noTasks.font = BODY_FONT;
}
// tomorrow
// const tomorrowTasks = Reminder.allDueTomorrow();
widget.addSpacer(5);
MAX_TASKS_SHOWN = MAX_TASKS_SHOWN - todayTasks.length - 2
if (MAX_TASKS_SHOWN > 0){
if (tomorrowTasks.length) {
const title = widget.addText(`MORGEN ${tomorrowTasks.length}`);
title.textColor = TITLE_COLOR;
title.font = TITLE_FONT;
tomorrowTasks.slice(0, MAX_TASKS_SHOWN).forEach(({ title, dueDate }) => {
const task = widget.addText(`| ${title}`);
task.textColor = TASK_COLOR;
task.font = BODY_FONT;
task.lineLimit = 1;
});
} else {}
} else {}
widget.addSpacer();
return widget;
};
(async () => {
const widget = await getWidget();
if (config.runsInWidget) {
Script.setWidget(widget);
Script.complete();
} else await widget.presentSmall();
})();
A combination script and couple of shortcuts to check on CitiBike docks in particular locations (where I am, where I am going). This is the bike share system in New York City. Would probably work for other systems that use the GBFS standard. Will probably add some more interesting functionality for on-tap.