From 632bc3f2d66d747994dd2223244f965799b7181b Mon Sep 17 00:00:00 2001 From: Brian Hicks Date: Thu, 13 Oct 2022 20:42:03 -0500 Subject: [PATCH] choose a random task --- .../Resources/en.lproj/manifest.strings | 1 + .../Resources/en.lproj/pick.strings | 5 + Gardening.omnifocusjs/Resources/pick.js | 88 ++++++++++++++ Gardening.omnifocusjs/Resources/pick.png | Bin 0 -> 1044 bytes Gardening.omnifocusjs/Resources/pick.ts | 108 ++++++++++++++++++ Gardening.omnifocusjs/manifest.json | 13 +++ README.md | 1 + flake.nix | 2 +- tsconfig.json | 2 +- 9 files changed, 218 insertions(+), 2 deletions(-) create mode 100644 Gardening.omnifocusjs/Resources/en.lproj/manifest.strings create mode 100644 Gardening.omnifocusjs/Resources/en.lproj/pick.strings create mode 100644 Gardening.omnifocusjs/Resources/pick.js create mode 100644 Gardening.omnifocusjs/Resources/pick.png create mode 100644 Gardening.omnifocusjs/Resources/pick.ts create mode 100644 Gardening.omnifocusjs/manifest.json diff --git a/Gardening.omnifocusjs/Resources/en.lproj/manifest.strings b/Gardening.omnifocusjs/Resources/en.lproj/manifest.strings new file mode 100644 index 0000000..4196eef --- /dev/null +++ b/Gardening.omnifocusjs/Resources/en.lproj/manifest.strings @@ -0,0 +1 @@ +"zone.bytes.gardening" = "Digital Gardening for Omnifocus"; diff --git a/Gardening.omnifocusjs/Resources/en.lproj/pick.strings b/Gardening.omnifocusjs/Resources/en.lproj/pick.strings new file mode 100644 index 0000000..aba9d5a --- /dev/null +++ b/Gardening.omnifocusjs/Resources/en.lproj/pick.strings @@ -0,0 +1,5 @@ +"label" = "Pick Something"; +"shortLabel" = "Pick"; +"mediumLabel" = "Pick Something"; +"longLabel" = "Pick Something"; +"paletteLabel" = "Pick"; diff --git a/Gardening.omnifocusjs/Resources/pick.js b/Gardening.omnifocusjs/Resources/pick.js new file mode 100644 index 0000000..2f6f2d0 --- /dev/null +++ b/Gardening.omnifocusjs/Resources/pick.js @@ -0,0 +1,88 @@ +"use strict"; +(() => { + let creds = new Credentials(); + var getWeights = function () { + const now = new Date(); + const hour = now.getHours(); + const day = now.getDay(); + if (hour >= 8 && hour <= 17 && day != 0 && day != 6) { + return { work: 0.8, personal: 0.2 }; + } + else { + return { work: 0.8, personal: 0.2 }; + return { work: 0.0, personal: 1.0 }; + } + }; + var categorizeTask = function (task) { + var todo = task.tags; + while (todo.length != 0) { + let tag = todo.pop(); + if (tag.name == "work") { + return "work"; + } + else if (tag.name == "personal") { + return "personal"; + } + else if (tag.parent) { + todo.push(tag.parent); + } + } + return null; + }; + var daysBetween = function (a, b) { + let millis = Math.abs(a.getTime() - b.getTime()); + return millis / 1000 / 60 / 60 / 24; + }; + var weightedRandom = function (pairs) { + var total = 0; + for (let pair of pairs) { + total += pair[1]; + } + let target = Math.random() * total; + for (let pair of pairs) { + target -= pair[1]; + if (target <= 0) { + return pair[0]; + } + } + return null; + }; + var chooseATask = function () { + let weights = getWeights(); + let now = new Date(); + let possibleTasks = flattenedProjects + .filter((p) => p.status == Project.Status.Active) + .flatMap((p) => p.flattenedTasks.filter((t) => t.taskStatus == Task.Status.Available || + t.taskStatus == Task.Status.DueSoon || + t.taskStatus == Task.Status.Next || + t.taskStatus == Task.Status.Overdue)); + let weightedTasks = []; + for (let task of possibleTasks) { + // weight some categories higher than others + let categoryWeight = 0; + let category = categorizeTask(task); + if (category) { + categoryWeight = weights[category]; + } + // weight stale-er tasks higher, up to 7 days + let ageWeight = Math.min(7, daysBetween(now, task.modified)) / 7; + // weight due-er tasks higher, up to 100 points + let dueWeight = 0; + if (task.effectiveDueDate) { + dueWeight = 100 - daysBetween(now, task.effectiveDueDate); + } + weightedTasks.push([task, ageWeight + dueWeight + categoryWeight]); + } + return weightedRandom(weightedTasks); + }; + var action = new PlugIn.Action(async () => { + try { + console.log(`task: ${chooseATask()}`); + } + catch (err) { + console.error(err); + throw err; + } + }); + return action; +})(); diff --git a/Gardening.omnifocusjs/Resources/pick.png b/Gardening.omnifocusjs/Resources/pick.png new file mode 100644 index 0000000000000000000000000000000000000000..b0e51467397cb31f737a5a38ddd177118504b213 GIT binary patch literal 1044 zcmV+v1nc{WP)5LZ7{`CB7QFa&uwuZX2mJsAp?eXrCH)XENjxLN)AU-r3+hZ~<5lmQM;{AnZimQ(}F< zD`2(3HDms#N!a`->_nab`cV17js(}5K&Vk}+;5rxZBt$V(wNthK&Vk}+>ENkv=8ur zm%vZp24PsEWy8EyRG$!Q9?V)mFrU8)^aAIBC1D*l&1=}6;mb(LK5?O*fI^4u2A>Z3d;gA0KbHFb%Kz_yp{=s->B4EED3;zz;j`J ztq^RO*OEYJ0%s{Vq){ah51ML>v4hFn6J5|*_pNjmHb!X;pxu&gyn zXN6rrm;?R@>$fJ8Y4I!}G*MG;Mzc9KWV$Yp1q7AP+e@>Jlw@(#CIoFhaXz0`Nmqx{ zLKp+?0Q<<%Jf#1Rk&VD>V7IXR0Pr!%jbdeKLc~-iEultM=2OVV1_=p-8X#J$b1hpt zM4})R);!qVBBLSH{o_T{L~3EpgZ&K>v%nr;FKQ3LXUp|>)m2wrS=B%2tW2)nEmdOx O0000 { + let creds = new Credentials(); + + var getWeights = function (): { work: number; personal: number } { + const now = new Date(); + const hour = now.getHours(); + const day = now.getDay(); + + if (hour >= 8 && hour <= 17 && day != 0 && day != 6) { + return { work: 0.8, personal: 0.2 }; + } else { + return { work: 0.8, personal: 0.2 }; + return { work: 0.0, personal: 1.0 }; + } + }; + + var categorizeTask = function (task: Task): "work" | "personal" | null { + var todo = task.tags; + while (todo.length != 0) { + let tag = todo.pop(); + + if (tag.name == "work") { + return "work"; + } else if (tag.name == "personal") { + return "personal"; + } else if (tag.parent) { + todo.push(tag.parent); + } + } + + return null; + }; + + var daysBetween = function (a: Date, b: Date): number { + let millis = Math.abs(a.getTime() - b.getTime()); + return millis / 1000 / 60 / 60 / 24; + }; + + var weightedRandom = function (pairs: [Type, number][]): Type | null { + var total = 0; + for (let pair of pairs) { + total += pair[1]; + } + + let target = Math.random() * total; + for (let pair of pairs) { + target -= pair[1]; + + if (target <= 0) { + return pair[0]; + } + } + + return null; + }; + + var chooseATask = function (): Task | null { + let weights = getWeights(); + let now = new Date(); + + let possibleTasks = flattenedProjects + .filter((p) => p.status == Project.Status.Active) + .flatMap((p) => + p.flattenedTasks.filter( + (t: Task) => + t.taskStatus == Task.Status.Available || + t.taskStatus == Task.Status.DueSoon || + t.taskStatus == Task.Status.Next || + t.taskStatus == Task.Status.Overdue + ) + ); + + let weightedTasks: [Task, number][] = []; + + for (let task of possibleTasks) { + // weight some categories higher than others + let categoryWeight = 0; + let category = categorizeTask(task); + if (category) { + categoryWeight = weights[category]; + } + + // weight stale-er tasks higher, up to 7 days + let ageWeight = Math.min(7, daysBetween(now, task.modified)) / 7; + + // weight due-er tasks higher, up to 100 points + let dueWeight = 0; + if (task.effectiveDueDate) { + dueWeight = 100 - daysBetween(now, task.effectiveDueDate); + } + + weightedTasks.push([task, ageWeight + dueWeight + categoryWeight]); + } + + return weightedRandom(weightedTasks); + }; + + var action = new PlugIn.Action(async () => { + try { + console.log(`task: ${chooseATask()}`); + } catch (err) { + console.error(err); + throw err; + } + }); + + return action; +})(); diff --git a/Gardening.omnifocusjs/manifest.json b/Gardening.omnifocusjs/manifest.json new file mode 100644 index 0000000..bfdf156 --- /dev/null +++ b/Gardening.omnifocusjs/manifest.json @@ -0,0 +1,13 @@ +{ + "author": "Brian Hicks", + "identifier": "zone.bytes.gardening", + "defaultLocale": "en", + "version": "1.0", + "description": "Digital Gardening in OmniFocus", + "actions": [ + { + "identifier": "pick", + "image": "pick.png" + } + ] +} diff --git a/README.md b/README.md index 6ed459e..ce80123 100644 --- a/README.md +++ b/README.md @@ -25,3 +25,4 @@ Except for third-party icons and assets listed below, this code is released unde Exceptions: - The Linear logo icon is provided by [Linear in their Brand Guidelines](https://linear.app/docs/brand-guidelines) section, and does not have a license listed. +- Other image icons from [Josh Hughes](https://omnifocusicons.josh-hughes.com/), licensed under "Please feel free to use the icons as you see fit" diff --git a/flake.nix b/flake.nix index 8393bf5..b29888a 100644 --- a/flake.nix +++ b/flake.nix @@ -9,6 +9,6 @@ let pkgs = import inputs.nixpkgs { inherit system; }; in { devShell = - pkgs.mkShell { packages = [ pkgs.nodePackages.typescript ]; }; + pkgs.mkShell { packages = [ pkgs.nodePackages.typescript pkgs.nodePackages.prettier ]; }; }); } diff --git a/tsconfig.json b/tsconfig.json index 6f4df7c..6cda634 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,7 +12,7 @@ /* Language and Environment */ "target": "es2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ - "lib": ["es7"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + "lib": ["es2019"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ // "jsx": "preserve", /* Specify what JSX code is generated. */ // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */