diff --git a/package.json b/package.json index 392bf91..e164f44 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "clsx": "^2.1.1", "lucide-react": "^0.563.0", "next-themes": "^0.4.6", + "pretty-bytes": "^7.1.0", "react": "^19.2.4", "react-dom": "^19.2.4", "react-xtermjs": "^1.0.10", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ac2cdb6..5c524b2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -92,6 +92,9 @@ importers: next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + pretty-bytes: + specifier: ^7.1.0 + version: 7.1.0 react: specifier: ^19.2.4 version: 19.2.4 @@ -1051,79 +1054,66 @@ packages: resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==} cpu: [arm] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.57.1': resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==} cpu: [arm] os: [linux] - libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.57.1': resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==} cpu: [arm64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.57.1': resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==} cpu: [arm64] os: [linux] - libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.57.1': resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==} cpu: [loong64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.57.1': resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==} cpu: [loong64] os: [linux] - libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.57.1': resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==} cpu: [ppc64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.57.1': resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==} cpu: [ppc64] os: [linux] - libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.57.1': resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==} cpu: [riscv64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.57.1': resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==} cpu: [riscv64] os: [linux] - libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.57.1': resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==} cpu: [s390x] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.57.1': resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==} cpu: [x64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-musl@4.57.1': resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==} cpu: [x64] os: [linux] - libc: [musl] '@rollup/rollup-openbsd-x64@4.57.1': resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==} @@ -1231,28 +1221,24 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.1.18': resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.1.18': resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.1.18': resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.1.18': resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==} @@ -1425,35 +1411,30 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@tauri-apps/cli-linux-arm64-musl@2.10.0': resolution: {integrity: sha512-GUoPdVJmrJRIXFfW3Rkt+eGK9ygOdyISACZfC/bCSfOnGt8kNdQIQr5WRH9QUaTVFIwxMlQyV3m+yXYP+xhSVA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] '@tauri-apps/cli-linux-riscv64-gnu@2.10.0': resolution: {integrity: sha512-JO7s3TlSxshwsoKNCDkyvsx5gw2QAs/Y2GbR5UE2d5kkU138ATKoPOtxn8G1fFT1aDW4LH0rYAAfBpGkDyJJnw==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] - libc: [glibc] '@tauri-apps/cli-linux-x64-gnu@2.10.0': resolution: {integrity: sha512-Uvh4SUUp4A6DVRSMWjelww0GnZI3PlVy7VS+DRF5napKuIehVjGl9XD0uKoCoxwAQBLctvipyEK+pDXpJeoHng==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] '@tauri-apps/cli-linux-x64-musl@2.10.0': resolution: {integrity: sha512-AP0KRK6bJuTpQ8kMNWvhIpKUkQJfcPFeba7QshOQZjJ8wOS6emwTN4K5g/d3AbCMo0RRdnZWwu67MlmtJyxC1Q==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@tauri-apps/cli-win32-arm64-msvc@2.10.0': resolution: {integrity: sha512-97DXVU3dJystrq7W41IX+82JEorLNY+3+ECYxvXWqkq7DBN6FsA08x/EFGE8N/b0LTOui9X2dvpGGoeZKKV08g==} @@ -2761,28 +2742,24 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] lightningcss-linux-arm64-musl@1.30.2: resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] lightningcss-linux-x64-gnu@1.30.2: resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] lightningcss-linux-x64-musl@1.30.2: resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] lightningcss-win32-arm64-msvc@1.30.2: resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} @@ -3281,6 +3258,10 @@ packages: engines: {node: '>=14'} hasBin: true + pretty-bytes@7.1.0: + resolution: {integrity: sha512-nODzvTiYVRGRqAOvE84Vk5JDPyyxsVk0/fbA/bq7RqlnhksGpset09XTxbpvLTIjoaF7K8Z8DG8yHtKGTPSYRw==} + engines: {node: '>=20'} + pretty-format@27.5.1: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} @@ -7339,6 +7320,8 @@ snapshots: prettier@3.8.1: {} + pretty-bytes@7.1.0: {} + pretty-format@27.5.1: dependencies: ansi-regex: 5.0.1 diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 7edb3f2..e1c405b 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -223,6 +223,21 @@ dependencies = [ "xattr", ] +[[package]] +name = "astral_async_zip" +version = "0.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab72a761e6085828cc8f0e05ed332b2554701368c5dc54de551bfaec466518ba" +dependencies = [ + "async-compression", + "crc32fast", + "futures-lite", + "pin-project", + "thiserror 1.0.69", + "tokio", + "tokio-util", +] + [[package]] name = "async-broadcast" version = "0.7.2" @@ -400,6 +415,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "async-spooled-tempfile" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9c58a1dbecfc23aa470d57e5aff60877ab1b459bf05e60e861dd18fcaa5f5" +dependencies = [ + "tempfile", + "tokio", +] + [[package]] name = "async-task" version = "4.7.1" @@ -955,7 +980,7 @@ dependencies = [ [[package]] name = "barrier_cell" version = "0.1.0" -source = "git+https://github.com/prefix-dev/pixi?rev=ce8654e0a401024dda627bc550059a89fb4add0a#ce8654e0a401024dda627bc550059a89fb4add0a" +source = "git+https://github.com/prefix-dev/pixi?rev=7bf7c1be384f29a8f894e2b15835b79c39bcc533#7bf7c1be384f29a8f894e2b15835b79c39bcc533" dependencies = [ "parking_lot", "thiserror 2.0.18", @@ -2446,7 +2471,7 @@ dependencies = [ [[package]] name = "fancy_display" version = "0.1.0" -source = "git+https://github.com/prefix-dev/pixi?rev=ce8654e0a401024dda627bc550059a89fb4add0a#ce8654e0a401024dda627bc550059a89fb4add0a" +source = "git+https://github.com/prefix-dev/pixi?rev=7bf7c1be384f29a8f894e2b15835b79c39bcc533#7bf7c1be384f29a8f894e2b15835b79c39bcc533" dependencies = [ "console", ] @@ -5787,7 +5812,7 @@ dependencies = [ [[package]] name = "pixi_api" version = "0.1.0" -source = "git+https://github.com/prefix-dev/pixi?rev=ce8654e0a401024dda627bc550059a89fb4add0a#ce8654e0a401024dda627bc550059a89fb4add0a" +source = "git+https://github.com/prefix-dev/pixi?rev=7bf7c1be384f29a8f894e2b15835b79c39bcc533#7bf7c1be384f29a8f894e2b15835b79c39bcc533" dependencies = [ "console", "dunce", @@ -5805,6 +5830,8 @@ dependencies = [ "pixi_pypi_spec", "pixi_spec", "pixi_utils", + "pixi_uv_conversions", + "pypi_modifiers", "rattler_conda_types", "rattler_lock", "rattler_repodata_gateway", @@ -5816,13 +5843,16 @@ dependencies = [ "tokio", "tracing", "url", + "uv-distribution", + "uv-distribution-types", "uv-normalize", + "uv-types", ] [[package]] name = "pixi_auth" version = "0.1.0" -source = "git+https://github.com/prefix-dev/pixi?rev=ce8654e0a401024dda627bc550059a89fb4add0a#ce8654e0a401024dda627bc550059a89fb4add0a" +source = "git+https://github.com/prefix-dev/pixi?rev=7bf7c1be384f29a8f894e2b15835b79c39bcc533#7bf7c1be384f29a8f894e2b15835b79c39bcc533" dependencies = [ "pixi_config", "rattler_networking", @@ -5832,7 +5862,7 @@ dependencies = [ [[package]] name = "pixi_build_discovery" version = "0.1.0" -source = "git+https://github.com/prefix-dev/pixi?rev=ce8654e0a401024dda627bc550059a89fb4add0a#ce8654e0a401024dda627bc550059a89fb4add0a" +source = "git+https://github.com/prefix-dev/pixi?rev=7bf7c1be384f29a8f894e2b15835b79c39bcc533#7bf7c1be384f29a8f894e2b15835b79c39bcc533" dependencies = [ "dunce", "itertools 0.14.0", @@ -5855,7 +5885,7 @@ dependencies = [ [[package]] name = "pixi_build_frontend" version = "0.1.0" -source = "git+https://github.com/prefix-dev/pixi?rev=ce8654e0a401024dda627bc550059a89fb4add0a#ce8654e0a401024dda627bc550059a89fb4add0a" +source = "git+https://github.com/prefix-dev/pixi?rev=7bf7c1be384f29a8f894e2b15835b79c39bcc533#7bf7c1be384f29a8f894e2b15835b79c39bcc533" dependencies = [ "fs-err", "futures", @@ -5876,7 +5906,7 @@ dependencies = [ [[package]] name = "pixi_build_type_conversions" version = "0.1.0" -source = "git+https://github.com/prefix-dev/pixi?rev=ce8654e0a401024dda627bc550059a89fb4add0a#ce8654e0a401024dda627bc550059a89fb4add0a" +source = "git+https://github.com/prefix-dev/pixi?rev=7bf7c1be384f29a8f894e2b15835b79c39bcc533#7bf7c1be384f29a8f894e2b15835b79c39bcc533" dependencies = [ "itertools 0.14.0", "ordermap", @@ -5889,7 +5919,7 @@ dependencies = [ [[package]] name = "pixi_build_types" version = "0.1.0" -source = "git+https://github.com/prefix-dev/pixi?rev=ce8654e0a401024dda627bc550059a89fb4add0a#ce8654e0a401024dda627bc550059a89fb4add0a" +source = "git+https://github.com/prefix-dev/pixi?rev=7bf7c1be384f29a8f894e2b15835b79c39bcc533#7bf7c1be384f29a8f894e2b15835b79c39bcc533" dependencies = [ "ordermap", "pixi_stable_hash", @@ -5904,7 +5934,7 @@ dependencies = [ [[package]] name = "pixi_command_dispatcher" version = "0.1.0" -source = "git+https://github.com/prefix-dev/pixi?rev=ce8654e0a401024dda627bc550059a89fb4add0a#ce8654e0a401024dda627bc550059a89fb4add0a" +source = "git+https://github.com/prefix-dev/pixi?rev=7bf7c1be384f29a8f894e2b15835b79c39bcc533#7bf7c1be384f29a8f894e2b15835b79c39bcc533" dependencies = [ "async-fd-lock", "base64 0.22.1", @@ -5963,7 +5993,7 @@ dependencies = [ [[package]] name = "pixi_config" version = "0.1.0" -source = "git+https://github.com/prefix-dev/pixi?rev=ce8654e0a401024dda627bc550059a89fb4add0a#ce8654e0a401024dda627bc550059a89fb4add0a" +source = "git+https://github.com/prefix-dev/pixi?rev=7bf7c1be384f29a8f894e2b15835b79c39bcc533#7bf7c1be384f29a8f894e2b15835b79c39bcc533" dependencies = [ "clap", "console", @@ -5989,7 +6019,7 @@ dependencies = [ [[package]] name = "pixi_consts" version = "0.1.0" -source = "git+https://github.com/prefix-dev/pixi?rev=ce8654e0a401024dda627bc550059a89fb4add0a#ce8654e0a401024dda627bc550059a89fb4add0a" +source = "git+https://github.com/prefix-dev/pixi?rev=7bf7c1be384f29a8f894e2b15835b79c39bcc533#7bf7c1be384f29a8f894e2b15835b79c39bcc533" dependencies = [ "console", "rattler_cache", @@ -6000,7 +6030,7 @@ dependencies = [ [[package]] name = "pixi_core" version = "0.1.0" -source = "git+https://github.com/prefix-dev/pixi?rev=ce8654e0a401024dda627bc550059a89fb4add0a#ce8654e0a401024dda627bc550059a89fb4add0a" +source = "git+https://github.com/prefix-dev/pixi?rev=7bf7c1be384f29a8f894e2b15835b79c39bcc533#7bf7c1be384f29a8f894e2b15835b79c39bcc533" dependencies = [ "async-once-cell", "barrier_cell", @@ -6097,7 +6127,7 @@ dependencies = [ [[package]] name = "pixi_default_versions" version = "0.1.0" -source = "git+https://github.com/prefix-dev/pixi?rev=ce8654e0a401024dda627bc550059a89fb4add0a#ce8654e0a401024dda627bc550059a89fb4add0a" +source = "git+https://github.com/prefix-dev/pixi?rev=7bf7c1be384f29a8f894e2b15835b79c39bcc533#7bf7c1be384f29a8f894e2b15835b79c39bcc533" dependencies = [ "rattler_conda_types", ] @@ -6105,7 +6135,7 @@ dependencies = [ [[package]] name = "pixi_diff" version = "0.1.0" -source = "git+https://github.com/prefix-dev/pixi?rev=ce8654e0a401024dda627bc550059a89fb4add0a#ce8654e0a401024dda627bc550059a89fb4add0a" +source = "git+https://github.com/prefix-dev/pixi?rev=7bf7c1be384f29a8f894e2b15835b79c39bcc533#7bf7c1be384f29a8f894e2b15835b79c39bcc533" dependencies = [ "ahash 0.8.12", "console", @@ -6123,7 +6153,7 @@ dependencies = [ [[package]] name = "pixi_git" version = "0.0.1" -source = "git+https://github.com/prefix-dev/pixi?rev=ce8654e0a401024dda627bc550059a89fb4add0a#ce8654e0a401024dda627bc550059a89fb4add0a" +source = "git+https://github.com/prefix-dev/pixi?rev=7bf7c1be384f29a8f894e2b15835b79c39bcc533#7bf7c1be384f29a8f894e2b15835b79c39bcc533" dependencies = [ "dashmap", "dunce", @@ -6144,7 +6174,7 @@ dependencies = [ [[package]] name = "pixi_glob" version = "0.1.0" -source = "git+https://github.com/prefix-dev/pixi?rev=ce8654e0a401024dda627bc550059a89fb4add0a#ce8654e0a401024dda627bc550059a89fb4add0a" +source = "git+https://github.com/prefix-dev/pixi?rev=7bf7c1be384f29a8f894e2b15835b79c39bcc533#7bf7c1be384f29a8f894e2b15835b79c39bcc533" dependencies = [ "dashmap", "fs-err", @@ -6161,7 +6191,7 @@ dependencies = [ [[package]] name = "pixi_install_pypi" version = "0.1.0" -source = "git+https://github.com/prefix-dev/pixi?rev=ce8654e0a401024dda627bc550059a89fb4add0a#ce8654e0a401024dda627bc550059a89fb4add0a" +source = "git+https://github.com/prefix-dev/pixi?rev=7bf7c1be384f29a8f894e2b15835b79c39bcc533#7bf7c1be384f29a8f894e2b15835b79c39bcc533" dependencies = [ "ahash 0.8.12", "chrono", @@ -6222,7 +6252,7 @@ dependencies = [ [[package]] name = "pixi_manifest" version = "0.1.0" -source = "git+https://github.com/prefix-dev/pixi?rev=ce8654e0a401024dda627bc550059a89fb4add0a#ce8654e0a401024dda627bc550059a89fb4add0a" +source = "git+https://github.com/prefix-dev/pixi?rev=7bf7c1be384f29a8f894e2b15835b79c39bcc533#7bf7c1be384f29a8f894e2b15835b79c39bcc533" dependencies = [ "chrono", "console", @@ -6263,7 +6293,7 @@ dependencies = [ [[package]] name = "pixi_path" version = "0.1.0" -source = "git+https://github.com/prefix-dev/pixi?rev=ce8654e0a401024dda627bc550059a89fb4add0a#ce8654e0a401024dda627bc550059a89fb4add0a" +source = "git+https://github.com/prefix-dev/pixi?rev=7bf7c1be384f29a8f894e2b15835b79c39bcc533#7bf7c1be384f29a8f894e2b15835b79c39bcc533" dependencies = [ "fs-err", "serde", @@ -6274,7 +6304,7 @@ dependencies = [ [[package]] name = "pixi_progress" version = "0.1.0" -source = "git+https://github.com/prefix-dev/pixi?rev=ce8654e0a401024dda627bc550059a89fb4add0a#ce8654e0a401024dda627bc550059a89fb4add0a" +source = "git+https://github.com/prefix-dev/pixi?rev=7bf7c1be384f29a8f894e2b15835b79c39bcc533#7bf7c1be384f29a8f894e2b15835b79c39bcc533" dependencies = [ "indicatif", "parking_lot", @@ -6283,7 +6313,7 @@ dependencies = [ [[package]] name = "pixi_pypi_spec" version = "0.1.0" -source = "git+https://github.com/prefix-dev/pixi?rev=ce8654e0a401024dda627bc550059a89fb4add0a#ce8654e0a401024dda627bc550059a89fb4add0a" +source = "git+https://github.com/prefix-dev/pixi?rev=7bf7c1be384f29a8f894e2b15835b79c39bcc533#7bf7c1be384f29a8f894e2b15835b79c39bcc533" dependencies = [ "itertools 0.14.0", "pep440_rs", @@ -6303,7 +6333,7 @@ dependencies = [ [[package]] name = "pixi_python_status" version = "0.1.0" -source = "git+https://github.com/prefix-dev/pixi?rev=ce8654e0a401024dda627bc550059a89fb4add0a#ce8654e0a401024dda627bc550059a89fb4add0a" +source = "git+https://github.com/prefix-dev/pixi?rev=7bf7c1be384f29a8f894e2b15835b79c39bcc533#7bf7c1be384f29a8f894e2b15835b79c39bcc533" dependencies = [ "rattler", ] @@ -6311,7 +6341,7 @@ dependencies = [ [[package]] name = "pixi_record" version = "0.1.0" -source = "git+https://github.com/prefix-dev/pixi?rev=ce8654e0a401024dda627bc550059a89fb4add0a#ce8654e0a401024dda627bc550059a89fb4add0a" +source = "git+https://github.com/prefix-dev/pixi?rev=7bf7c1be384f29a8f894e2b15835b79c39bcc533#7bf7c1be384f29a8f894e2b15835b79c39bcc533" dependencies = [ "file_url", "itertools 0.14.0", @@ -6334,7 +6364,7 @@ dependencies = [ [[package]] name = "pixi_reporters" version = "0.1.0" -source = "git+https://github.com/prefix-dev/pixi?rev=ce8654e0a401024dda627bc550059a89fb4add0a#ce8654e0a401024dda627bc550059a89fb4add0a" +source = "git+https://github.com/prefix-dev/pixi?rev=7bf7c1be384f29a8f894e2b15835b79c39bcc533#7bf7c1be384f29a8f894e2b15835b79c39bcc533" dependencies = [ "console", "futures", @@ -6366,7 +6396,7 @@ dependencies = [ [[package]] name = "pixi_spec" version = "0.1.0" -source = "git+https://github.com/prefix-dev/pixi?rev=ce8654e0a401024dda627bc550059a89fb4add0a#ce8654e0a401024dda627bc550059a89fb4add0a" +source = "git+https://github.com/prefix-dev/pixi?rev=7bf7c1be384f29a8f894e2b15835b79c39bcc533#7bf7c1be384f29a8f894e2b15835b79c39bcc533" dependencies = [ "dirs", "file_url", @@ -6392,7 +6422,7 @@ dependencies = [ [[package]] name = "pixi_spec_containers" version = "0.1.0" -source = "git+https://github.com/prefix-dev/pixi?rev=ce8654e0a401024dda627bc550059a89fb4add0a#ce8654e0a401024dda627bc550059a89fb4add0a" +source = "git+https://github.com/prefix-dev/pixi?rev=7bf7c1be384f29a8f894e2b15835b79c39bcc533#7bf7c1be384f29a8f894e2b15835b79c39bcc533" dependencies = [ "indexmap 2.13.0", "itertools 0.14.0", @@ -6404,7 +6434,7 @@ dependencies = [ [[package]] name = "pixi_stable_hash" version = "0.1.0" -source = "git+https://github.com/prefix-dev/pixi?rev=ce8654e0a401024dda627bc550059a89fb4add0a#ce8654e0a401024dda627bc550059a89fb4add0a" +source = "git+https://github.com/prefix-dev/pixi?rev=7bf7c1be384f29a8f894e2b15835b79c39bcc533#7bf7c1be384f29a8f894e2b15835b79c39bcc533" dependencies = [ "ordermap", "rattler_conda_types", @@ -6416,7 +6446,7 @@ dependencies = [ [[package]] name = "pixi_toml" version = "0.1.0" -source = "git+https://github.com/prefix-dev/pixi?rev=ce8654e0a401024dda627bc550059a89fb4add0a#ce8654e0a401024dda627bc550059a89fb4add0a" +source = "git+https://github.com/prefix-dev/pixi?rev=7bf7c1be384f29a8f894e2b15835b79c39bcc533#7bf7c1be384f29a8f894e2b15835b79c39bcc533" dependencies = [ "digest", "hex", @@ -6431,7 +6461,7 @@ dependencies = [ [[package]] name = "pixi_url" version = "0.1.0" -source = "git+https://github.com/prefix-dev/pixi?rev=ce8654e0a401024dda627bc550059a89fb4add0a#ce8654e0a401024dda627bc550059a89fb4add0a" +source = "git+https://github.com/prefix-dev/pixi?rev=7bf7c1be384f29a8f894e2b15835b79c39bcc533#7bf7c1be384f29a8f894e2b15835b79c39bcc533" dependencies = [ "bzip2 0.6.1", "dashmap", @@ -6461,7 +6491,7 @@ dependencies = [ [[package]] name = "pixi_utils" version = "0.1.0" -source = "git+https://github.com/prefix-dev/pixi?rev=ce8654e0a401024dda627bc550059a89fb4add0a#ce8654e0a401024dda627bc550059a89fb4add0a" +source = "git+https://github.com/prefix-dev/pixi?rev=7bf7c1be384f29a8f894e2b15835b79c39bcc533#7bf7c1be384f29a8f894e2b15835b79c39bcc533" dependencies = [ "async-fd-lock", "fs-err", @@ -6497,7 +6527,7 @@ dependencies = [ [[package]] name = "pixi_uv_context" version = "0.1.0" -source = "git+https://github.com/prefix-dev/pixi?rev=ce8654e0a401024dda627bc550059a89fb4add0a#ce8654e0a401024dda627bc550059a89fb4add0a" +source = "git+https://github.com/prefix-dev/pixi?rev=7bf7c1be384f29a8f894e2b15835b79c39bcc533#7bf7c1be384f29a8f894e2b15835b79c39bcc533" dependencies = [ "fs-err", "miette 7.6.0", @@ -6522,7 +6552,7 @@ dependencies = [ [[package]] name = "pixi_uv_conversions" version = "0.1.0" -source = "git+https://github.com/prefix-dev/pixi?rev=ce8654e0a401024dda627bc550059a89fb4add0a#ce8654e0a401024dda627bc550059a89fb4add0a" +source = "git+https://github.com/prefix-dev/pixi?rev=7bf7c1be384f29a8f894e2b15835b79c39bcc533#7bf7c1be384f29a8f894e2b15835b79c39bcc533" dependencies = [ "chrono", "dunce", @@ -6557,7 +6587,7 @@ dependencies = [ [[package]] name = "pixi_variant" version = "0.1.0" -source = "git+https://github.com/prefix-dev/pixi?rev=ce8654e0a401024dda627bc550059a89fb4add0a#ce8654e0a401024dda627bc550059a89fb4add0a" +source = "git+https://github.com/prefix-dev/pixi?rev=7bf7c1be384f29a8f894e2b15835b79c39bcc533#7bf7c1be384f29a8f894e2b15835b79c39bcc533" dependencies = [ "pixi_build_types", "rattler_lock", @@ -6906,7 +6936,7 @@ dependencies = [ [[package]] name = "pypi_mapping" version = "0.1.0" -source = "git+https://github.com/prefix-dev/pixi?rev=ce8654e0a401024dda627bc550059a89fb4add0a#ce8654e0a401024dda627bc550059a89fb4add0a" +source = "git+https://github.com/prefix-dev/pixi?rev=7bf7c1be384f29a8f894e2b15835b79c39bcc533#7bf7c1be384f29a8f894e2b15835b79c39bcc533" dependencies = [ "async-once-cell", "dashmap", @@ -6936,7 +6966,7 @@ dependencies = [ [[package]] name = "pypi_modifiers" version = "0.1.0" -source = "git+https://github.com/prefix-dev/pixi?rev=ce8654e0a401024dda627bc550059a89fb4add0a#ce8654e0a401024dda627bc550059a89fb4add0a" +source = "git+https://github.com/prefix-dev/pixi?rev=7bf7c1be384f29a8f894e2b15835b79c39bcc533#7bf7c1be384f29a8f894e2b15835b79c39bcc533" dependencies = [ "miette 7.6.0", "pixi_default_versions", @@ -7201,9 +7231,9 @@ dependencies = [ [[package]] name = "rattler" -version = "0.39.10" +version = "0.39.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf0a84307599e2db6ddbe757ee7568b11f71a3ce8fa90cc520206a53a93e4482" +checksum = "a2b36266a8e3aa3ff6a029e2b292c3c070a625a28a5d8d420d254d9cc020a5ed" dependencies = [ "anyhow", "digest", @@ -7244,9 +7274,9 @@ dependencies = [ [[package]] name = "rattler_cache" -version = "0.6.9" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b03fb92095e4998b5c985a03edeed2bb4d98dfedf19bc585982237ea1e4df628" +checksum = "c1fbe0b42ef6292283dc1ad0f71b67a43cc30e733de0bab198981e03c1889a0d" dependencies = [ "ahash 0.8.12", "anyhow", @@ -7277,9 +7307,9 @@ dependencies = [ [[package]] name = "rattler_conda_types" -version = "0.43.1" +version = "0.43.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "274c3177bbd304ee0963d37855d62b5234c0b243437d02769fa61d4de79047c6" +checksum = "9005286dbf1cb4ce89da2ff359e97e75b5f7f1cb434857822deb55acba00aa8c" dependencies = [ "ahash 0.8.12", "chrono", @@ -7339,9 +7369,9 @@ dependencies = [ [[package]] name = "rattler_lock" -version = "0.26.12" +version = "0.26.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8c093a0b5edcd91e7823001ca251342332765f10629d80d3954febbb06f095" +checksum = "8f031cf58694067a9ac437d4f7032c7ecd49fe6791cba31fb3866b3d1b6e98a0" dependencies = [ "ahash 0.8.12", "chrono", @@ -7375,9 +7405,9 @@ dependencies = [ [[package]] name = "rattler_menuinst" -version = "0.2.45" +version = "0.2.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7c55d5a3afeb5b2230ff3bc360a1e02a1fedff1698561024d3cd8133a5120" +checksum = "680f72dbbfeff0559b49cf9cccdf2d3c81c41df65d9e2090b18d5a863a51581b" dependencies = [ "chrono", "configparser", @@ -7406,9 +7436,9 @@ dependencies = [ [[package]] name = "rattler_networking" -version = "0.25.32" +version = "0.25.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97335853bd2e219abd08004b9548667ed93feed1e3574e6a4dc052ec3a69266d" +checksum = "0eda517ae63aa07521f0016d42d1cff311f7ab08456148485b3d6b328d791ca3" dependencies = [ "anyhow", "async-once-cell", @@ -7438,15 +7468,18 @@ dependencies = [ [[package]] name = "rattler_package_streaming" -version = "0.23.24" +version = "0.23.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f7c879af2e36e42e829d44f98ecafa2cbf377cdb81002fd59849d7de16bb1b8" +checksum = "38c239645576e4ae97aacf132763a1988981bee1366e9d287141d43592fe80e0" dependencies = [ "astral-tokio-tar", + "astral_async_zip", "async-compression", + "async-spooled-tempfile", "bzip2 0.6.1", "chrono", "fs-err", + "futures", "futures-util", "getrandom 0.2.17", "getrandom 0.3.4", @@ -7495,9 +7528,9 @@ dependencies = [ [[package]] name = "rattler_repodata_gateway" -version = "0.25.10" +version = "0.25.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d45327ab1159e76a4eb33047ec782cf14409cb157c1c3a04f499877d14013533" +checksum = "a9c27220d71e4ae2563e6b82278fae5c43e20f0a9c92c9bac29e7fae681d89d0" dependencies = [ "anyhow", "async-compression", @@ -7556,9 +7589,9 @@ dependencies = [ [[package]] name = "rattler_shell" -version = "0.25.18" +version = "0.25.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccec6f2170a78989f2de4cc46ce9dc231151ed5d0f19ef8071118612686b6cae" +checksum = "be6ca946206e132f00d5035de6c075f3aa95f89632ee2bc8af1305b6d0d5a5db" dependencies = [ "anyhow", "enum_dispatch", @@ -7576,9 +7609,9 @@ dependencies = [ [[package]] name = "rattler_solve" -version = "4.2.2" +version = "4.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10781d00bbb3ccbdad8c2402ff6147552281450eb6ecdc0e12bcc153418d355f" +checksum = "1d3525adc4c8a1b6a08beeea4cbba3dcf79c837a5b3a066544444eaa6dfd5326" dependencies = [ "chrono", "futures", @@ -7595,9 +7628,9 @@ dependencies = [ [[package]] name = "rattler_virtual_packages" -version = "2.3.7" +version = "2.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9954f20a004bac67e28f1ca4744f24d717837b470c9899eebd4e54beffac56a3" +checksum = "bd96ff7990366c336bd9b03e8cc4e8f38c2cfc1957c5276dc256b9f4463b694b" dependencies = [ "archspec", "libloading 0.9.0", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 4cec93b..0bacf64 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -27,7 +27,7 @@ miette = "7" notify = "8" notify-debouncer-full = "0.7" percent-encoding = "2" -pixi_api = { package = "pixi_api", git = "https://github.com/prefix-dev/pixi", rev = "ce8654e0a401024dda627bc550059a89fb4add0a" } +pixi_api = { package = "pixi_api", git = "https://github.com/prefix-dev/pixi", rev = "7bf7c1be384f29a8f894e2b15835b79c39bcc533" } portable-pty = "0.9" serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index d0235dd..b940703 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -85,6 +85,7 @@ pub fn run(workspace_path: Option) { pixi::workspace::add::add_conda_deps, pixi::workspace::add::add_pypi_deps, pixi::workspace::init::init, + pixi::workspace::list::list_packages, pixi::workspace::reinstall::reinstall, pixi::workspace::remove::remove_conda_deps, pixi::workspace::remove::remove_pypi_deps, @@ -111,6 +112,7 @@ pub fn run(workspace_path: Option) { pixi::workspace::workspace::list_platforms, pixi::workspace::workspace::add_platforms, pixi::workspace::workspace::remove_platforms, + pixi::workspace::workspace::current_platform, pixi::workspace::task::list_tasks, pixi::workspace::task::add_task, pixi::workspace::task::remove_task, diff --git a/src-tauri/src/pixi/workspace/list.rs b/src-tauri/src/pixi/workspace/list.rs new file mode 100644 index 0000000..cf2ccfe --- /dev/null +++ b/src-tauri/src/pixi/workspace/list.rs @@ -0,0 +1,42 @@ +use std::path::PathBuf; + +use crate::{ + error::Error, + utils::{self, spawn_local}, +}; +use pixi_api::{ + core::environment::LockFileUsage, rattler_conda_types::Platform, workspace::Package, +}; +use tauri::{Runtime, Window}; + +#[tauri::command] +#[allow(clippy::too_many_arguments)] +pub async fn list_packages( + window: Window, + workspace: PathBuf, + regex: Option, + platform: Option, + environment: Option, + explicit: bool, + no_install: bool, + lock_file_usage: LockFileUsage, +) -> Result, Error> { + spawn_local(move || async move { + let platform: Option = + platform.map(|p| p.parse::()).transpose().unwrap(); + + let packages = utils::workspace_context(window, workspace)? + .list_packages( + regex, + platform, + environment, + explicit, + no_install, + lock_file_usage, + ) + .await?; + + Ok(packages) + }) + .await +} diff --git a/src-tauri/src/pixi/workspace/mod.rs b/src-tauri/src/pixi/workspace/mod.rs index dd8fc38..7c3d9a8 100644 --- a/src-tauri/src/pixi/workspace/mod.rs +++ b/src-tauri/src/pixi/workspace/mod.rs @@ -1,5 +1,6 @@ pub mod add; pub mod init; +pub mod list; pub mod reinstall; pub mod remove; pub mod search; diff --git a/src-tauri/src/pixi/workspace/workspace.rs b/src-tauri/src/pixi/workspace/workspace.rs index 747b11e..e1a714a 100644 --- a/src-tauri/src/pixi/workspace/workspace.rs +++ b/src-tauri/src/pixi/workspace/workspace.rs @@ -184,6 +184,11 @@ pub async fn remove_platforms( .await } +#[tauri::command] +pub fn current_platform() -> String { + Platform::current().to_string() +} + #[tauri::command] pub async fn list_features( window: Window, diff --git a/src/components/common/circularIcon.tsx b/src/components/common/circularIcon.tsx index 2347d16..c206e3f 100644 --- a/src/components/common/circularIcon.tsx +++ b/src/components/common/circularIcon.tsx @@ -11,6 +11,8 @@ import { } from "lucide-react"; import type { ReactNode } from "react"; +import { cn } from "@/lib/utils"; + export const presetIcons = { task: GalleryVerticalEndIcon, feature: PuzzleIcon, @@ -29,19 +31,29 @@ interface CircularIconProps { children?: ReactNode; icon?: PresetIcon; size?: "sm" | "md"; + variant?: "default" | "muted"; + className?: string; } export function CircularIcon({ children, icon, size = "sm", + variant = "default", + className, }: CircularIconProps) { const sizeClasses = size === "md" ? "h-12 w-12" : "h-9 w-9"; const IconComponent = icon ? presetIcons[icon] : null; return (
{IconComponent ? : children}
diff --git a/src/components/common/preferencesGroup.tsx b/src/components/common/preferencesGroup.tsx index f1483ac..6c27dd3 100644 --- a/src/components/common/preferencesGroup.tsx +++ b/src/components/common/preferencesGroup.tsx @@ -70,7 +70,7 @@ export function PreferencesGroup({ {(title || headerPrefix || headerSuffix) && (
setCommandInput(e.target.value)} onKeyDown={(e) => e.key === "Enter" && runFreeformTask()} - icon={} + icon={} /> {filteredCommands.map(([id, { command, editor }]) => ( setLocalSearch(event.target.value)} - placeholder="Search…" + placeholder="Search tasks…" autoComplete="off" spellCheck={false} autoCorrect="off" autoFocus={true} - icon={} + icon={} />
{Object.entries(tasks) diff --git a/src/components/pixi/inspect/columns.ts b/src/components/pixi/inspect/columns.ts new file mode 100644 index 0000000..c9a61ed --- /dev/null +++ b/src/components/pixi/inspect/columns.ts @@ -0,0 +1,164 @@ +import prettyBytes from "pretty-bytes"; + +import type { Package } from "@/lib/pixi/workspace/list"; + +export type ColumnKey = + | "version" + | "requested-spec" + | "build" + | "timestamp" + | "license" + | "license-family" + | "size" + | "kind" + | "platform" + | "arch" + | "subdir" + | "noarch" + | "source" + | "file-name" + | "url" + | "md5" + | "sha256" + | "constrains" + | "depends"; + +export type SortColumn = "name" | ColumnKey; +export type SortDirection = "asc" | "desc"; + +export interface ColumnDefinition { + key: ColumnKey; + label: string; +} + +export const COLUMNS: ColumnDefinition[] = [ + { key: "kind", label: "Package Kind" }, + { key: "version", label: "Version" }, + { key: "requested-spec", label: "Requested Spec" }, + { key: "build", label: "Build" }, + { key: "timestamp", label: "Timestamp" }, + { key: "license", label: "License" }, + { key: "license-family", label: "License Family" }, + { key: "source", label: "Source" }, + { key: "file-name", label: "File Name" }, + { key: "url", label: "URL" }, + { key: "subdir", label: "Subdirectory" }, + { key: "platform", label: "Platform" }, + { key: "arch", label: "Architecture" }, + { key: "noarch", label: "Noarch" }, + { key: "size", label: "Size" }, + { key: "sha256", label: "SHA256 Hash" }, + { key: "md5", label: "MD5 Hash" }, + { key: "depends", label: "Dependencies" }, + { key: "constrains", label: "Constrains" }, +]; + +export const DEFAULT_VISIBLE_COLUMNS = new Set([ + "version", + "requested-spec", + "build", + "size", +]); + +export function getColumnValue(pkg: Package, key: ColumnKey): string { + switch (key) { + case "version": + return pkg.version; + case "requested-spec": + return pkg.requested_spec ?? ""; + case "build": + return pkg.build ?? ""; + case "timestamp": + return pkg.timestamp != null + ? new Date(pkg.timestamp).toLocaleString() + : ""; + case "license": + return pkg.license ?? ""; + case "license-family": + return pkg.license_family ?? ""; + case "size": + return pkg.size_bytes != null ? prettyBytes(pkg.size_bytes) : ""; + case "kind": + if (pkg.name.startsWith("__")) return "Virtual"; + return pkg.kind === "conda" ? "Conda" : "PyPI"; + case "platform": + return pkg.platform ?? ""; + case "arch": + return pkg.arch ?? ""; + case "subdir": + return pkg.subdir ?? ""; + case "noarch": + return pkg.noarch ?? ""; + case "source": + return pkg.source ?? ""; + case "file-name": + return pkg.file_name ?? ""; + case "url": + return pkg.url ?? ""; + case "md5": + return pkg.md5 ?? ""; + case "sha256": + return pkg.sha256 ?? ""; + case "constrains": + return pkg.constrains.join(", "); + case "depends": + return pkg.depends.join(", "); + } +} + +export function comparePackages( + a: Package, + b: Package, + sortColumn: SortColumn, + sortDirection: SortDirection, +): number { + let result: number; + switch (sortColumn) { + case "name": + result = a.name.localeCompare(b.name); + break; + case "size": + result = (a.size_bytes ?? 0) - (b.size_bytes ?? 0); + break; + case "timestamp": + result = (a.timestamp ?? 0) - (b.timestamp ?? 0); + break; + default: + result = getColumnValue(a, sortColumn).localeCompare( + getColumnValue(b, sortColumn), + ); + break; + } + return ( + (sortDirection === "asc" ? result : -result) || a.name.localeCompare(b.name) + ); +} + +export function createVirtualPackage(name: string, version: string): Package { + return { + name, + version: version || "", + build: null, + build_number: null, + size_bytes: null, + kind: "conda", + source: null, + license: null, + license_family: null, + is_explicit: false, + is_editable: false, + md5: null, + sha256: null, + arch: null, + platform: null, + subdir: null, + timestamp: null, + noarch: null, + file_name: null, + url: null, + requested_spec: null, + constrains: [], + depends: [], + track_features: [], + }; +} diff --git a/src/components/pixi/inspect/inspect.tsx b/src/components/pixi/inspect/inspect.tsx new file mode 100644 index 0000000..4b03085 --- /dev/null +++ b/src/components/pixi/inspect/inspect.tsx @@ -0,0 +1,541 @@ +import { getRouteApi } from "@tanstack/react-router"; +import { + ArrowDownIcon, + ArrowUpIcon, + BoxIcon, + Columns3CogIcon, + CpuIcon, + MaximizeIcon, + MinimizeIcon, + SearchIcon, +} from "lucide-react"; +import prettyBytes from "pretty-bytes"; +import { useEffect, useState } from "react"; + +import { PreferencesGroup } from "@/components/common/preferencesGroup"; +import { + COLUMNS, + type ColumnKey, + DEFAULT_VISIBLE_COLUMNS, + type SortColumn, + type SortDirection, + comparePackages, + createVirtualPackage, + getColumnValue, +} from "@/components/pixi/inspect/columns"; +import { PackageDialog } from "@/components/pixi/inspect/packageDialog"; +import { PackageRow } from "@/components/pixi/inspect/packageRow"; +import { Button } from "@/components/shadcn/button"; +import { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuLabel, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/shadcn/dropdown-menu"; +import { Input } from "@/components/shadcn/input"; + +import { type Package, listPackages } from "@/lib/pixi/workspace/list"; + +export function Inspect() { + const { workspace, environments, platforms, currentPlatform } = + getRouteApi("/workspace/$path").useLoaderData(); + const { search = "" } = getRouteApi("/workspace/$path/").useSearch(); + const navigate = getRouteApi("/workspace/$path").useNavigate(); + + const [localSearch, setLocalSearch] = useState(search); + const [selectedEnvironment, setSelectedEnvironment] = + useState("default"); + const [selectedPlatform, setSelectedPlatform] = + useState(currentPlatform); + + const [viewMode, setViewMode] = useState<"list" | "tree" | "inverted-tree">( + "list", + ); + const [showVirtualPackages, setShowVirtualPackages] = useState(false); + const [visibleColumns, setVisibleColumns] = useState>( + new Set(DEFAULT_VISIBLE_COLUMNS), + ); + const [maximized, setMaximized] = useState(false); + + const [packages, setPackages] = useState([]); + const [selectedPackage, setSelectedPackage] = useState(null); + const [expanded, setExpanded] = useState>(new Set()); + + const [sortColumn, setSortColumn] = useState("name"); + const [sortDirection, setSortDirection] = useState("asc"); + + // Sync local state when URL search changes externally + useEffect(() => { + setLocalSearch(search); + }, [search]); + + // Debounced URL update + useEffect(() => { + const timeout = setTimeout(() => { + if (localSearch !== search) { + navigate({ + search: (prev) => ({ ...prev, search: localSearch }), + replace: true, + }); + } + }, 300); + return () => clearTimeout(timeout); + }, [localSearch, search, navigate]); + + // Fetch packages when environment/platform changes + useEffect(() => { + let cancelled = false; + + listPackages(workspace.root, { + environment: selectedEnvironment, + platform: selectedPlatform, + }).then((pkgs) => { + if (!cancelled) { + setPackages(pkgs); + } + }); + + return () => { + cancelled = true; + }; + }, [workspace.root, selectedEnvironment, selectedPlatform]); + + // Reset platform when environment changes and current platform is unavailable + const availablePlatforms = platforms[selectedEnvironment] ?? []; + useEffect(() => { + const available = platforms[selectedEnvironment] ?? []; + if (!available.includes(selectedPlatform)) { + setSelectedPlatform(currentPlatform); + } + }, [platforms, selectedEnvironment, selectedPlatform, currentPlatform]); + + // Reset expanded nodes when switching modes or refetching + useEffect(() => { + setExpanded(new Set()); + }, [viewMode, packages]); + + // Extract virtual packages from dependency specs + const realNames = new Set(packages.map((p) => p.name)); + const virtualVersions = new Map(); + for (const pkg of packages) { + for (const dep of pkg.depends) { + const name = dep.split(/[\s[]/)[0]; + if (name.startsWith("__") && !realNames.has(name)) { + const version = dep.slice(name.length).trim(); + if (!virtualVersions.has(name) || version) { + virtualVersions.set(name, version); + } + } + } + } + const virtualPackages: Package[] = Array.from(virtualVersions.entries()).map( + ([name, version]) => createVirtualPackage(name, version), + ); + const allPackages = showVirtualPackages + ? [...packages, ...virtualPackages] + : packages; + + // Client-side search filtering + const needle = localSearch.trim().toLowerCase(); + const filteredPackages = needle + ? allPackages.filter((pkg) => { + if (pkg.name.toLowerCase().includes(needle)) return true; + return COLUMNS.some((col) => + getColumnValue(pkg, col.key).toLowerCase().includes(needle), + ); + }) + : allPackages; + + // Dependency tree + const packageMap = new Map(); + for (const pkg of allPackages) { + packageMap.set(pkg.name, pkg); + } + + function getDependencyNames(pkg: Package): string[] { + return [ + ...new Set( + pkg.depends + .map((dep) => dep.split(/[\s[]/)[0]) + .filter((name) => packageMap.has(name)), + ), + ]; + } + + // Reverse dependency map: package name -> names of packages that depend on it + const reverseDeps = new Map(); + for (const pkg of allPackages) { + for (const depName of getDependencyNames(pkg)) { + if (!reverseDeps.has(depName)) reverseDeps.set(depName, []); + reverseDeps.get(depName)!.push(pkg.name); + } + } + + const treeMode = viewMode !== "list"; + const invertTree = viewMode === "inverted-tree"; + + function getTreeChildNames(pkg: Package): string[] { + return invertTree + ? (reverseDeps.get(pkg.name) ?? []) + : getDependencyNames(pkg); + } + + function toggleColumn(col: ColumnKey) { + setVisibleColumns((prev) => { + const next = new Set(prev); + if (next.has(col)) next.delete(col); + else next.add(col); + return next; + }); + } + + function toggleSort(column: SortColumn) { + if (sortColumn === column) { + if (sortDirection === "asc") { + setSortDirection("desc"); + } else { + setSortColumn("name"); + setSortDirection("asc"); + } + } else { + setSortColumn(column); + setSortDirection("asc"); + } + } + + function toggleExpand(name: string) { + setExpanded((prev) => { + const next = new Set(prev); + if (next.has(name)) next.delete(name); + else next.add(name); + return next; + }); + } + + function highlightMatch(text: string): React.ReactNode { + if (!needle || !text.toLowerCase().includes(needle)) return text; + const parts = text.split( + new RegExp(`(${needle.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})`, "gi"), + ); + return parts.map((part, i) => + part.toLowerCase() === needle ? ( + + {part} + + ) : ( + part + ), + ); + } + + function renderTreeRows( + pkg: Package, + depth: number, + visited: Set, + parentKey: string = "", + ): React.ReactNode[] { + if (visited.has(pkg.name)) return []; + const nextVisited = new Set(visited); + nextVisited.add(pkg.name); + + const childNames = getTreeChildNames(pkg); + const nodeKey = parentKey ? `${parentKey}>${pkg.name}` : pkg.name; + const isOpen = expanded.has(nodeKey); + + const rows: React.ReactNode[] = [ + 0} + isOpen={isOpen} + activeColumns={activeColumns} + highlightMatch={highlightMatch} + onSelect={setSelectedPackage} + onToggleExpand={toggleExpand} + />, + ]; + + if (isOpen) { + for (const childName of childNames.sort()) { + const depPkg = packageMap.get(childName); + if (depPkg) { + rows.push(...renderTreeRows(depPkg, depth + 1, nextVisited, nodeKey)); + } + } + } + + return rows; + } + + // Determine roots for tree mode + // Normal: explicit packages are roots, expand to see their dependencies + // Inverted: all packages are roots, expand to see what depends on them + const treeRoots = invertTree + ? filteredPackages + : (() => { + const roots = filteredPackages.filter((pkg) => pkg.is_explicit); + return roots.length > 0 ? roots : filteredPackages; + })(); + + // Visible columns in display order + const activeColumns = COLUMNS.filter((opt) => visibleColumns.has(opt.key)); + + // Sort packages + const sort = (a: Package, b: Package) => + comparePackages(a, b, sortColumn, sortDirection); + const sortedPackages = [...filteredPackages].sort(sort); + const sortedTreeRoots = [...treeRoots].sort(sort); + + return ( + <> + {/* Content */} +
+ div]:flex [&>div]:flex-1 [&>div]:flex-col [&>div]:min-h-0 [&>div]:mt-0! [&>div]:mb-0! [&>div>div:last-child]:flex [&>div>div:last-child]:flex-1 [&>div>div:last-child]:flex-col [&>div>div:last-child]:min-h-0" + : "-mt-2" + } + headerPrefix={ +
+ {/* Search */} + setLocalSearch(event.target.value)} + placeholder="Search…" + autoComplete="off" + spellCheck={false} + autoCorrect="off" + icon={} + size="sm" + className="w-48" + /> + {/* Environment */} + + + + + + Environment + + + {[...environments] + .sort((a, b) => { + if (a.name === "default") return -1; + if (b.name === "default") return 1; + return a.name.localeCompare(b.name); + }) + .map((env) => ( + + {env.name} + + ))} + + + + {/* Platform */} + + + + + + Platform + + + {[...availablePlatforms].sort().map((p) => ( + + {p} + + ))} + + + +
+ } + headerSuffix={ +
+ + {filteredPackages.length}{" "} + {filteredPackages.length === 1 ? "package" : "packages"} + {(() => { + const totalBytes = filteredPackages.reduce( + (sum, pkg) => sum + (pkg.size_bytes ?? 0), + 0, + ); + return totalBytes > 0 ? ` (${prettyBytes(totalBytes)})` : ""; + })()} + + {/* Column Selection */} + + + + + + View Settings + + + setViewMode(v as "list" | "tree" | "inverted-tree") + } + > + e.preventDefault()} + > + List + + e.preventDefault()} + > + Tree + + e.preventDefault()} + > + Inverted Tree + + + + + setShowVirtualPackages((prev) => !prev) + } + onSelect={(e) => e.preventDefault()} + > + Show Virtual Packages + + + {COLUMNS.map((opt) => ( + toggleColumn(opt.key)} + onSelect={(e) => e.preventDefault()} + > + {opt.label} + + ))} + + + {/* Maximize/Minimize */} + +
+ } + > +
+ {/* Actual List */} + + + + + {activeColumns.map((f) => ( + + ))} + + + + {treeMode + ? sortedTreeRoots.flatMap((pkg) => + renderTreeRows(pkg, 0, new Set()), + ) + : sortedPackages.map((pkg) => ( + + ))} + +
toggleSort("name")} + > + + Package + {sortColumn === "name" && + (sortDirection === "asc" ? ( + + ) : ( + + ))} + + toggleSort(f.key)} + > + + {f.label} + {sortColumn === f.key && + (sortDirection === "asc" ? ( + + ) : ( + + ))} + +
+
+
+
+ + {/* Package detail dialog */} + {selectedPackage && ( + !open && setSelectedPackage(null)} + /> + )} + + ); +} diff --git a/src/components/pixi/inspect/packageDialog.tsx b/src/components/pixi/inspect/packageDialog.tsx new file mode 100644 index 0000000..fefc081 --- /dev/null +++ b/src/components/pixi/inspect/packageDialog.tsx @@ -0,0 +1,197 @@ +import { openUrl } from "@tauri-apps/plugin-opener"; +import { ArrowLeftIcon } from "lucide-react"; +import { useState } from "react"; + +import { PreferencesGroup } from "@/components/common/preferencesGroup"; +import { COLUMNS, getColumnValue } from "@/components/pixi/inspect/columns"; +import type { ColumnDefinition } from "@/components/pixi/inspect/columns"; +import { Badge } from "@/components/shadcn/badge"; +import { Button } from "@/components/shadcn/button"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@/components/shadcn/dialog"; + +import type { Package } from "@/lib/pixi/workspace/list"; +import { isUrl } from "@/lib/utils"; + +interface PackageDialogProps { + pkg: Package; + allPackages: Package[]; + open: boolean; + onOpenChange: (open: boolean) => void; +} + +export function PackageDialog({ + pkg: initialPkg, + allPackages, + open, + onOpenChange, +}: PackageDialogProps) { + const [history, setHistory] = useState([]); + const pkg = history.length > 0 ? history[history.length - 1] : initialPkg; + + const packageMap = new Map(); + for (const p of allPackages) { + packageMap.set(p.name, p); + } + + const reverseDeps = new Map(); + for (const p of allPackages) { + for (const dep of p.depends) { + const name = dep.split(/[\s[]/)[0]; + if (!reverseDeps.has(name)) reverseDeps.set(name, []); + reverseDeps.get(name)!.push(p.name); + } + } + + function navigateTo(name: string) { + const target = packageMap.get(name); + if (target) { + setHistory((prev) => [...prev, target]); + } + } + + function goBack() { + setHistory((prev) => prev.slice(0, -1)); + } + + function handleOpenChange(isOpen: boolean) { + if (!isOpen) { + setHistory([]); + } + onOpenChange(isOpen); + } + + const sourceKeys = new Set(["source", "file-name", "url", "sha256", "md5"]); + const allEntries = COLUMNS.filter( + (c) => c.key !== "depends" && c.key !== "constrains", + ) + .map((col) => ({ col, value: getColumnValue(pkg, col.key) })) + .filter((e) => e.value); + const entries = allEntries.filter((e) => !sourceKeys.has(e.col.key)); + const sourceEntries = allEntries.filter((e) => sourceKeys.has(e.col.key)); + + const deps = [...pkg.depends].sort().map((dep) => ({ + spec: dep, + name: dep.split(/[\s[]/)[0], + })); + const revDepNames = [...new Set(reverseDeps.get(pkg.name) ?? [])].sort(); + + return ( + + + + + {history.length > 0 && ( + + )} + {pkg.name} + + + +
+ {/* Details */} + + + + + {/* Dependencies */} + {deps.length > 0 && ( + +
+ {deps.map(({ spec, name }) => ( + navigateTo(name) : undefined + } + > + {spec} + + ))} +
+
+ )} + + {/* Constraints */} + {pkg.constrains.length > 0 && ( + +
+ {[...pkg.constrains].sort().map((c) => ( + + {c} + + ))} +
+
+ )} + + {/* Required By */} + {revDepNames.length > 0 && ( + +
+ {revDepNames.map((name) => ( + navigateTo(name)} + > + {name} + + ))} +
+
+ )} + + {/* Origin */} + {sourceEntries.length > 0 && ( + + + + )} +
+
+
+ ); +} + +function EntryList({ + entries, +}: { + entries: { col: ColumnDefinition; value: string }[]; +}) { + return ( +
+ {entries.map(({ col, value }) => ( +
+
{col.label}
+
+ {isUrl(value) ? ( + + ) : ( + value + )} +
+
+ ))} +
+ ); +} diff --git a/src/components/pixi/inspect/packageRow.tsx b/src/components/pixi/inspect/packageRow.tsx new file mode 100644 index 0000000..0b623f3 --- /dev/null +++ b/src/components/pixi/inspect/packageRow.tsx @@ -0,0 +1,106 @@ +import { openUrl } from "@tauri-apps/plugin-opener"; +import { + ChevronRightIcon, + MicrochipIcon, + PackageCheckIcon, + PackageIcon, +} from "lucide-react"; + +import { + type ColumnDefinition, + getColumnValue, +} from "@/components/pixi/inspect/columns"; + +import type { Package } from "@/lib/pixi/workspace/list"; +import { isUrl } from "@/lib/utils"; + +export interface PackageRowProps { + pkg: Package; + depth: number; + nodeKey: string; + treeMode: boolean; + hasChildren: boolean; + isOpen: boolean; + activeColumns: ColumnDefinition[]; + highlightMatch: (text: string) => React.ReactNode; + onSelect: (pkg: Package) => void; + onToggleExpand: (key: string) => void; +} + +export function PackageRow({ + pkg, + depth, + nodeKey, + treeMode, + hasChildren, + isOpen, + activeColumns, + highlightMatch, + onSelect, + onToggleExpand, +}: PackageRowProps) { + return ( + onSelect(pkg)} + > + +
0 ? { paddingLeft: depth * 20 } : undefined} + > + {treeMode && + (hasChildren ? ( + + ) : ( +
+ ))} + {pkg.name.startsWith("__") ? ( + + ) : pkg.is_explicit ? ( + + ) : ( + + )} + {highlightMatch(pkg.name)} +
+ + {activeColumns.map((f) => { + const value = getColumnValue(pkg, f.key); + const isLink = isUrl(value); + return ( + + {isLink ? ( + + ) : ( + highlightMatch(value) + )} + + ); + })} + + ); +} diff --git a/src/components/shadcn/dropdown-menu.tsx b/src/components/shadcn/dropdown-menu.tsx index 00cd139..217204d 100644 --- a/src/components/shadcn/dropdown-menu.tsx +++ b/src/components/shadcn/dropdown-menu.tsx @@ -95,7 +95,7 @@ function DropdownMenuCheckboxItem({ & { +}: Omit, "size"> & { label?: string; icon?: ReactNode; suffix?: ReactNode; + size?: "default" | "sm"; }) { if (label) { return ( @@ -65,7 +67,9 @@ function Input({ } return (
- {icon &&
{icon}
} + {icon && ( +
{icon}
+ )} - {suffix &&
{suffix}
} + {suffix && ( +
+ {suffix} +
+ )}
); } diff --git a/src/components/shadcn/select.tsx b/src/components/shadcn/select.tsx index 073037a..2354a9f 100644 --- a/src/components/shadcn/select.tsx +++ b/src/components/shadcn/select.tsx @@ -125,7 +125,7 @@ function SelectItem({ { + return invoke("list_packages", { + workspace, + regex: options.regex ?? null, + platform: options.platform ?? null, + environment: options.environment ?? null, + explicit: options.explicit ?? false, + noInstall: options.noInstall ?? false, + lockFileUsage: options.lockFileUsage ?? "Update", + }); +} diff --git a/src/lib/pixi/workspace/workspace.ts b/src/lib/pixi/workspace/workspace.ts index 750a62e..06e26ac 100644 --- a/src/lib/pixi/workspace/workspace.ts +++ b/src/lib/pixi/workspace/workspace.ts @@ -84,6 +84,10 @@ export function listPlatforms( return invoke>("list_platforms", { workspace }); } +export function currentPlatform(): Promise { + return invoke("current_platform"); +} + export async function addPlatforms( workspace: string, platforms: string[], diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 9f9d957..24b996a 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -11,6 +11,10 @@ export function toPixiName(value: string): string { return value.toLowerCase().replace(/\s+/g, "-"); } +export function isUrl(value: string): boolean { + return value.startsWith("http://") || value.startsWith("https://"); +} + export const COMMON_PLATFORMS = [ { id: "win-64", name: "Windows (x64)" }, { id: "win-arm64", name: "Windows (ARM64)" }, diff --git a/src/routes/workspace.$path/index.tsx b/src/routes/workspace.$path/index.tsx index 27f90d5..6ea730f 100644 --- a/src/routes/workspace.$path/index.tsx +++ b/src/routes/workspace.$path/index.tsx @@ -1,10 +1,11 @@ import { createFileRoute, getRouteApi } from "@tanstack/react-router"; -import { Bug, CirclePlay, ScrollTextIcon } from "lucide-react"; +import { Bug, CirclePlay, PackageIcon, ScrollTextIcon } from "lucide-react"; import { AppMenu } from "@/components/common/appMenu"; import { Header } from "@/components/common/header"; import { Debug } from "@/components/pixi/debug"; import { Environments } from "@/components/pixi/environments/environments"; +import { Inspect } from "@/components/pixi/inspect/inspect"; import { Manifest } from "@/components/pixi/manifest/manifest"; import { Tabs, @@ -48,6 +49,10 @@ function WorkspaceComponent() { Run + + + Inspect + Manifest @@ -62,6 +67,9 @@ function WorkspaceComponent() { + + + diff --git a/src/routes/workspace.$path/route.tsx b/src/routes/workspace.$path/route.tsx index 7b3c896..9d1c08b 100644 --- a/src/routes/workspace.$path/route.tsx +++ b/src/routes/workspace.$path/route.tsx @@ -22,6 +22,7 @@ import { type Environment, type Feature, type Workspace, + currentPlatform, getWorkspace, listChannels, listEnvironments, @@ -39,22 +40,32 @@ export interface WorkspaceLoaderData { environments: Environment[]; channels: Record; platforms: Record; + currentPlatform: string; } export const Route = createFileRoute("/workspace/$path")({ loader: async ({ params: { path } }): Promise => { console.info("Load manifest:", path); const workspace = await getWorkspace(path); - const [tasks, features, environments, channels, platforms] = + const [tasks, features, environments, channels, platforms, hostPlatform] = await Promise.all([ listTask(workspace.root), listFeatures(workspace.root), listEnvironments(workspace.root), listChannels(workspace.root), listPlatforms(workspace.root), + currentPlatform(), ]); await addRecentWorkspace(workspace); - return { workspace, tasks, features, environments, channels, platforms }; + return { + workspace, + tasks, + features, + environments, + channels, + platforms, + currentPlatform: hostPlatform, + }; }, staleTime: 1_000, onError: async (error) => {