Initial Commit
This commit is contained in:
commit
32c9572556
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
data/*
|
||||
.nbb/*
|
28
README.md
Normal file
28
README.md
Normal 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
26
flake.lock
Normal 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
41
flake.nix
Normal 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'
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
|
565
package-lock.json
generated
Normal file
565
package-lock.json
generated
Normal 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
8
package.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"csv-parse": "^5.4.0",
|
||||
"ink": "^4.2.0",
|
||||
"ink-focus": "^1.0.1",
|
||||
"nbb": "^1.2.174"
|
||||
}
|
||||
}
|
8
src/habits/components/table.cljs
Normal file
8
src/habits/components/table.cljs
Normal 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
194
src/habits/index.cljs
Normal 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]))
|
Loading…
Reference in a new issue