From 663c9d7b78ca94df3c04c05bae4cfe976aa780f7 Mon Sep 17 00:00:00 2001 From: Thijs Zijdel Date: Fri, 30 Aug 2024 08:52:07 +0200 Subject: [PATCH 1/5] add: package tauri-plugin-sql-api --- .gitignore | 3 +- package.json | 3 +- src-tauri/Cargo.lock | 583 ++++++++++++++++++++++++++++++++++++++++++- src-tauri/Cargo.toml | 5 + yarn.lock | 8 +- 5 files changed, 597 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 9d22bd1..e2a00d9 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,5 @@ yarn-error.log assets/vips/ .yarn -.eslintcache \ No newline at end of file +.eslintcache +.idea diff --git a/package.json b/package.json index 09ed47f..2f816e7 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,7 @@ "rehype-raw": "^7.0.0", "remark-math": "^6.0.0", "simple-base-converter": "^1.0.19", + "tauri-plugin-sql-api": "https://github.com/tauri-apps/tauri-plugin-sql#v1", "tauri-plugin-store-api": "https://github.com/tauri-apps/tauri-plugin-store", "terser": "^5.30.0", "tinycolor2": "^1.6.0", @@ -128,4 +129,4 @@ "vite": "^5.2.7", "vite-tsconfig-paths": "^5.0.1" } -} +} diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 5bbf3e0..759da24 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -74,6 +74,12 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -139,6 +145,15 @@ dependencies = [ "system-deps 6.2.2", ] +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + [[package]] name = "autocfg" version = "1.2.0" @@ -200,6 +215,12 @@ dependencies = [ "simd-abstraction", ] +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bit_field" version = "0.10.2" @@ -217,6 +238,9 @@ name = "bitflags" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +dependencies = [ + "serde", +] [[package]] name = "bitvec" @@ -485,6 +509,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "const-str" version = "0.3.2" @@ -573,6 +603,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" version = "1.4.0" @@ -610,6 +655,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.19" @@ -755,6 +809,17 @@ dependencies = [ "matches", ] +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "deranged" version = "0.3.11" @@ -812,6 +877,7 @@ dependencies = [ "tauri", "tauri-build", "tauri-plugin-aptabase", + "tauri-plugin-sql", "tauri-plugin-store", "tokio", "webp", @@ -824,7 +890,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", + "subtle", ] [[package]] @@ -863,6 +931,12 @@ dependencies = [ "libloading", ] +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + [[package]] name = "downcast-rs" version = "1.2.0" @@ -895,6 +969,9 @@ name = "either" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +dependencies = [ + "serde", +] [[package]] name = "embed-resource" @@ -947,6 +1024,23 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0474425d51df81997e2f90a21591180b38eccf27292d755f3e30750225c175b" +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + [[package]] name = "exr" version = "1.72.0" @@ -1033,7 +1127,9 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" dependencies = [ - "spin", + "futures-core", + "futures-sink", + "spin 0.9.8", ] [[package]] @@ -1151,6 +1247,17 @@ dependencies = [ "futures-util", ] +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + [[package]] name = "futures-io" version = "0.3.30" @@ -1577,6 +1684,19 @@ name = "hashbrown" version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash 0.8.11", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown 0.14.3", +] [[package]] name = "heck" @@ -1592,6 +1712,9 @@ name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +dependencies = [ + "unicode-segmentation", +] [[package]] name = "heck" @@ -1611,6 +1734,24 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "home" version = "0.5.9" @@ -1980,6 +2121,9 @@ name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] [[package]] name = "lebe" @@ -2021,6 +2165,12 @@ dependencies = [ "windows-targets 0.52.4", ] +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + [[package]] name = "libredox" version = "0.0.1" @@ -2032,6 +2182,17 @@ dependencies = [ "redox_syscall", ] +[[package]] +name = "libsqlite3-sys" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "libwebp-sys" version = "0.9.5" @@ -2364,12 +2525,49 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -2377,6 +2575,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -2663,6 +2862,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -2825,6 +3033,27 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.30" @@ -3304,6 +3533,26 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "rsa" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -3588,6 +3837,17 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.8" @@ -3617,6 +3877,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + [[package]] name = "simd-abstraction" version = "0.7.1" @@ -3697,6 +3967,12 @@ dependencies = [ "system-deps 5.0.0", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "spin" version = "0.9.8" @@ -3706,6 +3982,224 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlformat" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f895e3734318cc55f1fe66258926c9b910c124d47520339efecbb6c59cec7c1f" +dependencies = [ + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6" +dependencies = [ + "ahash 0.8.11", + "atoi", + "byteorder", + "bytes", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashlink", + "hex", + "indexmap 2.2.6", + "log", + "memchr", + "once_cell", + "paste", + "percent-encoding", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlformat", + "thiserror", + "time", + "tokio", + "tokio-stream", + "tracing", + "url", +] + +[[package]] +name = "sqlx-macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 1.0.109", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8" +dependencies = [ + "dotenvy", + "either", + "heck 0.4.1", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 1.0.109", + "tempfile", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418" +dependencies = [ + "atoi", + "base64 0.21.7", + "bitflags 2.5.0", + "byteorder", + "bytes", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa 1.0.11", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand 0.8.5", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "time", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e" +dependencies = [ + "atoi", + "base64 0.21.7", + "bitflags 2.5.0", + "byteorder", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa 1.0.11", + "log", + "md-5", + "memchr", + "once_cell", + "rand 0.8.5", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "time", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa" +dependencies = [ + "atoi", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "sqlx-core", + "time", + "tracing", + "url", + "urlencoding", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -3747,12 +4241,29 @@ dependencies = [ "quote", ] +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + [[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "surge-ping" version = "0.8.1" @@ -4072,10 +4583,26 @@ dependencies = [ "uuid", ] +[[package]] +name = "tauri-plugin-sql" +version = "0.0.0" +source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#677bade9089f2963e5858cc5062e5504787eaf7f" +dependencies = [ + "futures-core", + "log", + "serde", + "serde_json", + "sqlx", + "tauri", + "thiserror", + "time", + "tokio", +] + [[package]] name = "tauri-plugin-store" version = "0.0.0" -source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#066058f631b81d1203f55f6369b7b0a19e29a4d4" +source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#677bade9089f2963e5858cc5062e5504787eaf7f" dependencies = [ "log", "serde", @@ -4322,6 +4849,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-stream" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.10" @@ -4416,6 +4954,7 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -4533,12 +5072,24 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-properties" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" + [[package]] name = "unicode-segmentation" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + [[package]] name = "url" version = "2.5.0" @@ -4551,6 +5102,12 @@ dependencies = [ "serde", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf-8" version = "0.7.6" @@ -4653,6 +5210,12 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + [[package]] name = "wasm-bindgen" version = "0.2.92" @@ -4916,6 +5479,16 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" +[[package]] +name = "whoami" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" +dependencies = [ + "redox_syscall", + "wasite", +] + [[package]] name = "winapi" version = "0.3.9" @@ -5465,6 +6038,12 @@ dependencies = [ "syn 2.0.57", ] +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + [[package]] name = "zip" version = "0.6.6" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index ac9fa36..17184df 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -41,6 +41,11 @@ flate2 = "1.0" bardecoder = "0.5.0" imageinfo = "0.7.17" +[dependencies.tauri-plugin-sql] +git = "https://github.com/tauri-apps/plugins-workspace" +branch = "v1" +features = ["sqlite"] # or "postgres", or "mysql" + [features] default = ["custom-protocol"] custom-protocol = ["tauri/custom-protocol"] diff --git a/yarn.lock b/yarn.lock index 828c1da..20006b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1815,7 +1815,7 @@ resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-1.5.3.tgz#f7b362b1f30aadb0a8bbeb7ae111755c0ed33d73" integrity sha512-zxnDjHHKjOsrIzZm6nO5Xapb/BxqUq1tc7cGkFXsFkGTsSWgCPH1D8mm0XS9weJY2OaR73I3k3S+b7eSzJDfqA== -"@tauri-apps/api@^1.6.0": +"@tauri-apps/api@1.6.0", "@tauri-apps/api@^1.6.0": version "1.6.0" resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-1.6.0.tgz#745b7e4e26782c3b2ad9510d558fa5bb2cf29186" integrity sha512-rqI++FWClU5I2UBp4HXFvl+sBWkdigBkxnpJDQUWttNyG7IZP4FwQGhTNL5EOw0vI8i6eSAJ5frLqO7n7jbJdg== @@ -7736,6 +7736,12 @@ tar@^6.1.11: mkdirp "^1.0.3" yallist "^4.0.0" +"tauri-plugin-sql-api@https://github.com/tauri-apps/tauri-plugin-sql#v1": + version "0.0.0" + resolved "https://github.com/tauri-apps/tauri-plugin-sql#7ae8847ab1f2252e8b6f0fe44af215ac40664fd6" + dependencies: + "@tauri-apps/api" "1.6.0" + "tauri-plugin-store-api@https://github.com/tauri-apps/tauri-plugin-store": version "0.0.0" resolved "https://github.com/tauri-apps/tauri-plugin-store#02243686d0507d2aeeb2924cd889dd0bcb47ecef" From 2633aec49e8ae7a072ff5eaeecbb04ba28bd3b31 Mon Sep 17 00:00:00 2001 From: Thijs Zijdel Date: Fri, 30 Aug 2024 08:52:50 +0200 Subject: [PATCH 2/5] add: snippets db setup --- src-tauri/src/main.rs | 45 ++++++++++ src/App.tsx | 2 + src/Features/snippets/Snippets.tsx | 36 ++++++++ src/Layout/Navbar/items.tsx | 7 ++ src/utils/database.ts | 128 +++++++++++++++++++++++++++++ src/utils/db.ts | 2 +- 6 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 src/Features/snippets/Snippets.tsx create mode 100644 src/utils/database.ts diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 4ee79a5..4ad387c 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -7,6 +7,8 @@ use std::convert::TryInto; use std::env; use tauri::{Manager, WindowBuilder, WindowUrl}; +use tauri_plugin_sql::{Migration, MigrationKind}; + mod commands; use commands::base64_image::base64_image::base64_image; @@ -19,7 +21,50 @@ use commands::ping::ping::ping; use commands::qr::qr::read_qr; fn main() { + // Todo move to different file + let migrations = vec![ + Migration { + version: 1, + description: "create_initial_tables", + sql: "\ + CREATE TABLE snippets (\ + id INTEGER PRIMARY KEY AUTOINCREMENT,\ + name TEXT NOT NULL,\ + path TEXT NOT NULL,\ + content TEXT,\ + filetype TEXT,\ + parent_id INTEGER,\ + created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\ + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP\ + );\ + CREATE TABLE snippets_tags (\ + id INTEGER PRIMARY KEY AUTOINCREMENT,\ + snippet_id INTEGER NOT NULL,\ + tag TEXT NOT NULL,\ + FOREIGN KEY(snippet_id) REFERENCES snippets (id)\ + );\ + CREATE TABLE snippets_notes (\ + id INTEGER PRIMARY KEY AUTOINCREMENT,\ + snippet_id INTEGER NOT NULL,\ + note TEXT NOT NULL,\ + FOREIGN KEY(snippet_id) REFERENCES snippets (id)\ + );\ + CREATE TABLE snippets_files (\ + id INTEGER PRIMARY KEY AUTOINCREMENT,\ + snippet_id INTEGER NOT NULL,\ + file_path TEXT NOT NULL,\ + FOREIGN KEY(snippet_id) REFERENCES snippets (id)\ + );", + kind: MigrationKind::Up, + } + ]; + tauri::Builder::default() + .plugin( + tauri_plugin_sql::Builder::default() + .add_migrations("sqlite:devtools.db", migrations) + .build() + ) .plugin(tauri_plugin_store::Builder::default().build()) .plugin(tauri_plugin_aptabase::Builder::new("A-EU-0242299228").build()) .invoke_handler(tauri::generate_handler![ diff --git a/src/App.tsx b/src/App.tsx index 99a8b55..dffbf97 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -51,6 +51,7 @@ const TextDiff = loadable(() => import("./Features/text/TextDiff")); const TextUtils = loadable(() => import("./Features/text/TextUtils")); const Markdown = loadable(() => import("./Features/markdown/Markdown")); const Readme = loadable(() => import("./Features/markdown/Readme")); +const Snippets = loadable(() => import("./Features/snippets/Snippets")); const YamlJson = loadable(() => import("./Features/json-yaml/Yaml")); const Pastebin = loadable(() => import("./Features/pastebin/Pastebin")); const Repl = loadable(() => import("./Features/repl/Repl")); @@ -237,6 +238,7 @@ function App() { }> }> }> + }> }> }> }> diff --git a/src/Features/snippets/Snippets.tsx b/src/Features/snippets/Snippets.tsx new file mode 100644 index 0000000..bae7140 --- /dev/null +++ b/src/Features/snippets/Snippets.tsx @@ -0,0 +1,36 @@ +import { Group, Stack } from "@mantine/core"; +import { findAllSnippets, seedDatabase } from "@/utils/database"; +import { useEffect, useState } from "react"; + +const Snippets = () => { + const [snippets, setSnippets] = useState([]); + useEffect(() => { + const fetchData = async () => { + const data = await findAllSnippets(); + setSnippets(data as any); + }; + fetchData().then((r) => r); + }, []); + + const seed = async () => { + await seedDatabase(); + const data = await findAllSnippets(); + setSnippets(data as any); + }; + return ( + + +
+ Snippets +
+ +
{JSON.stringify(snippets, null, 2)}
+
+ ); +}; + +export default Snippets; diff --git a/src/Layout/Navbar/items.tsx b/src/Layout/Navbar/items.tsx index 2e6a250..8355807 100644 --- a/src/Layout/Navbar/items.tsx +++ b/src/Layout/Navbar/items.tsx @@ -260,6 +260,13 @@ export const navitems: NavItem[] = [ text: "Count chars", group: "Utilities", }, + { + id: "snippets", + to: "/snippets", + icon: , + text: "Snippets", + group: "Utilities", + }, { id: "markdown", to: "/markdown", diff --git a/src/utils/database.ts b/src/utils/database.ts new file mode 100644 index 0000000..b501df4 --- /dev/null +++ b/src/utils/database.ts @@ -0,0 +1,128 @@ +import Database from "tauri-plugin-sql-api"; + +const getDb = async () => await Database.load("sqlite:devtools.db"); + +// Insert a new snippet +async function insertSnippet(snippet: { + name: string; + path: string; + content?: string; + filetype?: string; + parent_id?: number; +}) { + const db = await getDb(); + await db + .execute( + "INSERT INTO snippets (name, path, content, filetype, parent_id) VALUES (?, ?, ?, ?, ?)", + [ + snippet.name, + snippet.path, + snippet.content, + snippet.filetype, + snippet.parent_id, + ] + ) + .then((r) => console.log(r)) + .catch((e) => console.log(e)); +} + +// Insert a new tag +async function insertSnippetTag(snippet_id: number, tag: string) { + const db = await getDb(); + await db.execute( + "INSERT INTO snippets_tags (snippet_id, tag) VALUES (?, ?)", + [snippet_id, tag] + ); +} + +// Insert a new note +async function insertSnippetNote(snippet_id: number, note: string) { + const db = await getDb(); + await db.execute( + "INSERT INTO snippets_notes (snippet_id, note) VALUES (?, ?)", + [snippet_id, note] + ); +} + +// Update a snippet +async function updateSnippet(snippet: { + id: number; + name?: string; + path?: string; + content?: string; + filetype?: string; + parent_id?: number; +}) { + const db = await getDb(); + await db.execute( + `UPDATE snippets SET + name = COALESCE(?, name), + path = COALESCE(?, path), + content = COALESCE(?, content), + filetype = COALESCE(?, filetype), + parent_id = COALESCE(?, parent_id) + WHERE id = ?`, + [ + snippet.name, + snippet.path, + snippet.content, + snippet.filetype, + snippet.parent_id, + snippet.id, + ] + ); +} + +// Get snippet by ID +async function getSnippetById(id: number) { + const db = await getDb(); + const snippet = await db.execute("SELECT * FROM snippets WHERE id = ?", [id]); + return snippet; +} + +export async function findAllSnippets() { + try { + const db = await getDb(); + const snippets = await db.execute("SELECT * FROM snippets"); + return snippets; + } catch (error) { + console.error(error); + return []; + } +} + +export async function seedDatabase() { + const db = await getDb(); + + // Clear existing data (optional) + await db.execute("DELETE FROM snippets"); + await db.execute("DELETE FROM snippets_tags"); + await db.execute("DELETE FROM snippets_notes"); + + // Insert sample snippets + const snippetId1 = await insertSnippet({ + name: "Sample Snippet 1", + path: "/path/to/snippet1", + content: "Content of the first snippet.", + filetype: "text", + parent_id: undefined, + }); + + const snippetId2 = await insertSnippet({ + name: "Sample Snippet 2", + path: "/path/to/snippet2", + content: "Content of the second snippet.", + filetype: "text", + parent_id: undefined, + }); + + // Insert tags + await insertSnippetTag(snippetId1, "sample"); + await insertSnippetTag(snippetId2, "example"); + + // Insert notes + await insertSnippetNote(snippetId1, "This is a note for the first snippet."); + await insertSnippetNote(snippetId2, "This is a note for the second snippet."); + + console.log("Database seeded successfully."); +} diff --git a/src/utils/db.ts b/src/utils/db.ts index 68abecd..c0131c5 100644 --- a/src/utils/db.ts +++ b/src/utils/db.ts @@ -1,5 +1,5 @@ import { Store } from "tauri-plugin-store-api"; -import { defaultConfig } from "../Contexts/AppContextProvider"; +import { defaultConfig } from "@/Contexts/AppContextProvider"; const db = new Store("settings.json"); From 4896e446164355a1fe4c7b9bf4516622e7457508 Mon Sep 17 00:00:00 2001 From: Thijs Zijdel Date: Sun, 15 Sep 2024 19:24:56 +0200 Subject: [PATCH 3/5] feat: initial snippets ui --- src-tauri/src/main.rs | 56 ++-- src/Features/colors/CustomPicker.tsx | 1 - src/Features/colors/colorgenerator.module.css | 16 +- src/Features/snippets/Snippets.module.css | 28 ++ src/Features/snippets/Snippets.tsx | 97 +++++-- src/Layout/Navbar/items.tsx | 72 ++--- src/utils/database.ts | 251 ++++++++++++++---- 7 files changed, 375 insertions(+), 146 deletions(-) create mode 100644 src/Features/snippets/Snippets.module.css diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 4ad387c..806c13a 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -26,34 +26,34 @@ fn main() { Migration { version: 1, description: "create_initial_tables", - sql: "\ - CREATE TABLE snippets (\ - id INTEGER PRIMARY KEY AUTOINCREMENT,\ - name TEXT NOT NULL,\ - path TEXT NOT NULL,\ - content TEXT,\ - filetype TEXT,\ - parent_id INTEGER,\ - created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\ - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP\ - );\ - CREATE TABLE snippets_tags (\ - id INTEGER PRIMARY KEY AUTOINCREMENT,\ - snippet_id INTEGER NOT NULL,\ - tag TEXT NOT NULL,\ - FOREIGN KEY(snippet_id) REFERENCES snippets (id)\ - );\ - CREATE TABLE snippets_notes (\ - id INTEGER PRIMARY KEY AUTOINCREMENT,\ - snippet_id INTEGER NOT NULL,\ - note TEXT NOT NULL,\ - FOREIGN KEY(snippet_id) REFERENCES snippets (id)\ - );\ - CREATE TABLE snippets_files (\ - id INTEGER PRIMARY KEY AUTOINCREMENT,\ - snippet_id INTEGER NOT NULL,\ - file_path TEXT NOT NULL,\ - FOREIGN KEY(snippet_id) REFERENCES snippets (id)\ + sql: " + CREATE TABLE snippets ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + path TEXT NOT NULL, + content TEXT, + filetype TEXT, + parent_id INTEGER, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP + ); + CREATE TABLE snippets_tags ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + snippet_id INTEGER NOT NULL, + tag TEXT NOT NULL, + FOREIGN KEY(snippet_id) REFERENCES snippets (id) + ); + CREATE TABLE snippets_notes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + snippet_id INTEGER NOT NULL, + note TEXT NOT NULL, + FOREIGN KEY(snippet_id) REFERENCES snippets (id) + ); + CREATE TABLE snippets_files ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + snippet_id INTEGER NOT NULL, + file_path TEXT NOT NULL, + FOREIGN KEY(snippet_id) REFERENCES snippets (id) );", kind: MigrationKind::Up, } diff --git a/src/Features/colors/CustomPicker.tsx b/src/Features/colors/CustomPicker.tsx index d76f28d..8baa0c8 100644 --- a/src/Features/colors/CustomPicker.tsx +++ b/src/Features/colors/CustomPicker.tsx @@ -165,7 +165,6 @@ class CustomColorPicker extends React.Component { onMouseDown={onPointerMouseDown} > { + const [snippetId, setSnippetId] = useState(null); + const [snippet, setSnippet] = useState(null); + const [snippets, setSnippets] = useState([]); + const [sidebar] = useState({ + fields: ["id", "name", "path", "filetype", "parent_id"], + }); + // "content" + useEffect(() => { - const fetchData = async () => { - const data = await findAllSnippets(); - setSnippets(data as any); - }; - fetchData().then((r) => r); - }, []); + listSnippets(sidebar).then((r) => { + setSnippets(r as any); + }); + }, [...Object.values(sidebar)]); + + const loadSnippet = async (id: number) => { + setSnippetId(id); + await getSnippetById(id).then((r) => { + setSnippet(r as any); + }); + }; + + const onChange = async (content: string) => { + setSnippet((prev: any) => ({ ...prev, content })); + await updateSnippet({ ...snippet, content, id: snippet.id }); + }; const seed = async () => { await seedDatabase(); - const data = await findAllSnippets(); + const data = await findAllSnippets().then((r) => { + console.log(r); + return r; + }); setSnippets(data as any); }; + return ( - - -
- Snippets + +
+ +
{JSON.stringify(snippet, null, 2)}
- -
{JSON.stringify(snippets, null, 2)}
- + ); }; diff --git a/src/Layout/Navbar/items.tsx b/src/Layout/Navbar/items.tsx index 7140698..7c52ac1 100644 --- a/src/Layout/Navbar/items.tsx +++ b/src/Layout/Navbar/items.tsx @@ -1,49 +1,49 @@ import { - IconPdf, - IconSort09, - IconWorldWww, - IconClock, - IconPingPong, - IconGrain, - IconQrcode, - IconScan, - IconMenuDeep, - IconBrandReact, - IconBrandCss3, - IconForms, - IconPhotoEdit, - IconCrop, - IconClipboard, IconAlignBoxLeftStretch, - IconCameraPlus, - IconBubbleText, - IconPhotoScan, - IconHash, - IconFile, - IconTicTac, IconAnchor, + IconBrandCss3, IconBrandOauth, - IconSql, - IconPalette, - IconWand, - IconPaint, + IconBrandReact, + IconBubbleText, + IconCameraPlus, + IconClipboard, + IconClock, + IconCloudLock, + IconCrop, + IconExchange, + IconFile, IconFileDiff, - IconMultiplier1x, - IconMarkdown, IconFileInfo, - IconYinYang, - IconExchange, - IconProgressDown, - IconCloudLock, + IconForms, + IconGrain, + IconHash, IconHierarchy, - IconLink, - IconLine, IconHtml, - IconSvg, - IconStar, IconId, + IconLine, + IconLink, + IconMarkdown, + IconMenuDeep, + IconMultiplier1x, + IconPaint, + IconPalette, + IconPdf, + IconPhotoEdit, + IconPhotoScan, + IconPingPong, + IconProgressDown, + IconQrcode, IconRegex, + IconScan, + IconSort09, + IconSql, + IconStar, + IconSvg, IconTableAlias, + IconTicTac, + IconWand, + IconWorldWww, + IconYinYang, } from "@tabler/icons-react"; import { NavItem } from "."; @@ -267,7 +267,7 @@ export const navitems: NavItem[] = [ { id: "snippets", to: "/snippets", - icon: , + icon: , text: "Snippets", group: "Utilities", }, diff --git a/src/utils/database.ts b/src/utils/database.ts index b501df4..b57516e 100644 --- a/src/utils/database.ts +++ b/src/utils/database.ts @@ -11,41 +11,59 @@ async function insertSnippet(snippet: { parent_id?: number; }) { const db = await getDb(); - await db - .execute( - "INSERT INTO snippets (name, path, content, filetype, parent_id) VALUES (?, ?, ?, ?, ?)", - [ - snippet.name, - snippet.path, - snippet.content, - snippet.filetype, - snippet.parent_id, - ] - ) - .then((r) => console.log(r)) - .catch((e) => console.log(e)); + try { + const snippetId = await db + .execute( + "INSERT INTO snippets (name, path, content, filetype, parent_id) VALUES (?, ?, ?, ?, ?)", + [ + snippet.name, + snippet.path, + snippet.content, + snippet.filetype, + snippet.parent_id, + ] + ) + .then((result) => result.lastInsertId) + .catch((error) => { + console.error(error); + return undefined; + }); + + return snippetId; + } catch (error) { + console.error("Failed to insert snippet:", error); + return undefined; + } } // Insert a new tag async function insertSnippetTag(snippet_id: number, tag: string) { const db = await getDb(); - await db.execute( - "INSERT INTO snippets_tags (snippet_id, tag) VALUES (?, ?)", - [snippet_id, tag] - ); + await db + .execute("INSERT INTO snippets_tags (snippet_id, tag) VALUES (?, ?)", [ + snippet_id, + tag, + ]) + .catch((error) => { + console.error("Tag:", error); + }); } // Insert a new note async function insertSnippetNote(snippet_id: number, note: string) { const db = await getDb(); - await db.execute( - "INSERT INTO snippets_notes (snippet_id, note) VALUES (?, ?)", - [snippet_id, note] - ); + await db + .execute("INSERT INTO snippets_notes (snippet_id, note) VALUES (?, ?)", [ + snippet_id, + note, + ]) + .catch((error) => { + console.error("Note:", error); + }); } // Update a snippet -async function updateSnippet(snippet: { +export async function updateSnippet(snippet: { id: number; name?: string; path?: string; @@ -74,16 +92,16 @@ async function updateSnippet(snippet: { } // Get snippet by ID -async function getSnippetById(id: number) { +export async function getSnippetById(id: number) { const db = await getDb(); - const snippet = await db.execute("SELECT * FROM snippets WHERE id = ?", [id]); - return snippet; + const snippet = await db.select("SELECT * FROM snippets WHERE id = ?", [id]); + return snippet && Array.isArray(snippet) ? snippet[0] : {}; } export async function findAllSnippets() { try { const db = await getDb(); - const snippets = await db.execute("SELECT * FROM snippets"); + const snippets = await db.select("SELECT * FROM snippets"); return snippets; } catch (error) { console.error(error); @@ -91,38 +109,157 @@ export async function findAllSnippets() { } } +type ListSnippetsProps = { + limit?: number; + skip?: number; + search?: string; + tags?: string[]; + parent_id?: number; + fields?: string[]; +}; + +export async function listSnippets(props: ListSnippetsProps) { + const db = await getDb(); + const { limit = 10, skip = 0, search = "", tags = [], parent_id } = props; + + const tagFilter = tags.length + ? `AND snippet_id IN ( + SELECT snippet_id FROM snippets_tags WHERE tag IN (${tags.map( + () => "?" + )}) + )` + : ""; + + const fields = props.fields ? props.fields.join(", ") : "*"; + + const snippets = await db.select( + `SELECT ? FROM snippets + WHERE name LIKE ? + AND parent_id = ? + ${tagFilter} + LIMIT ? + OFFSET ?`, + [fields, `%${search}%`, parent_id, ...tags, limit, skip] + ); + + return snippets; +} + export async function seedDatabase() { const db = await getDb(); - // Clear existing data (optional) - await db.execute("DELETE FROM snippets"); - await db.execute("DELETE FROM snippets_tags"); - await db.execute("DELETE FROM snippets_notes"); - - // Insert sample snippets - const snippetId1 = await insertSnippet({ - name: "Sample Snippet 1", - path: "/path/to/snippet1", - content: "Content of the first snippet.", - filetype: "text", - parent_id: undefined, - }); - - const snippetId2 = await insertSnippet({ - name: "Sample Snippet 2", - path: "/path/to/snippet2", - content: "Content of the second snippet.", - filetype: "text", - parent_id: undefined, - }); - - // Insert tags - await insertSnippetTag(snippetId1, "sample"); - await insertSnippetTag(snippetId2, "example"); - - // Insert notes - await insertSnippetNote(snippetId1, "This is a note for the first snippet."); - await insertSnippetNote(snippetId2, "This is a note for the second snippet."); - - console.log("Database seeded successfully."); + try { + // Clear existing data (optional) + await db.execute("DELETE FROM snippets_notes"); + await db.execute("DELETE FROM snippets_tags"); + await db.execute("DELETE FROM snippets"); + } catch (error) { + console.error("Failed to clear existing data:", error); + } + try { + // Insert sample snippets + const snippetId1 = await insertSnippet({ + name: "Kill port 3000 & 3001", + path: "/bash/ports", + content: "kill -9 $(lsof -ti:3000,3001)", + filetype: "bash", + parent_id: undefined, + }); + + const snippetId2 = await insertSnippet({ + name: "Grainy background", + path: "/css/backgrounds", + content: `.bg-grain { + z-index: auto; + position: fixed; + top: 0%; + bottom: 0%; + left: 0%; + right: 0%; +} + +.bg-grain:after { + animation: grain 8s steps(10) infinite; + background-image: url(https://uploads.com/noise.jpg); + content: ""; + height: 300%; + left: -50%; + opacity: 0.10; + position: fixed; + z-index: -10; + top: -100%; + width: 300%; +} + +::selection { + background: #955FFB; + color: #EAE5DB; + text-shadow: 0 0 6px #CEC9C9, 0 0 20px rgba(206,201,201,0.42); +} +`, + filetype: "css", + parent_id: undefined, + }); + + await insertSnippet({ + name: "Role Permissions", + path: "/sql/permissions", + filetype: "sql", + parent_id: undefined, + content: `SELECT t.name as "team", + r.name as "role", + u.displayName as "member" +FROM core.team_user as tu + JOIN core.user u ON tu.userId = u.id + JOIN core.role r ON tu.roleId = r.id + JOIN core.team t ON tu.teamId = t.id +WHERE tu.teamId = 1`, + }); + + await insertSnippet({ + name: "Get Interface", + path: "ts/interfaces", + filetype: "typescript", + parent_id: undefined, + content: `interface MyInterface { + id: number; + name: string; + properties: string[]; +} + +const myObject: MyInterface = { + id: 1, + name: 'foo', + properties: ['a', 'b', 'c'] +}; + +function getValue(value: keyof MyInterface) { + return myObject[value]; +} +`, + }); + + // Insert tags + if (!snippetId1 || !snippetId2) { + console.error("Failed to insert snippets."); + return; + } + console.log("Snippet IDs:", snippetId1, snippetId2); + await insertSnippetTag(snippetId1, "sample"); + await insertSnippetTag(snippetId2, "example"); + + // Insert notes + await insertSnippetNote( + snippetId1, + "This is a note for the first snippet." + ); + await insertSnippetNote( + snippetId2, + "This is a note for the second snippet." + ); + + console.log("Database seeded successfully."); + } catch (error) { + console.error("Failed to seed database:", error); + } } From 9a960e3d177c5de168badab4969c9b0a1ab9fbe2 Mon Sep 17 00:00:00 2001 From: Thijs Zijdel Date: Sun, 15 Sep 2024 22:25:46 +0200 Subject: [PATCH 4/5] fix: improve typings & force files --- src-tauri/src/main.rs | 10 +- src/Features/snippets/Snippets.module.css | 30 +- src/Features/snippets/Snippets.tsx | 252 ++++++++++++---- src/types/snippets.ts | 39 +++ src/utils/database.ts | 334 +++++++++++++++------- 5 files changed, 501 insertions(+), 164 deletions(-) create mode 100644 src/types/snippets.ts diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 806c13a..f998662 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -31,9 +31,6 @@ fn main() { id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, path TEXT NOT NULL, - content TEXT, - filetype TEXT, - parent_id INTEGER, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ); @@ -52,7 +49,12 @@ fn main() { CREATE TABLE snippets_files ( id INTEGER PRIMARY KEY AUTOINCREMENT, snippet_id INTEGER NOT NULL, - file_path TEXT NOT NULL, + name TEXT, + file_path TEXT, + filetype TEXT, + content TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY(snippet_id) REFERENCES snippets (id) );", kind: MigrationKind::Up, diff --git a/src/Features/snippets/Snippets.module.css b/src/Features/snippets/Snippets.module.css index 8de1067..44cdba5 100644 --- a/src/Features/snippets/Snippets.module.css +++ b/src/Features/snippets/Snippets.module.css @@ -1,28 +1,48 @@ .sidebar { - width: 150px; + --sidebar-width: 275px; + width: var(--sidebar-width); + min-width: var(--sidebar-width); + max-width: var(--sidebar-width); } ul { list-style: none; padding: 0; + margin: 0; } li { padding: 0.5rem; font-size: 12px; } + +.active, li:hover { - background-color: #f0f0f0; + background-color: light-dark( + var(--mantine-color-gray-1), + var(--mantine-color-dark-8) + ); cursor: pointer; } -.active { - background-color: #f0f0f0; -} li button { + display: flex; + align-items: center; + justify-content: start; background: transparent; + text-align: left; border: none; } .content { width: 100%; } + +.header { + margin: 0.26em; +} + +.tabInput input { + min-width: 0; + width: auto; + max-width: 150px; +} diff --git a/src/Features/snippets/Snippets.tsx b/src/Features/snippets/Snippets.tsx index 6283448..03d3641 100644 --- a/src/Features/snippets/Snippets.tsx +++ b/src/Features/snippets/Snippets.tsx @@ -1,47 +1,122 @@ -import { Flex } from "@mantine/core"; +import { Button, Flex, Stack, Tabs, TextInput } from "@mantine/core"; import { findAllSnippets, getSnippetById, + getSnippetFiles, + getSnippetNotes, + getSnippetTags, + insertSnippetFile, listSnippets, seedDatabase, - updateSnippet, + updateSnippetFile, } from "@/utils/database"; import { useEffect, useState } from "react"; import styles from "./Snippets.module.css"; +import type { + Snippet, + SnippetFile, + SnippetNote, + SnippetTag, +} from "@/types/snippets"; import { Monaco } from "@/Components/MonacoWrapper"; type SideBar = { tags?: string[]; search?: string; - parent_id?: number; fields?: string[]; }; + +type CombinedSnippet = { + snippet: Snippet; + files: SnippetFile[]; + notes: SnippetNote[]; + tags: SnippetTag[]; +}; const Snippets = () => { - const [snippetId, setSnippetId] = useState(null); - const [snippet, setSnippet] = useState(null); + const [activeIds, setActiveIds] = useState<{ + snippetId?: number; + fileId?: number; + noteId?: number; + }>({}); + + const activateId = (key: string, value: number) => + setActiveIds((prev) => ({ ...prev, [key]: value })); + + const [snippet, setSnippet] = useState(null); const [snippets, setSnippets] = useState([]); const [sidebar] = useState({ - fields: ["id", "name", "path", "filetype", "parent_id"], + fields: ["id", "name", "path", "filetype"], }); // "content" useEffect(() => { - listSnippets(sidebar).then((r) => { - setSnippets(r as any); - }); + const load = () => + listSnippets(sidebar).then((r) => { + console.log(r); + setSnippets(r as any); + }); + + load().then(() => {}); }, [...Object.values(sidebar)]); const loadSnippet = async (id: number) => { - setSnippetId(id); - await getSnippetById(id).then((r) => { - setSnippet(r as any); + activateId("snippetId", id); + const [snippet, files, notes, tags] = await Promise.all([ + getSnippetById(id), + getSnippetFiles(id), + getSnippetNotes(id), + getSnippetTags(id), + ]); + + if (!snippet) return; + setSnippet({ snippet, files, notes, tags }); + setActiveIds({ + snippetId: id, + fileId: files[0]?.id, + noteId: notes[0]?.id, }); }; + // Update current file const onChange = async (content: string) => { - setSnippet((prev: any) => ({ ...prev, content })); - await updateSnippet({ ...snippet, content, id: snippet.id }); + if (!snippet) return; + if (!activeIds.fileId) return; + const file = snippet.files.find((f) => f.id === activeIds.fileId); + if (!file) return; + await updateSnippetFile(file.id, { content }); + setSnippet((prev) => { + if (!prev) return null; + return { + ...prev, + files: prev.files.map((f) => + f.id === activeIds.fileId ? { ...f, content } : f + ), + }; + }); + }; + + const addFile = async () => { + if (!snippet) return; + const newFile = { + name: "Untitled", + content: "", + filetype: "js", + }; + if (!activeIds.snippetId) return; + const fileId = await insertSnippetFile(activeIds.snippetId, newFile); + if (!fileId) return; + setSnippet((prev) => { + if (!prev) return null; + return { + ...prev, + files: [...(prev?.files ?? []), { ...newFile, id: fileId }], + } as CombinedSnippet; + }); + setActiveIds((prev) => ({ + ...prev, + fileId, + })); }; const seed = async () => { @@ -54,47 +129,124 @@ const Snippets = () => { }; return ( - - -
- -
{JSON.stringify(snippet, null, 2)}
-
-
+
loadSnippet(snippet.id)} + onKeyDown={async (e) => { + if (e.key === "Enter") { + await loadSnippet(snippet.id); + } + }} + > + {snippet.name} +
+ + ))} + + + +
+ + setActiveIds((prev) => ({ + ...prev, + fileId: parseInt(fileId as string), + })) + } + > + + {snippet?.files?.map((t) => ( + { + if (e.button === 1) { + // const tabid = tabs.find((el) => el === t); + // setTabs(tabs.filter((e) => e !== t)); + setActiveIds((prev) => ({ + ...prev, + fileId: undefined, + })); + } + }} + > + { + setSnippet((prev) => { + if (!prev) return null; + return { + ...prev, + files: prev.files.map((f) => + f.id === t.id ? { ...f, name: e.target.value } : f + ), + }; + }); + }} + /> + + ))} + + + + {snippet?.files?.map((file) => ( + + + + ))} + + + + +
+ + ); }; diff --git a/src/types/snippets.ts b/src/types/snippets.ts new file mode 100644 index 0000000..bf39844 --- /dev/null +++ b/src/types/snippets.ts @@ -0,0 +1,39 @@ +export type SnippetTag = { + id: number; + snippet_id: number; + tag: string; +}; + +export type SnippetNote = { + id: number; + snippet_id: number; + note: string; +}; + +export type SnippetFile = { + id: number; + snippet_id: number; + name?: string; + file_path?: string; + filetype?: string; + content?: string; + + created_at: string; + updated_at: string; +}; + +export type SnippetFileInput = Pick< + SnippetFile, + "name" | "file_path" | "filetype" | "content" +>; + +export type Snippet = { + id: number; + name: string; + path: string; + + created_at: string; + updated_at: string; +}; + +export type SnippetInput = Pick; diff --git a/src/utils/database.ts b/src/utils/database.ts index b57516e..1a40521 100644 --- a/src/utils/database.ts +++ b/src/utils/database.ts @@ -1,102 +1,217 @@ import Database from "tauri-plugin-sql-api"; +import { + Snippet, + SnippetFile, + SnippetFileInput, + SnippetInput, + SnippetNote, + SnippetTag, +} from "@/types/snippets"; const getDb = async () => await Database.load("sqlite:devtools.db"); -// Insert a new snippet -async function insertSnippet(snippet: { - name: string; - path: string; - content?: string; - filetype?: string; - parent_id?: number; -}) { +const success = (message: string) => ({ status: 200, message }); +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const failure = (message: string, error?: any) => ({ status: 501, message }); + +// Insertions + +const insertSnippet = async ( + snippet: SnippetInput, + files: SnippetFileInput | SnippetFileInput[] +) => { const db = await getDb(); - try { - const snippetId = await db - .execute( - "INSERT INTO snippets (name, path, content, filetype, parent_id) VALUES (?, ?, ?, ?, ?)", - [ - snippet.name, - snippet.path, - snippet.content, - snippet.filetype, - snippet.parent_id, - ] - ) - .then((result) => result.lastInsertId) - .catch((error) => { - console.error(error); - return undefined; - }); - - return snippetId; - } catch (error) { - console.error("Failed to insert snippet:", error); - return undefined; - } -} + const snippetId = await db + .execute("INSERT INTO snippets (name, path) VALUES (?, ?)", [ + snippet.name, + snippet.path, + ]) + .then((result) => result.lastInsertId) + .catch((error) => { + console.error(error); + return undefined; + }); + + if (!snippetId) return undefined; -// Insert a new tag -async function insertSnippetTag(snippet_id: number, tag: string) { + files = Array.isArray(files) ? files : [files]; + await Promise.all(files.map((file) => insertSnippetFile(snippetId, file))); + + return snippetId; +}; + +export const insertSnippetFile = async ( + snippet_id: number, + file: SnippetFileInput +) => { const db = await getDb(); - await db + return db + .execute( + "INSERT INTO snippets_files (snippet_id, name, file_path, filetype, content) VALUES (?, ?, ?, ?, ?)", + [snippet_id, file.name, file.file_path, file.filetype, file.content] + ) + .then((result) => result.lastInsertId) + .catch((error) => { + console.error("File:", error); + return undefined; + }); +}; + +const insertSnippetTag = async (snippet_id: number, tag: string) => { + const db = await getDb(); + return db .execute("INSERT INTO snippets_tags (snippet_id, tag) VALUES (?, ?)", [ snippet_id, tag, ]) + .then((result) => result.lastInsertId) .catch((error) => { console.error("Tag:", error); + return undefined; }); -} +}; -// Insert a new note -async function insertSnippetNote(snippet_id: number, note: string) { +const insertSnippetNote = async (snippet_id: number, note: string) => { const db = await getDb(); - await db + return db .execute("INSERT INTO snippets_notes (snippet_id, note) VALUES (?, ?)", [ snippet_id, note, ]) + .then((result) => result.lastInsertId) .catch((error) => { console.error("Note:", error); + return undefined; }); -} +}; -// Update a snippet -export async function updateSnippet(snippet: { - id: number; - name?: string; - path?: string; - content?: string; - filetype?: string; - parent_id?: number; -}) { +// Updates + +export const updateSnippet = async ( + snippet_id: number, + snippet: SnippetInput +) => { const db = await getDb(); - await db.execute( - `UPDATE snippets SET - name = COALESCE(?, name), - path = COALESCE(?, path), - content = COALESCE(?, content), - filetype = COALESCE(?, filetype), - parent_id = COALESCE(?, parent_id) - WHERE id = ?`, - [ - snippet.name, - snippet.path, - snippet.content, - snippet.filetype, - snippet.parent_id, - snippet.id, - ] - ); -} + return db + .execute( + "UPDATE snippets SET name = ?, path = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?", + [snippet.name, snippet.path, snippet_id] + ) + .then(() => success("Snippet updated successfully.")) + .catch((error) => failure("Failed to update snippet.", error)); +}; + +export const updateSnippetFile = async ( + file_id: number, + snippet: SnippetFileInput +) => { + const db = await getDb(); + return db + .execute( + `UPDATE snippets_files + SET name = ?, + file_path = ?, + filetype = ?, + content = ?, + updated_at = CURRENT_TIMESTAMP + WHERE id = ?`, + [ + snippet.name, + snippet.file_path, + snippet.filetype, + snippet.content, + file_id, + ] + ) + .then(() => success("Snippet file updated successfully.")) + .catch((error) => failure("Failed to update snippet file.", error)); +}; + +export const updateSnippetNote = async (note_id: number, note: string) => { + const db = await getDb(); + return db + .execute(`UPDATE snippets_notes SET note = ? WHERE id = ?`, [note, note_id]) + .then(() => success("Snippet note updated successfully.")) + .catch((error) => failure("Failed to update snippet note.", error)); +}; + +// Removals + +export const removeSnippetNote = async ( + key: string, + value: string | number +) => { + const db = await getDb(); + return db + .execute("DELETE FROM snippets_notes WHERE ? = ?", [key, value]) + .then(() => success("Snippet note removed successfully.")) + .catch((error) => failure("Failed to remove snippet note.", error)); +}; + +export const removeSnippetFile = async ( + key: string, + value: string | number +) => { + const db = await getDb(); + return db + .execute("DELETE FROM snippets_files WHERE ? = ?", [key, value]) + .then(() => success("Snippet file removed successfully.")) + .catch((error) => failure("Failed to remove snippet file.", error)); +}; + +export const removeSnippetTag = async (key: string, value: string | number) => + await ( + await getDb() + ) + .execute("DELETE FROM snippets_tags WHERE ? = ?", [key, value]) + .then(() => success("Snippet tag removed successfully.")) + .catch((error) => failure("Failed to remove snippet tag.", error)); + +export const removeSnippet = async (snippet_id: number) => { + const db = await getDb(); + await removeSnippetNote("snippet_id", snippet_id); + await removeSnippetTag("snippet_id", snippet_id); + await removeSnippetFile("snippet_id", snippet_id); + await db.execute("DELETE FROM snippets WHERE id = ?", [snippet_id]); +}; // Get snippet by ID -export async function getSnippetById(id: number) { +export const getSnippetById = async ( + id: number +): Promise => { const db = await getDb(); const snippet = await db.select("SELECT * FROM snippets WHERE id = ?", [id]); - return snippet && Array.isArray(snippet) ? snippet[0] : {}; -} + return snippet && Array.isArray(snippet) ? snippet[0] : undefined; +}; + +export const getSnippetFiles = async (snippet_id: number) => { + const db = await getDb(); + const files = await db.select( + "SELECT * FROM snippets_files WHERE snippet_id = ?", + [snippet_id] + ); + return files as SnippetFile[]; +}; + +export const getSnippetTags = async (snippet_id: number) => { + const db = await getDb(); + const tags = await db.select( + "SELECT * FROM snippets_tags WHERE snippet_id = ?", + [snippet_id] + ); + return tags as SnippetTag[]; +}; + +export const getSnippetNotes = async (snippet_id: number) => { + const db = await getDb(); + const notes = await db.select( + "SELECT * FROM snippets_notes WHERE snippet_id = ?", + [snippet_id] + ); + return notes as SnippetNote[]; +}; + +// List snippets export async function findAllSnippets() { try { @@ -114,13 +229,12 @@ type ListSnippetsProps = { skip?: number; search?: string; tags?: string[]; - parent_id?: number; fields?: string[]; }; export async function listSnippets(props: ListSnippetsProps) { const db = await getDb(); - const { limit = 10, skip = 0, search = "", tags = [], parent_id } = props; + const { limit = 10, skip = 0, search = "", tags = [] } = props; const tagFilter = tags.length ? `AND snippet_id IN ( @@ -130,16 +244,13 @@ export async function listSnippets(props: ListSnippetsProps) { )` : ""; - const fields = props.fields ? props.fields.join(", ") : "*"; - const snippets = await db.select( - `SELECT ? FROM snippets + `SELECT * FROM snippets WHERE name LIKE ? - AND parent_id = ? ${tagFilter} LIMIT ? OFFSET ?`, - [fields, `%${search}%`, parent_id, ...tags, limit, skip] + [`%${search}%`, ...tags, limit, skip] ); return snippets; @@ -152,24 +263,32 @@ export async function seedDatabase() { // Clear existing data (optional) await db.execute("DELETE FROM snippets_notes"); await db.execute("DELETE FROM snippets_tags"); + await db.execute("DELETE FROM snippets_files"); await db.execute("DELETE FROM snippets"); } catch (error) { console.error("Failed to clear existing data:", error); } try { // Insert sample snippets - const snippetId1 = await insertSnippet({ - name: "Kill port 3000 & 3001", - path: "/bash/ports", - content: "kill -9 $(lsof -ti:3000,3001)", - filetype: "bash", - parent_id: undefined, - }); + const snippetId1 = await insertSnippet( + { + name: "Kill port 3000 & 3001", + path: "/bash/ports", + }, + { + filetype: "bash", + content: "kill -9 $(lsof -ti:3000,3001)", + } + ); - const snippetId2 = await insertSnippet({ - name: "Grainy background", - path: "/css/backgrounds", - content: `.bg-grain { + const snippetId2 = await insertSnippet( + { + name: "Grainy background", + path: "/css/backgrounds", + }, + { + filetype: "css", + content: `.bg-grain { z-index: auto; position: fixed; top: 0%; @@ -197,16 +316,17 @@ export async function seedDatabase() { text-shadow: 0 0 6px #CEC9C9, 0 0 20px rgba(206,201,201,0.42); } `, - filetype: "css", - parent_id: undefined, - }); + } + ); - await insertSnippet({ - name: "Role Permissions", - path: "/sql/permissions", - filetype: "sql", - parent_id: undefined, - content: `SELECT t.name as "team", + await insertSnippet( + { + name: "Role Permissions", + path: "/sql/permissions", + }, + { + filetype: "sql", + content: `SELECT t.name as "team", r.name as "role", u.displayName as "member" FROM core.team_user as tu @@ -214,14 +334,17 @@ FROM core.team_user as tu JOIN core.role r ON tu.roleId = r.id JOIN core.team t ON tu.teamId = t.id WHERE tu.teamId = 1`, - }); + } + ); - await insertSnippet({ - name: "Get Interface", - path: "ts/interfaces", - filetype: "typescript", - parent_id: undefined, - content: `interface MyInterface { + await insertSnippet( + { + name: "Get Interface", + path: "ts/interfaces", + }, + { + filetype: "typescript", + content: `interface MyInterface { id: number; name: string; properties: string[]; @@ -237,7 +360,8 @@ function getValue(value: keyof MyInterface) { return myObject[value]; } `, - }); + } + ); // Insert tags if (!snippetId1 || !snippetId2) { From 2438a7c5531fff9fe66ffb561bc4987d3593e251 Mon Sep 17 00:00:00 2001 From: Thijs Zijdel Date: Sun, 15 Sep 2024 22:36:05 +0200 Subject: [PATCH 5/5] feat: add file type changer --- src/Features/snippets/Snippets.tsx | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/Features/snippets/Snippets.tsx b/src/Features/snippets/Snippets.tsx index 03d3641..9e0ed04 100644 --- a/src/Features/snippets/Snippets.tsx +++ b/src/Features/snippets/Snippets.tsx @@ -172,7 +172,9 @@ const Snippets = () => { ))} - +
{ key={t.id} onMouseDown={async (e) => { if (e.button === 1) { - // const tabid = tabs.find((el) => el === t); - // setTabs(tabs.filter((e) => e !== t)); setActiveIds((prev) => ({ ...prev, fileId: undefined, @@ -238,6 +238,28 @@ const Snippets = () => { onChange={onChange as any} height="500px" /> + { + setSnippet((prev) => { + if (!prev) return null; + return { + ...prev, + files: prev.files.map((f) => + f.id === file.id + ? { ...f, filetype: e.target.value } + : f + ), + }; + }); + + updateSnippetFile(file.id, { + ...file, + filetype: e.target.value, + }).then(() => {}); + }} + /> ))}