├── AppleDevNews2 ├── strings │ ├── en.strings │ └── zh-Hans.strings ├── main.js ├── scripts │ ├── constants │ │ ├── fonts.js │ │ └── colors.js │ ├── widget │ │ ├── empty.js │ │ ├── small.js │ │ └── medium.js │ ├── app.js │ └── api.js ├── assets │ └── icon.png ├── README.md └── config.json ├── WidgetDoodles ├── dist.zip ├── assets │ └── icon.png ├── main.js ├── scripts │ ├── help.js │ ├── widget.js │ ├── app.js │ ├── util.js │ └── canvas.js ├── README_CN.md ├── strings │ ├── zh-Hans.strings │ └── en.strings ├── README.md └── config.json ├── QRCode.js ├── clock.js ├── xkcd.js └── IsItFriday.js /AppleDevNews2/strings/en.strings: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /AppleDevNews2/strings/zh-Hans.strings: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /AppleDevNews2/main.js: -------------------------------------------------------------------------------- 1 | const app = require("./scripts/app"); 2 | app.init(); -------------------------------------------------------------------------------- /WidgetDoodles/dist.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyanzhong/jsbox-widgets/HEAD/WidgetDoodles/dist.zip -------------------------------------------------------------------------------- /AppleDevNews2/scripts/constants/fonts.js: -------------------------------------------------------------------------------- 1 | exports.primary = { size: 19 }; 2 | exports.secondary = { size: 13 }; -------------------------------------------------------------------------------- /AppleDevNews2/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyanzhong/jsbox-widgets/HEAD/AppleDevNews2/assets/icon.png -------------------------------------------------------------------------------- /WidgetDoodles/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyanzhong/jsbox-widgets/HEAD/WidgetDoodles/assets/icon.png -------------------------------------------------------------------------------- /AppleDevNews2/scripts/constants/colors.js: -------------------------------------------------------------------------------- 1 | exports.systemBlue = $color("#007aff", "#0a84ff"); 2 | exports.secondary = $color("#666666", "#acacac"); -------------------------------------------------------------------------------- /AppleDevNews2/scripts/widget/empty.js: -------------------------------------------------------------------------------- 1 | module.exports = () => { 2 | return { 3 | type: "text", 4 | props: { 5 | text: "🎉 No News" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /WidgetDoodles/main.js: -------------------------------------------------------------------------------- 1 | if ($app.env === $env.widget) { 2 | const widget = require("./scripts/widget"); 3 | widget.init(); 4 | } else { 5 | const app = require("./scripts/app"); 6 | app.init(); 7 | } -------------------------------------------------------------------------------- /AppleDevNews2/README.md: -------------------------------------------------------------------------------- 1 | # AppleDevNews2 2 | 3 | A [JSBox](https://apps.apple.com/us/app/id1312014438) script for https://developer.apple.com/news/ 4 | 5 | > This is a demo scrpit for the [home screen widget](https://docs.xteko.com/#/en/home-widget/intro) support. -------------------------------------------------------------------------------- /WidgetDoodles/scripts/help.js: -------------------------------------------------------------------------------- 1 | exports.show = () => { 2 | $ui.push({ 3 | views: [ 4 | { 5 | type: "markdown", 6 | props: { 7 | content: $file.read($l10n("README_FILE")).string 8 | }, 9 | layout: $layout.fill 10 | } 11 | ] 12 | }); 13 | } -------------------------------------------------------------------------------- /WidgetDoodles/README_CN.md: -------------------------------------------------------------------------------- 1 | # WidgetDoodles 2 | 3 | [[JSBox](https://apps.apple.com/cn/app/id1312014438) 脚本] 使用 iOS 14 小组件功能,实现在桌面上放置您的涂鸦作品。 4 | 5 | # 配置 6 | 7 | - 创建涂鸦 8 | - 复制涂鸦到剪贴板 9 | - 添加小组件到桌面([教程](https://support.apple.com/zh-cn/HT207122)) 10 | - 编辑小组件,选择 WidgetDoodles 作为脚本 11 | - 将涂鸦粘贴到“输入参数”那一栏 12 | - 完成 -------------------------------------------------------------------------------- /WidgetDoodles/strings/zh-Hans.strings: -------------------------------------------------------------------------------- 1 | "README_FILE" = "README_CN.md"; 2 | 3 | "CREATE_SMALL_WIDGET" = "创建 2 * 2 小组件"; 4 | "CREATE_MEDIUM_WIDGET" = "创建 2 * 4 小组件"; 5 | "CREATE_LARGE_WIDGET" = "创建 4 * 4 小组件"; 6 | "HOW_TO_CONFIGURE" = "如何配置"; 7 | 8 | "CLOSE" = "关闭"; 9 | "CLEAR" = "清除"; 10 | "COPIED" = "已复制"; 11 | "LEARN_MORE" = "请参考“如何配置”教程里面的步骤将其添加到桌面。"; 12 | "OK" = "好的"; 13 | "NO_INPUT_VALUE" = "没有输入参数,请参考主应用内的教程。"; -------------------------------------------------------------------------------- /WidgetDoodles/README.md: -------------------------------------------------------------------------------- 1 | # WidgetDoodles 2 | 3 | [[JSBox](https://apps.apple.com/us/app/id1312014438) script] Doodles on your home screen, using iOS 14 widgets. 4 | 5 | # Configuration 6 | 7 | - Create doodles 8 | - Copy the doodles to clipboard 9 | - Add a widget to your home screen ([Guide](https://support.apple.com/en-us/HT207122)) 10 | - Edit the widget, choose WidgetDoodles as the script 11 | - Paste the doodles to the "Input Value" field 12 | - Done -------------------------------------------------------------------------------- /QRCode.js: -------------------------------------------------------------------------------- 1 | const inputValue = $widget.inputValue; 2 | 3 | $widget.setTimeline(ctx => { 4 | if (inputValue) { 5 | return { 6 | type: "image", 7 | props: { 8 | image: $qrcode.encode(inputValue), 9 | resizable: true, 10 | scaledToFit: true, 11 | padding: 12 12 | } 13 | } 14 | } else { 15 | return { 16 | type: "text", 17 | props: { 18 | text: "Please configure the widget input value.", 19 | padding: 15 20 | } 21 | } 22 | } 23 | }); -------------------------------------------------------------------------------- /WidgetDoodles/strings/en.strings: -------------------------------------------------------------------------------- 1 | "README_FILE" = "README.md"; 2 | 3 | "CREATE_SMALL_WIDGET" = "Create for 2 * 2 widget"; 4 | "CREATE_MEDIUM_WIDGET" = "Create 2 * 4 widget"; 5 | "CREATE_LARGE_WIDGET" = "Create 4 * 4 widget"; 6 | "HOW_TO_CONFIGURE" = "How to configure"; 7 | 8 | "CLOSE" = "Close"; 9 | "CLEAR" = "Clear"; 10 | "COPIED" = "Copied"; 11 | "LEARN_MORE" = "To add it to the home screen, please follow up steps in the \"How to configure\" guide."; 12 | "OK" = "OK"; 13 | "NO_INPUT_VALUE" = "No input value, please check guides in the main app."; -------------------------------------------------------------------------------- /AppleDevNews2/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "name": "", 4 | "url": "", 5 | "version": "1.0.0", 6 | "author": "", 7 | "website": "", 8 | "types": 0 9 | }, 10 | "settings": { 11 | "theme": "auto", 12 | "minSDKVer": "2.12.0", 13 | "minOSVer": "14.0.0", 14 | "idleTimerDisabled": false, 15 | "autoKeyboardEnabled": false, 16 | "keyboardToolbarEnabled": false, 17 | "rotateDisabled": false 18 | }, 19 | "widget": { 20 | "height": 0, 21 | "staticSize": false, 22 | "tintColor": "", 23 | "iconColor": "" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /WidgetDoodles/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "name": "", 4 | "url": "", 5 | "version": "1.0.0", 6 | "author": "", 7 | "website": "", 8 | "types": 0 9 | }, 10 | "settings": { 11 | "theme": "auto", 12 | "minSDKVer": "2.12.0", 13 | "minOSVer": "14.0.0", 14 | "idleTimerDisabled": false, 15 | "autoKeyboardEnabled": false, 16 | "keyboardToolbarEnabled": false, 17 | "rotateDisabled": false 18 | }, 19 | "widget": { 20 | "height": 0, 21 | "staticSize": false, 22 | "tintColor": "", 23 | "iconColor": "" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /AppleDevNews2/scripts/app.js: -------------------------------------------------------------------------------- 1 | const api = require("./api"); 2 | const emptyWidget = require("./widget/empty"); 3 | const smallWidget = require("./widget/small"); 4 | const mediumWidget = require("./widget/medium"); 5 | 6 | exports.init = async() => { 7 | const items = await api.fetch(); 8 | $widget.setTimeline(ctx => { 9 | if (items.length === 0) { 10 | return emptyWidget(); 11 | } 12 | switch (ctx.family) { 13 | case $widgetFamily.small: 14 | return smallWidget(items); 15 | case $widgetFamily.medium: 16 | return mediumWidget(items, 3); 17 | case $widgetFamily.large: 18 | return mediumWidget(items, 6); 19 | } 20 | }); 21 | } -------------------------------------------------------------------------------- /clock.js: -------------------------------------------------------------------------------- 1 | const midnight = new Date(); 2 | midnight.setHours(0, 0, 0, 0); 3 | 4 | const oneDayInMillis = 60 * 60 * 24 * 1000; 5 | const expireDate = new Date(midnight.getTime() + oneDayInMillis); 6 | 7 | $widget.setTimeline({ 8 | policy: { 9 | afterDate: expireDate 10 | }, 11 | render: ctx => { 12 | return { 13 | type: "text", 14 | props: { 15 | date: midnight, 16 | style: "timer", 17 | bold: true, 18 | font: { 19 | size: 120, 20 | monospaced: true 21 | }, 22 | frame: { 23 | maxWidth: ctx.displaySize.width - 30 24 | }, 25 | lineLimit: 1, 26 | minimumScaleFactor: 0.01 27 | } 28 | } 29 | } 30 | }); -------------------------------------------------------------------------------- /WidgetDoodles/scripts/widget.js: -------------------------------------------------------------------------------- 1 | const util = require("./util"); 2 | 3 | exports.init = () => { 4 | const inputValue = $widget.inputValue || ""; 5 | const uri = `data:image/jpeg;base64,${inputValue}`; 6 | $widget.setTimeline(ctx => { 7 | const cacheKey = util.sizeCacheKey(ctx.family); 8 | const cacheValue = ctx.displaySize; 9 | $cache.set(cacheKey, cacheValue); 10 | if (inputValue.length > 0) { 11 | return { 12 | type: "image", 13 | props: { 14 | uri: uri, 15 | resizable: true, 16 | scaledToFill: true, 17 | } 18 | } 19 | } else { 20 | return { 21 | type: "text", 22 | props: { 23 | text: $l10n("NO_INPUT_VALUE"), 24 | padding: 16 25 | } 26 | } 27 | } 28 | }); 29 | } -------------------------------------------------------------------------------- /AppleDevNews2/scripts/api.js: -------------------------------------------------------------------------------- 1 | const parser = require("./vendor/rss-parser"); 2 | const endpoint = "https://developer.apple.com/news/rss/news.rss"; 3 | const cachePath = "assets/cache.json"; 4 | 5 | function requestFailed(resp) { 6 | return resp == null || resp.response == null || resp.response.statusCode != 200; 7 | } 8 | 9 | function readCache() { 10 | const data = $file.read(cachePath); 11 | if (data) { 12 | return JSON.parse(data.string); 13 | } else { 14 | return []; 15 | } 16 | } 17 | 18 | async function fetch() { 19 | const resp = await $http.get(endpoint); 20 | if (requestFailed(resp)) { 21 | return readCache(); 22 | } 23 | 24 | const rss = await parser.parse(resp.data); 25 | const items = rss.items || []; 26 | 27 | if (rss.items) { 28 | $file.write({ 29 | path: cachePath, 30 | data: $data({ 31 | "string": JSON.stringify(rss.items) 32 | }) 33 | }); 34 | } 35 | 36 | return items; 37 | } 38 | 39 | exports.fetch = fetch; -------------------------------------------------------------------------------- /WidgetDoodles/scripts/app.js: -------------------------------------------------------------------------------- 1 | exports.init = () => { 2 | 3 | $ui.render({ 4 | props: { 5 | title: "WidgetDoodles" 6 | }, 7 | views: [ 8 | { 9 | type: "list", 10 | props: { 11 | style: 2, 12 | data: [ 13 | { 14 | rows: [ 15 | $l10n("CREATE_SMALL_WIDGET"), 16 | $l10n("CREATE_MEDIUM_WIDGET"), 17 | $l10n("CREATE_LARGE_WIDGET") 18 | ] 19 | }, 20 | { 21 | rows: [$l10n("HOW_TO_CONFIGURE")] 22 | } 23 | ] 24 | }, 25 | layout: $layout.fill, 26 | events: { 27 | didSelect: (_, indexPath) => { 28 | if (indexPath.section === 1) { 29 | showHelp(); 30 | } else { 31 | presentCanvas(indexPath.row); 32 | } 33 | } 34 | } 35 | } 36 | ] 37 | }); 38 | } 39 | 40 | function presentCanvas(family) { 41 | const canvas = require("./canvas"); 42 | canvas.present(family); 43 | } 44 | 45 | function showHelp() { 46 | const help = require("./help"); 47 | help.show(); 48 | } -------------------------------------------------------------------------------- /xkcd.js: -------------------------------------------------------------------------------- 1 | async function getMaxNum() { 2 | let result = await $http.get("https://xkcd.com/info.0.json"); 3 | return result.data.num; 4 | } 5 | 6 | function getRandomInt(min, max) { 7 | return Math.floor(Math.random() * (max - min + 1)) + min; 8 | } 9 | 10 | function requestFailed(resp) { 11 | return resp == null || resp.response == null || resp.response.statusCode != 200; 12 | } 13 | 14 | async function fetch() { 15 | const cache = $cache.get("image"); 16 | const maxNum = await getMaxNum(); 17 | const num = getRandomInt(1, maxNum); 18 | const json = await $http.get(`https://xkcd.com/${num}/info.0.json`); 19 | if (requestFailed(json)) { 20 | return cache; 21 | } 22 | 23 | const file = await $http.download(json.data.img); 24 | if (requestFailed(file)) { 25 | return cache; 26 | } 27 | 28 | const image = file.data.image; 29 | if (image) { 30 | $cache.set("image", image); 31 | } 32 | 33 | return image; 34 | } 35 | 36 | const image = await fetch(); 37 | $widget.setTimeline(ctx => { 38 | return { 39 | type: "image", 40 | props: { 41 | image: image, 42 | resizable: true, 43 | scaledToFit: true 44 | } 45 | } 46 | }); -------------------------------------------------------------------------------- /WidgetDoodles/scripts/util.js: -------------------------------------------------------------------------------- 1 | const smallPadding = 22; 2 | const largePadding = 27; 3 | const isPad = $device.isIpad; 4 | const maxWidth = Math.min($device.info.screen.width, $device.info.screen.height); 5 | 6 | const smallLength = (() => { 7 | if (isPad) { 8 | return 155; 9 | } else { 10 | return (maxWidth - 2 * largePadding - smallPadding) * 0.5; 11 | } 12 | })(); 13 | 14 | const largeLength = (() => { 15 | if (isPad) { 16 | return 330; 17 | } else { 18 | return maxWidth - 2 * largePadding; 19 | } 20 | })(); 21 | 22 | const widths = [smallLength, largeLength, largeLength]; 23 | const heights = [smallLength, smallLength, largeLength]; 24 | 25 | function sizeCacheKey(family) { 26 | const width = $device.info.screen.width; 27 | const height = $device.info.screen.height; 28 | return `size(${family},${width},${height})`; 29 | } 30 | 31 | exports.sizeForFamily = family => { 32 | const cacheKey = sizeCacheKey(family); 33 | const cacheValue = $cache.get(cacheKey); 34 | if (cacheValue) { 35 | return cacheValue; 36 | } else { 37 | return { 38 | width: widths[family], 39 | height: heights[family], 40 | }; 41 | } 42 | } 43 | 44 | exports.sizeCacheKey = sizeCacheKey; -------------------------------------------------------------------------------- /AppleDevNews2/scripts/widget/small.js: -------------------------------------------------------------------------------- 1 | const colors = require("../constants/colors"); 2 | const fonts = require("../constants/fonts"); 3 | 4 | module.exports = items => { 5 | const news = items[0]; 6 | return { 7 | type: "vstack", 8 | props: { 9 | alignment: "leading", 10 | frame: { 11 | maxWidth: Infinity, 12 | maxHeight: Infinity, 13 | alignment: "topLeading" 14 | }, 15 | padding: 15, 16 | widgetURL: news.link 17 | }, 18 | views: [ 19 | { 20 | type: "hstack", 21 | views: [ 22 | { 23 | type: "color", 24 | props: { 25 | color: colors.systemBlue, 26 | frame: { 27 | width: 6, 28 | height: 64 29 | }, 30 | cornerRadius: 3 31 | } 32 | }, 33 | { 34 | type: "text", 35 | props: { 36 | text: news.title, 37 | font: fonts.primary, 38 | bold: true, 39 | frame: { height: 72 } 40 | } 41 | } 42 | ] 43 | }, 44 | { type: "spacer" }, 45 | { 46 | type: "text", 47 | props: { 48 | text: news.pubDate, 49 | font: fonts.secondary, 50 | color: colors.secondary 51 | } 52 | } 53 | ] 54 | } 55 | } -------------------------------------------------------------------------------- /IsItFriday.js: -------------------------------------------------------------------------------- 1 | $app.strings = { 2 | "en": { 3 | "title": "Is It Friday?", 4 | "msg0": "Nope. 😱", 5 | "msg1": "Nope. 🥱", 6 | "msg2": "Nope. 🧐", 7 | "msg3": "Nope. 😑", 8 | "msg4": "Soon. 🤩", 9 | "msg5": "Yep! 🍻", 10 | "msg6": "Better! 🎉", 11 | }, 12 | "zh-Hans": { 13 | "title": "今天是周五吗?", 14 | "msg0": "不是 😱", 15 | "msg1": "不是 🥱", 16 | "msg2": "不是 🧐", 17 | "msg3": "不是 😑", 18 | "msg4": "快了 🤩", 19 | "msg5": "是的 🍻", 20 | "msg6": "更好 🎉", 21 | } 22 | }; 23 | 24 | const title = $l10n("title"); 25 | const message = $l10n(`msg${(new Date()).getDay()}`); 26 | 27 | $widget.setTimeline(ctx => { 28 | const family = ctx.family; 29 | 30 | if (family === $widgetFamily.accessoryCircular) { 31 | return { 32 | type: "text", 33 | props: { 34 | text: message.slice(-2), 35 | font: $font("bold", 32) 36 | } 37 | } 38 | } 39 | 40 | if (family === $widgetFamily.accessoryInline) { 41 | return { 42 | type: "text", 43 | props: { 44 | text: message 45 | } 46 | } 47 | } 48 | 49 | return { 50 | type: "vstack", 51 | props: { 52 | alignment: $widget.horizontalAlignment.leading, 53 | spacing: 8, 54 | frame: { 55 | maxWidth: Infinity, 56 | maxHeight: Infinity, 57 | alignment: $widget.alignment.leading 58 | } 59 | }, 60 | views: [ 61 | { 62 | type: "text", 63 | props: { 64 | text: title, 65 | color: $color("black") 66 | } 67 | }, 68 | { 69 | type: "text", 70 | props: { 71 | text: message, 72 | font: $font("bold", 20) 73 | } 74 | } 75 | ] 76 | } 77 | }); -------------------------------------------------------------------------------- /AppleDevNews2/scripts/widget/medium.js: -------------------------------------------------------------------------------- 1 | const colors = require("../constants/colors"); 2 | const fonts = require("../constants/fonts"); 3 | 4 | module.exports = (items, lines) => { 5 | return { 6 | type: "vstack", 7 | props: { 8 | alignment: "leading", 9 | spacing: 0, 10 | padding: $insets(5, 15, 5, 15) 11 | }, 12 | views: (() => { 13 | const result = []; 14 | for (let idx=0; idx", 19 | props: ["family", "container", "canvas"], 20 | events: { 21 | "canBecomeFirstResponder": () => { 22 | return true; 23 | }, 24 | "initWithFamily:": family => { 25 | self = self.$super().$init(); 26 | self.$setFamily(family); 27 | return self; 28 | }, 29 | "viewDidLoad": () => { 30 | self.$super().$viewDidLoad(); 31 | self.$view().$setBackgroundColor($color("#F0F0F0", "#2C2C2E")); 32 | 33 | const container = $objc("UIView").$new(); 34 | container.$layer().$setMasksToBounds(true); 35 | container.$layer().$setCornerRadius(24); 36 | container.$layer().$setCornerCurve("continuous"); 37 | 38 | self.$view().$addSubview(container); 39 | self.$setContainer(container); 40 | self.$resetCanvas(); 41 | 42 | const that = self; 43 | const imageBarItem = (name, action) => { 44 | const image = $objc("UIImage").$systemImageNamed(name); 45 | const item = $objc("UIBarButtonItem").$alloc().$initWithImage_style_target_action(image, 0, that, action); 46 | return item; 47 | } 48 | 49 | const titleBarItem = (title, action) => { 50 | return $objc("UIBarButtonItem").$alloc().$initWithTitle_style_target_action(title, 0, that, action); 51 | } 52 | 53 | const shareButton = imageBarItem("square.and.arrow.up", "shareButtonTapped"); 54 | const undoButton = imageBarItem("arrow.uturn.left.circle", "undoButtonTapped"); 55 | const redoButton = imageBarItem("arrow.uturn.right.circle", "redoButtonTapped"); 56 | const rightButtons = NSMutableArray.$new(); 57 | rightButtons.$addObject(shareButton); 58 | rightButtons.$addObject(redoButton); 59 | rightButtons.$addObject(undoButton); 60 | self.$navigationItem().$setRightBarButtonItems(rightButtons); 61 | 62 | const closeButton = titleBarItem($l10n("CLOSE"), "closeButtonTapped"); 63 | const clearButton = titleBarItem($l10n("CLEAR"), "clearButtonTapped"); 64 | const leftButtons = NSMutableArray.$new(); 65 | leftButtons.$addObject(closeButton); 66 | leftButtons.$addObject(clearButton); 67 | self.$navigationItem().$setLeftBarButtonItems(leftButtons); 68 | }, 69 | "viewDidAppear:": animated => { 70 | self.$super().$viewDidAppear(animated); 71 | self.$resetToolbar(); 72 | }, 73 | "viewDidLayoutSubviews": () => { 74 | self.$super().$viewDidLayoutSubviews(); 75 | const maxBounds = self.$view().$bounds(); 76 | const pageWidth = maxBounds.width; 77 | const pageHeight = maxBounds.height - 76; 78 | 79 | const desiredSize = util.sizeForFamily(self.$family()); 80 | const canvasWidth = desiredSize.width; 81 | const canvasHeight = desiredSize.height; 82 | 83 | const container = self.$container(); 84 | container.$setFrame({ 85 | "x": (pageWidth - canvasWidth) * 0.5, 86 | "y": (pageHeight - canvasHeight) * 0.5, 87 | "width": canvasWidth, 88 | "height": canvasHeight 89 | }); 90 | 91 | const canvas = self.$canvas(); 92 | canvas.$setFrame(container.$bounds()); 93 | }, 94 | "resetCanvas": () => { 95 | if (self.$canvas()) { 96 | self.$canvas().$removeFromSuperview(); 97 | } 98 | const canvas = $objc("PKCanvasView").$new(); 99 | self.$container().$addSubview(canvas); 100 | self.$setCanvas(canvas); 101 | self.$view().$setNeedsLayout(); 102 | self.$view().$layoutIfNeeded(); 103 | }, 104 | "resetToolbar": () => { 105 | const window = self.$view().$window(); 106 | const picker = $objc("PKToolPicker").$sharedToolPickerForWindow(window); 107 | self.$setToolbar(picker); 108 | 109 | const canvas = self.$canvas(); 110 | picker.$addObserver(canvas); 111 | picker.$setVisible_forFirstResponder(true, canvas); 112 | canvas.$becomeFirstResponder(); 113 | }, 114 | "closeButtonTapped": () => { 115 | self.$dismissViewControllerAnimated_completion(true, null); 116 | }, 117 | "clearButtonTapped": () => { 118 | self.$resetCanvas(); 119 | self.$resetToolbar(); 120 | }, 121 | "undoButtonTapped": () => { 122 | const manager = self.$canvas().$undoManager(); 123 | if (manager.$canUndo()) { 124 | manager.$undo(); 125 | } 126 | }, 127 | "redoButtonTapped": () => { 128 | const manager = self.$canvas().$undoManager(); 129 | if (manager.$canRedo()) { 130 | manager.$redo(); 131 | } 132 | }, 133 | "shareButtonTapped": () => { 134 | const canvas = self.$canvas(); 135 | const bounds = canvas.$bounds(); 136 | const scale = $device.info.screen.scale; 137 | const drawing = canvas.$drawing(); 138 | 139 | const image = drawing.$imageFromRect_scale(bounds, scale); 140 | const data = image.jsValue().jpg(0.8); 141 | $clipboard.text = $text.base64Encode(data); 142 | $device.taptic(2); 143 | 144 | $ui.alert({ 145 | title: $l10n("COPIED"), 146 | message: $l10n("LEARN_MORE"), 147 | actions: [$l10n("OK")] 148 | }); 149 | } 150 | } 151 | }); 152 | 153 | $define({ 154 | type: "WSNavigatorVC: UINavigationController", 155 | events: { 156 | "supportedInterfaceOrientations": () => { 157 | return 2; 158 | } 159 | } 160 | }); 161 | 162 | exports.family = family; 163 | exports.present = present; --------------------------------------------------------------------------------