Initial Commit

main
Anish Lakhwara 2023-06-16 19:56:17 +10:00
commit 32c9572556
9 changed files with 873 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
data/*
.nbb/*

28
README.md Normal file
View File

@ -0,0 +1,28 @@
##### uHabits Terminal
A Tiny TUI that is compatible with [Loop Habits]().
### TODO
- Parse date
- Add all entries to current date
- Read atoms from config
- Tests
- Types
- Colors for each habit
- Help menu
- Scroll bar for habits
- Save changes to file
- Add new habit
- Delete / Archive habit
- Make compatible with db
- Integrating Loop Habit "scores"
- Notifications
- Package with Nix so that it can run anywhere
### Built With
- [nbb]()
- [nodejs]()
- [ink]()
- [reagent]()
- [parse-csv]()
- [nix]()

26
flake.lock Normal file
View File

@ -0,0 +1,26 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1685351734,
"narHash": "sha256-0XzJKhXFmdkKU/y3vdQBxUjLOdMtNPBc5rNzjlVGBoo=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "a380c8b7b1f67ad2b34260061140494936c65307",
"type": "github"
},
"original": {
"owner": "nixos",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

41
flake.nix Normal file
View File

@ -0,0 +1,41 @@
{
description = "nbb TUI Habits Tracker compatible with uHabits (android)";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs";
};
outputs = { self, nixpkgs }:
let
pkgs = nixpkgs.legacyPackages.x86_64-linux;
in
{
packages.x86_64-linux.nbb-habits = pkgs.stdenv.mkDerivation {
name = "nbb-habits";
src = ./.;
# Need node_modules here
# Probably need some sort of wrapper of the bin too
installPhase = ''
mkdir -p $out
cp -r src/ $out/
cp nbb.edn $out/
'';
};
defaultPackage.x86_64-linux = self.packages.x86_64-linux.nbb-habits;
devShell.x86_64-linux = pkgs.mkShell {
buildInputs = with pkgs; [
jdk11
nodejs
babashka
clojure
];
shellHook = ''
alias nbb='./node_modules/.bin/nbb'
alias run='nbb src/habits/index.cljs'
'';
};
};
}

1
nbb.edn Normal file
View File

@ -0,0 +1 @@
{:paths ["src"]}

565
package-lock.json generated Normal file
View File

@ -0,0 +1,565 @@
{
"name": "nbb-habits",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"csv-parse": "^5.4.0",
"ink": "^4.2.0",
"ink-focus": "^1.0.1",
"nbb": "^1.2.174"
}
},
"node_modules/ansi-escapes": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.0.tgz",
"integrity": "sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw==",
"dependencies": {
"type-fest": "^3.0.0"
},
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ansi-escapes/node_modules/type-fest": {
"version": "3.11.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.11.0.tgz",
"integrity": "sha512-JaPw5U9ixP0XcpUbQoVSbxSDcK/K4nww20C3kjm9yE6cDRRhptU28AH60VWf9ltXmCrIfIbtt9J+2OUk2Uqiaw==",
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ansi-regex": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/ansi-styles": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/auto-bind": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz",
"integrity": "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==",
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/chalk": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz",
"integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==",
"engines": {
"node": "^12.17.0 || ^14.13 || >=16.0.0"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/ci-info": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz",
"integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/sibiraj-s"
}
],
"engines": {
"node": ">=8"
}
},
"node_modules/cli-boxes": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz",
"integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cli-cursor": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz",
"integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==",
"dependencies": {
"restore-cursor": "^4.0.0"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cli-truncate": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz",
"integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==",
"dependencies": {
"slice-ansi": "^5.0.0",
"string-width": "^5.0.0"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cli-truncate/node_modules/slice-ansi": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz",
"integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==",
"dependencies": {
"ansi-styles": "^6.0.0",
"is-fullwidth-code-point": "^4.0.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
}
},
"node_modules/code-excerpt": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz",
"integrity": "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==",
"dependencies": {
"convert-to-spaces": "^2.0.1"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
}
},
"node_modules/convert-to-spaces": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz",
"integrity": "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==",
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
}
},
"node_modules/csv-parse": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-5.4.0.tgz",
"integrity": "sha512-JiQosUWiOFgp4hQn0an+SBoV9IKdqzhROM0iiN4LB7UpfJBlsSJlWl9nq4zGgxgMAzHJ6V4t29VAVD+3+2NJAg=="
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
},
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
},
"node_modules/escape-string-regexp": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
"integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
"engines": {
"node": ">=8"
}
},
"node_modules/import-meta-resolve": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-2.2.2.tgz",
"integrity": "sha512-f8KcQ1D80V7RnqVm+/lirO9zkOxjGxhaTC1IPrBGd3MEfNgmNG67tSUO9gTi2F3Blr2Az6g1vocaxzkVnWl9MA==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/indent-string": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz",
"integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ink": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/ink/-/ink-4.2.0.tgz",
"integrity": "sha512-q7SeFAEFMyKxTblyVI+CsxHzfiMMP9JUDG0cRmOKEAmJiYrtrDW1YYTv129RXqfn7fMKcVc4h/LbAJvqvZIuEQ==",
"dependencies": {
"ansi-escapes": "^6.0.0",
"auto-bind": "^5.0.1",
"chalk": "^5.2.0",
"cli-boxes": "^3.0.0",
"cli-cursor": "^4.0.0",
"cli-truncate": "^3.1.0",
"code-excerpt": "^4.0.0",
"indent-string": "^5.0.0",
"is-ci": "^3.0.1",
"is-lower-case": "^2.0.2",
"is-upper-case": "^2.0.2",
"lodash": "^4.17.21",
"patch-console": "^2.0.0",
"react-reconciler": "^0.29.0",
"scheduler": "^0.23.0",
"signal-exit": "^3.0.7",
"slice-ansi": "^6.0.0",
"stack-utils": "^2.0.6",
"string-width": "^5.1.2",
"type-fest": "^0.12.0",
"widest-line": "^4.0.1",
"wrap-ansi": "^8.1.0",
"ws": "^8.12.0",
"yoga-wasm-web": "~0.3.3"
},
"engines": {
"node": ">=14.16"
},
"peerDependencies": {
"@types/react": ">=18.0.0",
"react": ">=18.0.0",
"react-devtools-core": "^4.19.1"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"react-devtools-core": {
"optional": true
}
}
},
"node_modules/ink-focus": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/ink-focus/-/ink-focus-1.0.1.tgz",
"integrity": "sha512-ddAI9GBpQWdkfklvb+JUUnPi1pVh4fKs75Wjwgi6qBlEpm9gJgF0r9QTNVw2CChjsHUDX1cJoCrlSUL5gdRQtw==",
"dependencies": {
"keypress": "^0.2.1"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"ink": ">=3.0.0",
"react": ">=16.8.0"
}
},
"node_modules/is-ci": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz",
"integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==",
"dependencies": {
"ci-info": "^3.2.0"
},
"bin": {
"is-ci": "bin.js"
}
},
"node_modules/is-fullwidth-code-point": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz",
"integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-lower-case": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/is-lower-case/-/is-lower-case-2.0.2.tgz",
"integrity": "sha512-bVcMJy4X5Og6VZfdOZstSexlEy20Sr0k/p/b2IlQJlfdKAQuMpiv5w2Ccxb8sKdRUNAG1PnHVHjFSdRDVS6NlQ==",
"dependencies": {
"tslib": "^2.0.3"
}
},
"node_modules/is-upper-case": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/is-upper-case/-/is-upper-case-2.0.2.tgz",
"integrity": "sha512-44pxmxAvnnAOwBg4tHPnkfvgjPwbc5QIsSstNU+YcJ1ovxVzCWpSGosPJOZh/a1tdl81fbgnLc9LLv+x2ywbPQ==",
"dependencies": {
"tslib": "^2.0.3"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"node_modules/keypress": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/keypress/-/keypress-0.2.1.tgz",
"integrity": "sha512-HjorDJFNhnM4SicvaUXac0X77NiskggxJdesG72+O5zBKpSqKFCrqmndKVqpu3pFqkla0St6uGk8Ju0sCurrmg=="
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
"bin": {
"loose-envify": "cli.js"
}
},
"node_modules/mimic-fn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
"engines": {
"node": ">=6"
}
},
"node_modules/nbb": {
"version": "1.2.174",
"resolved": "https://registry.npmjs.org/nbb/-/nbb-1.2.174.tgz",
"integrity": "sha512-Yw/hBgV4SHOxqrElHip+/7zKwtnxsgPqsOWgCRluMcoL0B1yB+bYx67yWH64E64REIk8u12D7LVwXGg9R/0b7g==",
"dependencies": {
"import-meta-resolve": "^2.1.0"
},
"bin": {
"nbb": "cli.js",
"nbbun": "nbbun.js"
}
},
"node_modules/onetime": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
"dependencies": {
"mimic-fn": "^2.1.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/patch-console": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/patch-console/-/patch-console-2.0.0.tgz",
"integrity": "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==",
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
}
},
"node_modules/react": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
"integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-reconciler": {
"version": "0.29.0",
"resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.29.0.tgz",
"integrity": "sha512-wa0fGj7Zht1EYMRhKWwoo1H9GApxYLBuhoAuXN0TlltESAjDssB+Apf0T/DngVqaMyPypDmabL37vw/2aRM98Q==",
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.0"
},
"engines": {
"node": ">=0.10.0"
},
"peerDependencies": {
"react": "^18.2.0"
}
},
"node_modules/restore-cursor": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz",
"integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==",
"dependencies": {
"onetime": "^5.1.0",
"signal-exit": "^3.0.2"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/scheduler": {
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
"integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
"dependencies": {
"loose-envify": "^1.1.0"
}
},
"node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
},
"node_modules/slice-ansi": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-6.0.0.tgz",
"integrity": "sha512-6bn4hRfkTvDfUoEQYkERg0BVF1D0vrX9HEkMl08uDiNWvVvjylLHvZFZWkDo6wjT8tUctbYl1nCOuE66ZTaUtA==",
"dependencies": {
"ansi-styles": "^6.2.1",
"is-fullwidth-code-point": "^4.0.0"
},
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
}
},
"node_modules/stack-utils": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
"integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==",
"dependencies": {
"escape-string-regexp": "^2.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"dependencies": {
"eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/strip-ansi": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"dependencies": {
"ansi-regex": "^6.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/tslib": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.2.tgz",
"integrity": "sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA=="
},
"node_modules/type-fest": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.12.0.tgz",
"integrity": "sha512-53RyidyjvkGpnWPMF9bQgFtWp+Sl8O2Rp13VavmJgfAP9WWG6q6TkrKU8iyJdnwnfgHI6k2hTlgqH4aSdjoTbg==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/widest-line": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz",
"integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==",
"dependencies": {
"string-width": "^5.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/wrap-ansi": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/ws": {
"version": "8.13.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/yoga-wasm-web": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/yoga-wasm-web/-/yoga-wasm-web-0.3.3.tgz",
"integrity": "sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA=="
}
}
}

8
package.json Normal file
View File

@ -0,0 +1,8 @@
{
"dependencies": {
"csv-parse": "^5.4.0",
"ink": "^4.2.0",
"ink-focus": "^1.0.1",
"nbb": "^1.2.174"
}
}

View File

@ -0,0 +1,8 @@
(ns habits.components.table
(:require ["ink" :refer [Text Box Spacer]]))
(defn row []
[:> [Box
[Text "Hello"]
[Spacer]
[Text "World"]]])

194
src/habits/index.cljs Normal file
View File

@ -0,0 +1,194 @@
(ns habits.index
(:require ["ink" :refer [render Box Text Spacer useInput useApp]]
["csv-parse/sync" :as csv]
["fs" :as fs]
[habits.components.table :as table]
[reagent.core :as r]))
; Things This Project would benefit from
; Splitting this single file into smaller files
; Testing
; Types + Type checking
; A working LSP
; Each habit is just a map with keys :checkmarks and :name
; We'll probably need :color and maybe a couple other things, order?
; :checkmarks is an array of maps with keys :date :result
#_(def habits-type [{:name str
:checkmarks [{:date str
:result int}]}])
; Config section
(defonce size (r/atom [5 3]))
(defonce scroll-index (r/atom (map dec @size))) ; Position within viewable frame defined by size
(defonce select-index (r/atom [0 0])) ; Position within all the data loaded
(defonce uhabits-csv (r/atom "./data/Loop Habits CSV 2023-05-30/Checkmarks.csv"))
; Mock Data
(def checkmarks_data
[{:date "27/09/12" :result -1}
{:date "26/09/12" :result 2}
{:date "25/09/12" :result 1}
{:date "24/09/12" :result 0}
{:date "23/09/12" :result 0}])
(def meditate
{:name "meditate"
:checkmarks checkmarks_data})
(def exercise
{:name "exercise"
:checkmarks checkmarks_data})
; CSV Parsing
(defn uhabits-data [csv-file]
(js->clj (csv/parse (str (fs/readFileSync csv-file)) {:columns true})))
(defn parse-row [habits row]
(let [date (first row)
values (drop-last (rest row))
checkmarks (->> values
(mapv (fn [v] {:result (js/parseInt v) :date date})))]
(for [[index habit] (map-indexed vector habits)]
(assoc habit :checkmarks (conj (:checkmarks habit) (nth checkmarks index))))))
(defn make-habits [csv]
(let [headers (drop-last (drop 1 (first csv)))
data (take 20 (rest csv))
habits (mapv (fn [x] {:name x :checkmarks []}) headers)]
(vec (reduce parse-row habits data))))
(def my-data (make-habits (uhabits-data @uhabits-csv)))
; Data manipulation
(defonce habits (r/atom my-data))
(defn get-dates [habits]
(let [dates (->> habits
(first)
(:checkmarks)
(mapv #(get % :date)))]
dates))
(defn toggle-habits [habits]
(let [selected @select-index
scrolled @scroll-index
pos (->> @size
(map #(- % 1))
(map - scrolled))
[i j] (map + pos selected)
habit (nth habits i)
checkmarks (:checkmarks habit)
checkmark (nth checkmarks j)
new-checkmark (if (<= (:result checkmark) 0) 1 0)
new-checkmarks (assoc checkmarks j {:date (:date checkmark) :result new-checkmark})
new-habits (assoc habits i {:name (:name habit)
:checkmarks new-checkmarks})]
new-habits))
(defn filter-checkmarks [checkmarks]
(let [[_ date-size] @size
[_ y] @scroll-index
y-adj (if (>= y date-size) (- y (- date-size 1)) 0)]
(->> checkmarks
(drop y-adj)
(take date-size))))
(defn filter-habits [habits]
(let [[habit-size _] @size
[x _] @scroll-index
x-adj (if (>= x habit-size) (- x (- habit-size 1)) 0)
filtered-habits (->> habits
(drop x-adj)
(take habit-size)
(map #(assoc % :checkmarks (filter-checkmarks (:checkmarks %)))))]
filtered-habits))
(defn update-index [dir]
(let [[habit-size date-size] @size
habit-limit (- habit-size 1)
date-limit (- date-size 1)
[cur-x cur-y] @select-index]
(case dir
"down" (doall (swap! scroll-index (fn [[x y]] (if (>= cur-x habit-limit) [(inc x) y] [x y])))
(swap! select-index (fn [[x y]] (if (>= x habit-limit) [habit-limit y] [(inc x) y]))))
"up" (doall (swap! scroll-index (fn [[x y]] (if (= cur-x 0)
(if (<= x habit-limit)
[habit-limit y]
[(dec x) y])
[x y])))
(swap! select-index (fn [[x y]] (if (<= x 0) [0 y] [(dec x) y]))))
"right" (doall
(swap! scroll-index (fn [[x y]] (if (>= cur-y date-limit) [x (inc y)] [x y])))
(swap! select-index (fn [[x y]] (if (>= y date-limit) [x date-limit] [x (inc y)]))))
"left" (doall (swap! scroll-index (fn [[x y]] (if (= cur-y 0)
(if (<= y date-limit)
[x date-limit]
[x (dec y)])
[x y])))
(swap! select-index (fn [[x y]] (if (<= y 0) [x 0] [x (dec y)])))))))
; UI
(defn headers [dates]
[:> Box {:width "100%" :alignItems "center"}
[:> Spacer]
(for [date dates]
[:<> {:key (str "header:" date)}
[:> Box {:minWidth 10}
[:> Spacer]]
[:> Text date]
[:> Spacer]])])
(defn check-box [isChecked isSelected]
(let [mark (if (>= isChecked 1) "✔" "✗")]
(if isSelected
[:> Text {:color "yellow"} mark]
[:> Text mark])))
(defn row [checkmarks habit j]
[:> Box {:borderStyle "round" :width "100%" :paddingRight 2 :alignItems "center" :flexGrow 1}
(for [[index checkmark] (map-indexed vector checkmarks)]
(let [isSelected (= j index)]
[:<> {:key (str "row:" habit (:date checkmark))}
[:> Spacer]
(check-box (:result checkmark) isSelected)
[:> Spacer]]))])
(defn column [habits i j]
[:> Box {:borderStyle "round" :flexDirection "column" :columnGap 1}
(headers (get-dates habits))
(for [[index habit] (map-indexed vector habits)]
(let [checkmarks (:checkmarks habit)
n (:name habit)]
[:> Box {:key (str "box:row:" n) :alignItems "flex-end"}
(if (= index i)
[:<> {:key (str "row:" n)}
[:> Box {:minWidth 10 :alignSelf "center"}
[:> Text {:color "yellow"} n]]
(row checkmarks n j)]
[:<> {:key (str "row:" n)}
[:> Box {:minWidth 10 :alignSelf "center"}
[:> Text n]]
(row checkmarks n false)])
[:> Spacer]]))])
(defn selector []
(let [[i j] @select-index
habits_ (filter-habits @habits)
exit (useApp)]
(useInput (fn [input k]
(if (= input "q")
(. exit exit))
(if (= input " ")
(swap! habits toggle-habits))
(if k.downArrow
(update-index "down"))
(if k.upArrow
(update-index "up"))
(if k.rightArrow
(update-index "right"))
(if k.leftArrow
(update-index "left"))))
(column habits_ i j)))
(render (r/as-element [:f> selector]))