diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..36a3128 --- /dev/null +++ b/.clang-format @@ -0,0 +1,34 @@ +BasedOnStyle: WebKit + +AlignAfterOpenBracket: Align +AlignEscapedNewlines: DontAlign +AllowAllParametersOfDeclarationOnNextLine: "false" +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: "false" +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: "false" +AlwaysBreakAfterReturnType: TopLevelDefinitions +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: "false" +BinPackParameters: "false" +BreakBeforeBraces: Allman +BreakBeforeTernaryOperators: "true" +BreakConstructorInitializers: BeforeComma +ColumnLimit: 120 +Cpp11BracedListStyle: "false" +FixNamespaceComments: "true" +IncludeBlocks: Preserve +IndentWidth: "4" +InsertBraces: "true" +MaxEmptyLinesToKeep: "2" +NamespaceIndentation: None +PointerAlignment: Left +ReflowComments: "false" +SortIncludes: "true" +SpaceAfterCStyleCast: "false" +SpaceInEmptyBlock: "false" +SpacesBeforeTrailingComments: "2" +SpacesInAngles: "true" +SpacesInParentheses: "true" +SpacesInSquareBrackets: "true" +Standard: c++17 \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..9ccbd91 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,152 @@ +name: Build and Release ALG App Store + +on: + push: + branches: [ "main", "devel", "qt6" ] + paths: + - '**.cpp' + - '**.h' + - 'CMakeLists.txt' + - '.github/workflows/**' + pull_request: + branches: [ "main", "devel", "qt6" ] + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + container: + image: archlinux:latest + outputs: + version: ${{ steps.get_version.outputs.version }} + should_release: ${{ steps.check_release.outputs.should_release }} + artifact_name: ${{ steps.artifact.outputs.name }} + + steps: + - name: Install base dependencies + run: | + pacman -Syu --noconfirm + pacman -S --noconfirm --needed \ + base-devel \ + git \ + cmake \ + qt6-base \ + qt6-tools \ + pacman \ + libarchive \ + curl \ + pkgconf + + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 2 # Need history to check changed files + + - name: Mark git directory as safe + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + + - name: Extract version from CMakeLists.txt + id: get_version + run: | + VERSION=$(grep -oP 'project\(alg-app-store VERSION \K[0-9]+\.[0-9]+\.[0-9]+' CMakeLists.txt) + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Detected version: $VERSION" + + - name: Check if this should trigger a release + id: check_release + run: | + # Only release on main branch when CMakeLists.txt was modified + if [[ "${{ github.ref }}" == "refs/heads/main" && "${{ github.event_name }}" == "push" ]]; then + # Check if CMakeLists.txt was changed in this push + if git diff --name-only HEAD~1 HEAD | grep -q "CMakeLists.txt"; then + echo "should_release=true" >> $GITHUB_OUTPUT + echo "Release triggered: CMakeLists.txt changed on main branch" + else + echo "should_release=false" >> $GITHUB_OUTPUT + echo "No release: CMakeLists.txt not changed" + fi + else + echo "should_release=false" >> $GITHUB_OUTPUT + echo "No release: not on main branch or not a push event" + fi + + - name: Configure CMake + run: | + mkdir -p build + cd build + cmake .. + + - name: Build + run: | + cd build + make -j$(nproc) + + - name: Check build artifacts + run: | + ls -lh build/alg-app-store + file build/alg-app-store + + - name: Set artifact name + id: artifact + run: | + # Replace forward slashes with dashes to handle PR refs like "12/merge" + ARTIFACT_NAME="alg-app-store-${{ github.ref_name }}-${{ steps.get_version.outputs.version }}" + ARTIFACT_NAME="${ARTIFACT_NAME//\//-}" + echo "name=$ARTIFACT_NAME" >> $GITHUB_OUTPUT + echo "Artifact name: $ARTIFACT_NAME" + + - name: Upload build artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.artifact.outputs.name }} + path: build/alg-app-store + retention-days: 30 + + release: + needs: build + runs-on: ubuntu-latest + if: needs.build.outputs.should_release == 'true' + permissions: + contents: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Download build artifact + uses: actions/download-artifact@v4 + with: + name: ${{ needs.build.outputs.artifact_name }} + path: ./release + + - name: Prepare release assets + run: | + chmod +x ./release/alg-app-store + tar -czvf alg-app-store-${{ needs.build.outputs.version }}-linux-x86_64.tar.gz -C ./release alg-app-store + + - name: Create GitHub Release + uses: softprops/action-gh-release@v1 + with: + tag_name: v${{ needs.build.outputs.version }} + name: ALG App Store v${{ needs.build.outputs.version }} + body: | + ## ALG App Store v${{ needs.build.outputs.version }} + + A modern package manager GUI for Arch Linux. + + ### Installation + ```bash + tar -xzvf alg-app-store-${{ needs.build.outputs.version }}-linux-x86_64.tar.gz + sudo mv alg-app-store /usr/local/bin/ + ``` + + ### Requirements + - Qt6 (Core, Gui, Widgets, Network, Concurrent) + - libalpm (pacman library) + - yay or paru (for AUR support) + files: | + alg-app-store-${{ needs.build.outputs.version }}-linux-x86_64.tar.gz + draft: false + prerelease: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b202a2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,52 @@ +# Qt/C++ Build artifacts +build/ +*.o +*.so +*.a +*.user +*.autosave +moc_*.cpp +qrc_*.cpp +ui_*.h + +# CMake +CMakeCache.txt +CMakeFiles/ +cmake_install.cmake +Makefile + +# Qt Creator +*.pro.user +*.pro.user.* + +# Executable +alg-app-store + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# Compiled Object files +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# OS specific +.DS_Store +Thumbs.db + +# Backup files +*~ +*.bak +*.backup diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e990cd5 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,117 @@ +cmake_minimum_required(VERSION 3.16) + +project(alg-app-store VERSION 0.2.28 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) + +# Find Qt6 packages +find_package(Qt6 REQUIRED COMPONENTS + Core + Gui + Widgets + Network + Concurrent +) + +# Find libalpm +find_package(PkgConfig REQUIRED) +pkg_check_modules(ALPM REQUIRED libalpm) + +# Include directories +include_directories( + ${CMAKE_SOURCE_DIR}/src + ${ALPM_INCLUDE_DIRS} +) + +# Source files +set(SOURCES + src/main.cpp + + # Core + src/core/alpm_wrapper.cpp + src/core/aur_helper.cpp + src/core/package_manager.cpp + + # GUI + src/gui/mainwindow.cpp + src/gui/home_widget.cpp + src/gui/search_widget.cpp + src/gui/installed_widget.cpp + src/gui/updates_widget.cpp + src/gui/settings_widget.cpp + src/gui/package_card.cpp + src/gui/package_details_dialog.cpp +) + +# Header files +set(HEADERS + src/utils/logger.h + src/utils/types.h + + # Core + src/core/alpm_wrapper.h + src/core/aur_helper.h + src/core/package_manager.h + + # GUI + src/gui/mainwindow.h + src/gui/home_widget.h + src/gui/search_widget.h + src/gui/installed_widget.h + src/gui/updates_widget.h + src/gui/settings_widget.h + src/gui/package_card.h + src/gui/package_details_dialog.h +) + +# Create executable +add_executable(${PROJECT_NAME} + ${SOURCES} + ${HEADERS} +) + +# Link libraries +target_link_libraries(${PROJECT_NAME} PRIVATE + Qt6::Core + Qt6::Gui + Qt6::Widgets + Qt6::Network + Qt6::Concurrent + ${ALPM_LIBRARIES} +) + +# Link directories +link_directories(${ALPM_LIBRARY_DIRS}) + +# Compiler flags +target_compile_options(${PROJECT_NAME} PRIVATE + -Wall + -Wextra + -Wpedantic +) + +# Install target +install(TARGETS ${PROJECT_NAME} + RUNTIME DESTINATION bin +) + +# Install desktop file +install(FILES assets/alg-app-store.desktop + DESTINATION share/applications +) + +# Copy stylesheet to build directory +configure_file( + ${CMAKE_SOURCE_DIR}/stylesheet.qss + ${CMAKE_BINARY_DIR}/stylesheet.qss + COPYONLY +) + +# Install stylesheet +install(FILES stylesheet.qss + DESTINATION share/alg-app-store +) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..48ad1ab --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,155 @@ +# Contributing to ALG Welcome + +Contributions are welcome and appreciated! To contribute: + +- Code follows C++17 standards +- Proper error handling and logging +- Thread safety for concurrent operations +- Qt best practices for GUI code +- Comments for complex logic + +### How to contribute +1. **Fork the Repository.** +2. **Create a New Branch** for your feature or bug fix: + ```bash + git checkout -b feature/your-feature-name + ``` +3. **Make Your Changes:** + - Follow modern C++17 best practices + - Use Qt6 APIs and conventions + - Ensure code compiles without warnings + - Test on your desktop environment (KDE, GNOME, or Xfce) +4. **Commit Your Changes** and push your branch: + ```bash + git commit -m "Add new feature or fix bug" + git push -u origin feature/your-feature-name + ``` +5. **Open a Pull Request** describing your changes. + + +## Project Structure + +``` +├── assets +│ ├── alg-app-store.desktop +│ └── alg-app-store.png +├── build.sh +├── CMakeLists.txt +├── CONTRIBUTING.md +├── DEVELOPER_GUIDE.md +├── LICENSE +├── QT_REWRITE_SUMMARY.md +├── README.md +├── src +│ ├── core +│ │ ├── alpm_wrapper.cpp +│ │ ├── alpm_wrapper.h +│ │ ├── aur_helper.cpp +│ │ ├── aur_helper.h +│ │ ├── package_manager.cpp +│ │ └── package_manager.h +│ ├── gui +│ │ ├── home_widget.cpp +│ │ ├── home_widget.h +│ │ ├── installed_widget.cpp +│ │ ├── installed_widget.h +│ │ ├── mainwindow.cpp +│ │ ├── mainwindow.h +│ │ ├── package_card.cpp +│ │ ├── package_card.h +│ │ ├── package_details_dialog.cpp +│ │ ├── package_details_dialog.h +│ │ ├── search_widget.cpp +│ │ ├── search_widget.h +│ │ ├── settings_widget.cpp +│ │ ├── settings_widget.h +│ │ ├── updates_widget.cpp +│ │ └── updates_widget.h +│ ├── main.cpp +│ └── utils +│ ├── logger.h +│ └── types.h +├── stylesheet.qss +└── TODO.md + +6 directories, 36 files +``` + +## Understanding the code +### Core +#### AlpmWrapper (Singleton) +Wraps libalpm functionality with thread-safe operations: +- Package searching +- Installed package enumeration +- Update detection +- Package information retrieval + +#### AurHelper +Handles AUR integration: +- Package search via AUR RPC API +- Package information retrieval +- Update checking for AUR packages + +#### PackageManager (Singleton) +Manages package operations with proper privilege escalation: +- Install/uninstall packages +- Update operations +- Automatic helper detection (yay/paru/pacman) +- Process management with signals + +### GUI Components +- **MainWindow**: Tabbed interface container +- **HomeWidget**: Featured packages display +- **SearchWidget**: Package search with filtering +- **InstalledWidget**: Installed package browser +- **UpdatesWidget**: Update management +- **PackageCard**: Reusable package display widget +- **PackageDetailsDialog**: Detailed package information + +## Design Decisions + +### Modern C++ Features + +- **Smart Pointers**: `std::unique_ptr` and `std::shared_ptr` for automatic memory management +- **Move Semantics**: `std::move()` for efficient resource transfer +- **Auto Type Deduction**: Cleaner, more maintainable code +- **Range-based Loops**: Cleaner iteration +- **Lambda Functions**: Inline callbacks and signal connections + +### Thread Safety + +- **Mutexes**: `std::mutex` and `std::lock_guard` for critical sections +- **Qt Concurrent**: `QtConcurrent::run()` for background operations +- **Signal/Slot**: Qt's thread-safe communication mechanism + +### Singleton Pattern + +Used for `AlpmWrapper` and `PackageManager` to ensure: +- Single libalpm handle instance +- Centralized package operation management +- Thread-safe access + +## Logging + +The application includes a comprehensive logging system: + +```cpp +Logger::info("Information message"); +Logger::warning("Warning message"); +Logger::error("Error message"); +Logger::debug("Debug message"); +``` + +Logs are output to standard output and can be redirected for persistent logging. + +## Package Helper Detection + +The application automatically detects available package helpers in this order: + +1. **yay** - Preferred for AUR support +2. **paru** - Alternative AUR helper +3. **pacman** - Fallback (official repos only) + +## Privilege Escalation + +Package operations require root privileges. The application uses `pkexec` (PolicyKit) for secure privilege escalation. Ensure PolicyKit is properly configured on your system. \ No newline at end of file diff --git a/GetPackage/get.go b/GetPackage/get.go deleted file mode 100644 index 96ba5f5..0000000 --- a/GetPackage/get.go +++ /dev/null @@ -1,184 +0,0 @@ -package getpackage - -import ( - "encoding/json" - "fmt" - "os" - "os/exec" - "strings" - "sync" - "time" - - "github.com/Jguer/go-alpm/v2" -) - -type PackageInfo struct { - Name string `json:"name"` - Version string `json:"version"` - Description string `json:"description"` - Repository string `json:"repository"` - Maintainer string `json:"maintainer"` - UpstreamURL string `json:"upstreamurl"` - DependList []string `json:"dependlist"` - LastUpdated string `json:"lastupdated"` -} - -func SavePackageInfoToFile(pkgName, fileName string) error { - pkgs := SearchPackage(pkgName) - if len(pkgs) == 0 { - return fmt.Errorf("package %s not found", pkgName) - } - - // Assume the first result is the most relevant one - pkgInfo := pkgs - - file, err := os.Create(fileName) - if err != nil { - return fmt.Errorf("failed to create file: %w", err) - } - defer file.Close() - - encoder := json.NewEncoder(file) - encoder.SetIndent("", " ") - err = encoder.Encode(pkgInfo) - if err != nil { - return fmt.Errorf("failed to encode package info to JSON: %w", err) - } - - fmt.Printf("Package information for %s saved to %s\n", pkgName, fileName) - return nil -} - -func SearchPackage(query string) []PackageInfo { - var h *alpm.Handle - - // var dbs []alpm.IDB - var dbs []alpm.IDB - var err error - h, err = alpm.Initialize("/", "/var/lib/pacman") - if err != nil { - fmt.Fprintf(os.Stderr, "failed to initialize alpm: %v\n", err) - os.Exit(1) - } - - // Register and sync repositories - repos := []string{"core", "extra"} - for _, repo := range repos { - db, err := h.RegisterSyncDB(repo, 0) - if err != nil { - fmt.Printf("Error getting sync db for %s: %v\n", repo, err) - } - dbs = append(dbs, db) - } - var results []PackageInfo - var wg sync.WaitGroup - resultChan := make(chan PackageInfo, 100) - doneChan := make(chan bool) - - // Start a goroutine to collect results - go func() { - for pkg := range resultChan { - results = append(results, pkg) - } - doneChan <- true - }() - - // Search official repositories concurrently - for _, db := range dbs { - wg.Add(1) - go func(db alpm.IDB) { - defer wg.Done() - searchDB(db, query, resultChan) - }(db) - } - - // Search AUR concurrently - wg.Add(1) - go func() { - defer wg.Done() - searchAUR(query, resultChan) - }() - - // Wait for all searches to complete - wg.Wait() - close(resultChan) - - // Wait for result collection to finish - <-doneChan - - return results -} - -func searchDB(db alpm.IDB, query string, resultChan chan<- PackageInfo) { - db.PkgCache().ForEach(func(pkg alpm.IPackage) error { - if strings.Contains(strings.ToLower(pkg.Name()), strings.ToLower(query)) { - lastUpdated := pkg.BuildDate().UTC().Format("Jan. 2, 2006, 3 p.m. MST") - resultChan <- PackageInfo{ - Name: pkg.Name(), - Version: pkg.Version(), - Description: pkg.Description(), - Repository: db.Name(), - Maintainer: pkg.Packager(), - UpstreamURL: pkg.URL(), - DependList: convertDependList(pkg.Depends()), - LastUpdated: lastUpdated, - } - } - return nil - }) -} - -func searchAUR(query string, resultChan chan<- PackageInfo) { - cmd := exec.Command("curl", "-s", fmt.Sprintf("https://aur.archlinux.org/rpc/?v=5&type=search&arg=%s", query)) - output, err := cmd.Output() - if err != nil { - fmt.Printf("Error searching AUR: %v\n", err) - return - } - - var aurResponse struct { - Results []struct { - Name string `json:"Name"` - Version string `json:"Version"` - Description string `json:"Description"` - Maintainer string `json:"Maintainer"` - URL string `json:"URL"` - LastModified int64 `json:"LastModified"` - } `json:"results"` - } - err = json.Unmarshal(output, &aurResponse) - if err != nil { - fmt.Printf("Error parsing AUR response: %v\n", err) - return - } - - for _, aurPkg := range aurResponse.Results { - lastUpdated := time.Unix(aurPkg.LastModified, 0).UTC().Format("02-01-2006") - resultChan <- PackageInfo{ - Name: aurPkg.Name, - Version: aurPkg.Version, - Description: aurPkg.Description, - Repository: "AUR", - Maintainer: aurPkg.Maintainer, - UpstreamURL: aurPkg.URL, - DependList: nil, - LastUpdated: lastUpdated, - } - } -} - -func convertDependList(depList alpm.IDependList) []string { - var deps []string - depList.ForEach(func(dep *alpm.Depend) error { - deps = append(deps, dep.Name) - return nil - }) - return deps -} - -func main() { - err := SavePackageInfoToFile("google-chrome", "package-info.json") - if err != nil { - fmt.Println("Error:", err) - } -} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..33100ba --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 ALG Team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index ed259fc..3fe1751 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,83 @@ -# README +# ALG App Store - Qt6/C++ Version -## About +A modern, native package manager for Arch Linux built with Qt6 and C++17. This is a complete rewrite of the original Wails-based application. -About your template +## Features -## Live Development +- **Search Packages**: Search through official Arch repositories (core, extra) and AUR +- **View Installed Packages**: Browse and manage installed packages +- **Check for Updates**: View available updates for both official and AUR packages +- **Package Management**: Install, uninstall, and update packages +- **Modern UI**: Clean, dark-themed interface with responsive design +- **Smart Helper Detection**: Automatically detects and uses yay, paru, or falls back to pacman +- **Thread-Safe**: Uses modern C++ threading features for safe concurrent operations +- **Comprehensive Logging**: Built-in logger for debugging and monitoring -To run in live development mode, run `wails dev` in the project directory. In another terminal, go into the `frontend` -directory and run `npm run dev`. The frontend dev server will run on http://localhost:34115. Connect to this in your -browser and connect to your application. +## Technology Stack + +- **Language**: C++17 +- **GUI Framework**: Qt6 (Widgets) +- **Package Management**: libalpm (Arch Linux Package Manager library) +- **AUR Integration**: AUR RPC API via Qt Network + Chaotic AUR Support +- **Build System**: CMake +- **Threading**: Qt Concurrent & STL threading + +## Prerequisites + +### Build Dependencies + +```bash +sudo pacman -S base-devel cmake qt6-base qt6-svg alpm pkgconf +``` + +You can optionally also have either either yay or paru if you would like to work with packages from the AUR. ## Building -To build a redistributable, production mode package, use `wails build`. +1. Clone the repository: +```bash +git clone https://github.com/arch-linux-gui/alg-app-store.git +cd alg-app-store +``` + +2. Run Build Script +```bash +# This will create a build directory. +./build.sh +``` + +Binary will be in the build directory. + +## Running + +### From Build Directory + +```bash +./build/alg-app-store +``` + +### From System Installation (if installed) + +```bash +alg-app-store +``` + + +## License + +This project is part of the Arch Linux GUI project. +It is distributed under the MIT License. Check LICENSE. + +## Credits + +- **Author**: DemonKiller +- **Original Project**: Wails-based ALG App Store +- **Rewrite**: Qt6/C++ implementation +- **Community**: Arch Linux and Qt communities + +## Contact + +For issues, questions, or contributions, please visit: +- GitHub: https://github.com/arch-linux-gui/alg-app-store +- Website: https://arkalinuxgui.org +- Discord: https://discord.com/invite/NgAFEw9Tkf \ No newline at end of file diff --git a/TODO.md b/TODO.md index 2555835..6998488 100644 --- a/TODO.md +++ b/TODO.md @@ -1,7 +1,25 @@ -- [x] Search packages from all DB: Core, Extra & AUR -- [x] Search Local DB and display packages in Install screen -- [x] Add Install Func -- [x] Add Uninstall Func -- [x] Do State Management while installing with loader -- [x] Do State Managerment while uninstalling with loader -- [ ] Add Update All for updation of all packages +- [x] Add chaotic aur to search tab if enabled +- [x] Add launch button on package card if package is installed and remove when uninstalled +- [] Add detailed mirrorlist tab to set mirrorlist +- [x] Add version information +- [] Implement an AUR helper in core to remove dependence on paru and yay +- [] Ask password only once on startup - startup_auth +- [] Improve settings page - settings_tab +- [] Move all styles to single stylesheet - style_and_theme +- [] Clean up UI; make UI look more modern (check gnome's styling options) - style_and_theme +- [] Set a light/dark theme toggle, or follow system's theme - style_and_theme +- [] Look into spdlog for logging +- [] Look into CppUTest or Google Test (gtest) for test + +## Future Enhancements + +- Package categories/tags +- Package ratings and reviews +- Automated testing suite +- Flatpak integration +- AppImage support +- Configuration file support +- Multi-language support +- Package download progress +- Transaction history +- Dependency visualization \ No newline at end of file diff --git a/app.go b/app.go deleted file mode 100644 index 64edd00..0000000 --- a/app.go +++ /dev/null @@ -1,647 +0,0 @@ -package main - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "log" - "os" - "sort" - "strings" - "sync" - "time" - - "os/exec" - - "github.com/Jguer/go-alpm/v2" - paconf "github.com/Morganamilo/go-pacmanconf" -) - -var h *alpm.Handle - -// var dbs []alpm.IDB -var dbs []alpm.IDB - -var DesktopEnv string - -// App struct -type App struct { - ctx context.Context -} - -// NewApp creates a new App application struct -func NewApp() *App { - return &App{} -} - -// startup is called at application startup -func (a *App) startup(ctx context.Context) { - // Perform your setup here - a.ctx = ctx - - DesktopEnv = getDesktopEnvironment() - - var err error - h, err = alpm.Initialize("/", "/var/lib/pacman") - if err != nil { - fmt.Fprintf(os.Stderr, "failed to initialize alpm: %v\n", err) - os.Exit(1) - } - - // Register and sync repositories - repos := []string{"core", "extra"} - for _, repo := range repos { - db, err := h.RegisterSyncDB(repo, 0) - if err != nil { - fmt.Printf("Error getting sync db for %s: %v\n", repo, err) - return - } - dbs = append(dbs, db) - } -} - -// domReady is called after front-end resources have been loaded -func (a App) domReady(ctx context.Context) { - // Add your action here -} - -// beforeClose is called when the application is about to quit, -// either by clicking the window close button or calling runtime.Quit. -// Returning true will cause the application to continue, false will continue shutdown as normal. -func (a *App) beforeClose(ctx context.Context) (prevent bool) { - return false -} - -// shutdown is called at application termination -func (a *App) shutdown(ctx context.Context) { - // Perform your teardown here - if h != nil { - h.Release() - } -} - -type PackageInfo struct { - Name string `json:"name"` - Version string `json:"version"` - Description string `json:"description"` - Repository string `json:"repository"` - Maintainer string `json:"maintainer"` - UpstreamURL string `json:"upstreamurl"` - DependList []string `json:"dependlist"` - LastUpdated string `json:"lastupdated"` -} - -func (a *App) SearchPackage(query string) []PackageInfo { - var results []PackageInfo - var wg sync.WaitGroup - resultChan := make(chan PackageInfo, 100) - doneChan := make(chan bool) - - // Start a goroutine to collect results - go func() { - for pkg := range resultChan { - results = append(results, pkg) - } - doneChan <- true - }() - - // Search official repositories concurrently - for _, db := range dbs { - wg.Add(1) - go func(db alpm.IDB) { - defer wg.Done() - searchDB(db, query, resultChan) - }(db) - } - - // Search AUR concurrently - wg.Add(1) - go func() { - defer wg.Done() - searchAUR(query, resultChan) - }() - - // Wait for all searches to complete - wg.Wait() - close(resultChan) - - // Wait for result collection to finish - <-doneChan - - return results -} - -func searchDB(db alpm.IDB, query string, resultChan chan<- PackageInfo) { - db.PkgCache().ForEach(func(pkg alpm.IPackage) error { - if strings.Contains(strings.ToLower(pkg.Name()), strings.ToLower(query)) { - lastUpdated := pkg.BuildDate().UTC().Format("Jan. 2, 2006, 3 p.m. MST") - resultChan <- PackageInfo{ - Name: pkg.Name(), - Version: pkg.Version(), - Description: pkg.Description(), - Repository: db.Name(), - Maintainer: pkg.Packager(), - UpstreamURL: pkg.URL(), - DependList: convertDependList(pkg.Depends()), - LastUpdated: lastUpdated, - } - } - return nil - }) -} - -func searchAUR(query string, resultChan chan<- PackageInfo) { - cmd := exec.Command("curl", "-s", fmt.Sprintf("https://aur.archlinux.org/rpc/?v=5&type=search&arg=%s", query)) - output, err := cmd.Output() - if err != nil { - fmt.Printf("Error searching AUR: %v\n", err) - return - } - - var aurResponse struct { - Results []struct { - Name string `json:"Name"` - Version string `json:"Version"` - Description string `json:"Description"` - Maintainer string `json:"Maintainer"` - URL string `json:"URL"` - LastModified int64 `json:"LastModified"` - } `json:"results"` - } - err = json.Unmarshal(output, &aurResponse) - if err != nil { - fmt.Printf("Error parsing AUR response: %v\n", err) - return - } - - for _, aurPkg := range aurResponse.Results { - lastUpdated := time.Unix(aurPkg.LastModified, 0).UTC().Format("02-01-2006") - resultChan <- PackageInfo{ - Name: aurPkg.Name, - Version: aurPkg.Version, - Description: aurPkg.Description, - Repository: "AUR", - Maintainer: aurPkg.Maintainer, - UpstreamURL: aurPkg.URL, - DependList: nil, - LastUpdated: lastUpdated, - } - } -} - -func convertDependList(depList alpm.IDependList) []string { - var deps []string - depList.ForEach(func(dep *alpm.Depend) error { - deps = append(deps, dep.Name) - return nil - }) - return deps -} - -func (a *App) GetInstalledPackages() ([]PackageInfo, error) { - h, err := alpm.Initialize("/", "/var/lib/pacman") - if err != nil { - fmt.Fprintf(os.Stderr, "failed to initialize alpm: %v\n", err) - os.Exit(1) - } - - if h == nil { - return nil, fmt.Errorf("ALPM handle is not initialized") - } - - db, err := h.LocalDB() - if err != nil { - return nil, fmt.Errorf("failed to get local DB: %v", err) - } - - var packages []PackageInfo - var mutex sync.Mutex - - err = db.PkgCache().ForEach(func(pkg alpm.IPackage) error { - mutex.Lock() - lastUpdated := pkg.BuildDate().UTC().Format("Jan. 2, 2006, 3 p.m. MST") - packages = append(packages, PackageInfo{ - Name: pkg.Name(), - Version: pkg.Version(), - Description: pkg.Description(), - Repository: pkg.DB().Name(), - Maintainer: pkg.Packager(), - UpstreamURL: pkg.URL(), - DependList: convertDependList(pkg.Depends()), - LastUpdated: lastUpdated, - }) - mutex.Unlock() - return nil - }) - - if err != nil { - return nil, fmt.Errorf("error iterating over packages: %v", err) - } - - if len(packages) == 0 { - return nil, fmt.Errorf("no installed packages found") - } - - return packages, nil -} - -func (a *App) SearchLocalPackage(pkg string) (bool, error) { - if pkg == "" { - return false, fmt.Errorf("empty package name provided") - } - - local, err := searchLocalDB(pkg) - if err != nil { - return false, fmt.Errorf("error searching local DB: %w", err) - } - - if local == nil { - return false, nil - } - - fmt.Println(strings.Contains(strings.ToLower(local.Name()), strings.ToLower(pkg))) - - return strings.Contains(strings.ToLower(local.Name()), strings.ToLower(pkg)), nil -} - -func searchLocalDB(pkg string) (alpm.IPackage, error) { - h, err := alpm.Initialize("/", "/var/lib/pacman") - if err != nil { - fmt.Fprintf(os.Stderr, "failed to initialize alpm: %v\n", err) - os.Exit(1) - } - if h == nil { - return nil, fmt.Errorf("ALPM handle is not initialized") - } - - db, err := h.LocalDB() - if err != nil { - return nil, fmt.Errorf("failed to get local DB: %w", err) - } - - res := db.Pkg(pkg) - return res, nil -} - -func (a *App) CheckPackageInstalled(packageName string) bool { - h, err := alpm.Initialize("/", "/var/lib/pacman") - if err != nil { - fmt.Fprintf(os.Stderr, "failed to initialize alpm: %v\n", err) - os.Exit(1) - } - if h == nil { - log.Fatal("ALPM handle is not initialized") - } - localDB, err := h.LocalDB() - if err != nil { - log.Fatal(err) - } - packageHandle := localDB.Pkg(packageName) - return packageHandle != nil -} - -func (a *App) Install(pkg string) { - cmdStr := fmt.Sprintf("pkexec yay -S %s --noconfirm", pkg) - cmd := exec.Command("sh", "-c", cmdStr) - fmt.Println("Executing command:", cmdStr) - - var outBuffer, errBuffer bytes.Buffer - cmd.Stdout = &outBuffer - cmd.Stderr = &errBuffer - - err := cmd.Run() - if err != nil { - fmt.Println("Error executing command:", err) - fmt.Println("stderr:", errBuffer.String()) - return - } - - fmt.Println("stdout:", outBuffer.String()) - fmt.Println("stderr:", errBuffer.String()) -} - -func (a *App) Uninstall(pkg string) { - cmdStr := fmt.Sprintf("pkexec yay -Rdd %s --noconfirm", pkg) - cmd := exec.Command("sh", "-c", cmdStr) - fmt.Println("Executing command:", cmdStr) - - var outBuffer, errBuffer bytes.Buffer - cmd.Stdout = &outBuffer - cmd.Stderr = &errBuffer - - err := cmd.Run() - if err != nil { - fmt.Println("Error executing command:", err) - fmt.Println("stderr:", errBuffer.String()) - return - } - - fmt.Println("stdout:", outBuffer.String()) - fmt.Println("stderr:", errBuffer.String()) -} - -// func openTerminal(cmd string) { -// var pkexecCmd *exec.Cmd - -// switch DesktopEnv { -// case "xfce": -// pkexecCmd = exec.Command("xfce4-terminal", "-e", cmd) -// case "gnome": -// pkexecCmd = exec.Command("gnome-terminal", "--", "bash", "-c", cmd) -// case "kde": -// pkexecCmd = exec.Command("konsole", "-e", cmd) -// case "mate": -// pkexecCmd = exec.Command("mate-terminal", "-e", cmd) -// case "lxde": -// pkexecCmd = exec.Command("lxterminal", "-e", cmd) -// case "lxqt": -// pkexecCmd = exec.Command("qterminal", "-e", cmd) -// default: -// fmt.Printf("Unsupported desktop environment: %s\n", DesktopEnv) -// return -// } - -// if err := pkexecCmd.Run(); err != nil { -// fmt.Printf("Error executing command: %v\n", err) -// } -// } - -func getDesktopEnvironment() string { - return strings.ToLower(os.Getenv("XDG_CURRENT_DESKTOP")) -} - -func (a *App) GetMultiplePackageInfo(packageNames []string) ([]PackageInfo, error) { - var results []PackageInfo - var wg sync.WaitGroup - resultChan := make(chan PackageInfo, len(packageNames)) - errorChan := make(chan error, len(packageNames)) - - for _, pkgName := range packageNames { - wg.Add(1) - go func(name string) { - defer wg.Done() - - // Search for package info - searchResults := a.SearchPackage(name) - - var pkg PackageInfo - for _, result := range searchResults { - if result.Repository == "core" || result.Repository == "extra" || result.Repository == "AUR" { - pkg = result - pkg.Name = name // Ensure the name matches the search query - resultChan <- pkg - return - } - } - - // If no package was found in core, extra, or AUR, add a placeholder - if pkg.Name == "" { - resultChan <- PackageInfo{ - Name: name, - Description: "Package not found in core, extra, or AUR", - Repository: "unknown", - } - } - }(pkgName) - } - - // Close channels when all goroutines are done - go func() { - wg.Wait() - close(resultChan) - close(errorChan) - }() - - // Collect results and errors - for i := 0; i < len(packageNames); i++ { - select { - case result := <-resultChan: - results = append(results, result) - case err := <-errorChan: - return nil, fmt.Errorf("error processing packages: %w", err) - case <-a.ctx.Done(): - return nil, a.ctx.Err() - } - } - - // Sort results to maintain order of input packageNames - sort.Slice(results, func(i, j int) bool { - iIndex := indexOf(packageNames, results[i].Name) - jIndex := indexOf(packageNames, results[j].Name) - return iIndex < jIndex - }) - - return results, nil -} - -func indexOf(slice []string, item string) int { - for i, s := range slice { - if s == item { - return i - } - } - return -1 -} - -type UpdateInfo struct { - Name string `json:"name"` - OldVersion string `json:"oldVersion"` - NewVersion string `json:"newVersion"` - Repository string `json:"repository"` - DownloadSize int64 `json:"downloadSize"` -} - -// GetAvailableUpdates returns a list of available updates for packages -func (a *App) GetAvailableUpdates() ([]UpdateInfo, error) { - // Initialize ALPM - h, err := alpm.Initialize("/", "/var/lib/pacman") - if err != nil { - return nil, fmt.Errorf("failed to initialize alpm: %v", err) - } - defer h.Release() - - // Parse pacman configuration - pacmanConfig, _, err := paconf.ParseFile("/etc/pacman.conf") - if err != nil { - return nil, fmt.Errorf("failed to parse pacman config: %v", err) - } - - // Register sync databases - for _, repo := range pacmanConfig.Repos { - db, err := h.RegisterSyncDB(repo.Name, 0) - if err != nil { - return nil, fmt.Errorf("failed to register sync db %s: %v", repo.Name, err) - } - db.SetServers(repo.Servers) - } - - // Get local database - localDB, err := h.LocalDB() - if err != nil { - return nil, fmt.Errorf("failed to get local DB: %v", err) - } - - // Get sync databases - syncDBs, err := h.SyncDBs() - if err != nil { - return nil, fmt.Errorf("failed to get sync DBs: %v", err) - } - - var updates []UpdateInfo - var mutex sync.Mutex - var wg sync.WaitGroup - - // Check for updates in official repositories - wg.Add(1) - go func() { - defer wg.Done() - for _, pkg := range localDB.PkgCache().Slice() { - select { - case <-a.ctx.Done(): - return - default: - newPkg := pkg.SyncNewVersion(syncDBs) - if newPkg != nil { - mutex.Lock() - updates = append(updates, UpdateInfo{ - Name: pkg.Name(), - OldVersion: pkg.Version(), - NewVersion: newPkg.Version(), - Repository: newPkg.DB().Name(), - DownloadSize: newPkg.Size(), - }) - mutex.Unlock() - } - } - } - }() - - // Check for AUR updates - wg.Add(1) - go func() { - defer wg.Done() - aurUpdates, err := a.checkAURUpdates() - if err != nil { - log.Printf("Error checking AUR updates: %v", err) - return - } - mutex.Lock() - updates = append(updates, aurUpdates...) - mutex.Unlock() - }() - - wg.Wait() - - return updates, nil -} - -func (a *App) checkAURUpdates() ([]UpdateInfo, error) { - var aurUpdates []UpdateInfo - - // Get list of AUR packages - cmd := exec.CommandContext(a.ctx, "yay", "-Qm") - output, err := cmd.Output() - if err != nil { - return nil, fmt.Errorf("failed to get AUR package list: %v", err) - } - - aurPackages := strings.Split(strings.TrimSpace(string(output)), "\n") - - for _, pkg := range aurPackages { - select { - case <-a.ctx.Done(): - return aurUpdates, a.ctx.Err() - default: - parts := strings.Fields(pkg) - if len(parts) != 2 { - continue - } - name, version := parts[0], parts[1] - - // Check for updates using AUR RPC - cmd := exec.CommandContext(a.ctx, "curl", "-s", fmt.Sprintf("https://aur.archlinux.org/rpc/v5/info/%s", name)) - output, err := cmd.Output() - if err != nil { - log.Printf("Error checking AUR for package %s: %v", name, err) - continue - } - - var aurResponse struct { - Results []struct { - Version string `json:"Version"` - } `json:"results"` - } - err = json.Unmarshal(output, &aurResponse) - if err != nil { - log.Printf("Error parsing AUR response for package %s: %v", name, err) - continue - } - - if len(aurResponse.Results) > 0 && aurResponse.Results[0].Version != version { - aurUpdates = append(aurUpdates, UpdateInfo{ - Name: name, - OldVersion: version, - NewVersion: aurResponse.Results[0].Version, - Repository: "AUR", - DownloadSize: 0, - }) - } - } - } - - return aurUpdates, nil -} - -// HumanReadableSize converts bytes to a human-readable string -func (a *App) HumanReadableSize(size int64) string { - floatsize := float32(size) - units := [...]string{"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi"} - for _, unit := range units { - if floatsize < 1024 { - return fmt.Sprintf("%.1f %sB", floatsize, unit) - } - floatsize /= 1024 - } - return fmt.Sprintf("%d%s", size, "B") -} - -func (a *App) UpdateSinglePkg(pkg string) { - cmdStr := fmt.Sprintf("pkexec yay -S %s --noconfirm", pkg) - cmd := exec.Command("sh", "-c", cmdStr) - fmt.Println("Executing command:", cmdStr) - - var outBuffer, errBuffer bytes.Buffer - cmd.Stdout = &outBuffer - cmd.Stderr = &errBuffer - - err := cmd.Run() - if err != nil { - fmt.Println("Error executing command:", err) - fmt.Println("stderr:", errBuffer.String()) - return - } - - fmt.Println("stdout:", outBuffer.String()) - fmt.Println("stderr:", errBuffer.String()) -} - -func (a *App) UpdateAllPkg() { - cmdStr := "pkexec yay -Syu --noconfirm" - cmd := exec.Command("sh", "-c", cmdStr) - fmt.Println("Executing command:", cmdStr) - - var outBuffer, errBuffer bytes.Buffer - cmd.Stdout = &outBuffer - cmd.Stderr = &errBuffer - - err := cmd.Run() - if err != nil { - fmt.Println("Error executing command:", err) - fmt.Println("stderr:", errBuffer.String()) - return - } - - fmt.Println("stdout:", outBuffer.String()) - fmt.Println("stderr:", errBuffer.String()) -} diff --git a/assets/alg-app-store.desktop b/assets/alg-app-store.desktop index c7449b1..9844259 100644 --- a/assets/alg-app-store.desktop +++ b/assets/alg-app-store.desktop @@ -2,12 +2,12 @@ Type=Application Version=1.0 Name=App Store -GenericName=ALG - App Store -Keywords=utility;system;welcome; +GenericName=ALG App Store +Keywords=app;store;software;install;system; Encoding=UTF-8 Terminal=false Exec=alg-app-store -Icon=/usr/share/pixmaps/alg-app-store.png -Comment=ALG - App Store -Categories=System;Go; +Icon=alg-app-store +Comment=Install all your favourite apps +Categories=System;Apps; StartupNotify=true diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..34e8e84 --- /dev/null +++ b/build.sh @@ -0,0 +1,64 @@ +#!/bin/bash + +# Build script for ALG App Store Qt6 version + +set -e + +echo "===================================" +echo "ALG App Store - Qt6 Build Script" +echo "===================================" +echo "" + +# Check for required dependencies +echo "Checking dependencies..." + +command -v cmake >/dev/null 2>&1 || { + echo "Error: cmake is not installed. Install with: sudo pacman -S cmake" + exit 1 +} + +command -v qmake6 >/dev/null 2>&1 || command -v qmake >/dev/null 2>&1 || { + echo "Error: Qt6 is not installed. Install with: sudo pacman -S qt6-base" + exit 1 +} + +pkg-config --exists libalpm || { + echo "Error: libalpm is not installed. Install with: sudo pacman -S pacman" + exit 1 +} + +echo "All dependencies found!" +echo "" + +# Clean previous build +if [ -d "build" ]; then + echo "Cleaning previous build..." + rm -rf build +fi + +# Create build directory +echo "Creating build directory..." +mkdir -p build +cd build + +# Configure with CMake +echo "" +echo "Configuring with CMake..." +cmake .. -DCMAKE_BUILD_TYPE=Release + +# Build +echo "" +echo "Building..." +make -j$(nproc) + +echo "" +echo "===================================" +echo "Build completed successfully!" +echo "===================================" +echo "" +echo "To run the application:" +echo " ./build/alg-app-store" +echo "" +echo "To install system-wide:" +echo " sudo make install (from build directory)" +echo "" diff --git a/build/README.md b/build/README.md deleted file mode 100644 index 1ae2f67..0000000 --- a/build/README.md +++ /dev/null @@ -1,35 +0,0 @@ -# Build Directory - -The build directory is used to house all the build files and assets for your application. - -The structure is: - -* bin - Output directory -* darwin - macOS specific files -* windows - Windows specific files - -## Mac - -The `darwin` directory holds files specific to Mac builds. -These may be customised and used as part of the build. To return these files to the default state, simply delete them -and -build with `wails build`. - -The directory contains the following files: - -- `Info.plist` - the main plist file used for Mac builds. It is used when building using `wails build`. -- `Info.dev.plist` - same as the main plist file but used when building using `wails dev`. - -## Windows - -The `windows` directory contains the manifest and rc files used when building with `wails build`. -These may be customised for your application. To return these files to the default state, simply delete them and -build with `wails build`. - -- `icon.ico` - The icon used for the application. This is used when building using `wails build`. If you wish to - use a different icon, simply replace this file with your own. If it is missing, a new `icon.ico` file - will be created using the `appicon.png` file in the build directory. -- `installer/*` - The files used to create the Windows installer. These are used when building using `wails build`. -- `info.json` - Application details used for Windows builds. The data here will be used by the Windows installer, - as well as the application itself (right click the exe -> properties -> details) -- `wails.exe.manifest` - The main application manifest file. \ No newline at end of file diff --git a/build/appicon.png b/build/appicon.png deleted file mode 100644 index 63617fe..0000000 Binary files a/build/appicon.png and /dev/null differ diff --git a/build/darwin/Info.dev.plist b/build/darwin/Info.dev.plist deleted file mode 100644 index 04727c2..0000000 --- a/build/darwin/Info.dev.plist +++ /dev/null @@ -1,68 +0,0 @@ - - - - CFBundlePackageType - APPL - CFBundleName - {{.Info.ProductName}} - CFBundleExecutable - {{.Name}} - CFBundleIdentifier - com.wails.{{.Name}} - CFBundleVersion - {{.Info.ProductVersion}} - CFBundleGetInfoString - {{.Info.Comments}} - CFBundleShortVersionString - {{.Info.ProductVersion}} - CFBundleIconFile - iconfile - LSMinimumSystemVersion - 10.13.0 - NSHighResolutionCapable - true - NSHumanReadableCopyright - {{.Info.Copyright}} - {{if .Info.FileAssociations}} - CFBundleDocumentTypes - - {{range .Info.FileAssociations}} - - CFBundleTypeExtensions - - {{.Ext}} - - CFBundleTypeName - {{.Name}} - CFBundleTypeRole - {{.Role}} - CFBundleTypeIconFile - {{.IconName}} - - {{end}} - - {{end}} - {{if .Info.Protocols}} - CFBundleURLTypes - - {{range .Info.Protocols}} - - CFBundleURLName - com.wails.{{.Scheme}} - CFBundleURLSchemes - - {{.Scheme}} - - CFBundleTypeRole - {{.Role}} - - {{end}} - - {{end}} - NSAppTransportSecurity - - NSAllowsLocalNetworking - - - - diff --git a/build/darwin/Info.plist b/build/darwin/Info.plist deleted file mode 100644 index 19cc937..0000000 --- a/build/darwin/Info.plist +++ /dev/null @@ -1,63 +0,0 @@ - - - - CFBundlePackageType - APPL - CFBundleName - {{.Info.ProductName}} - CFBundleExecutable - {{.Name}} - CFBundleIdentifier - com.wails.{{.Name}} - CFBundleVersion - {{.Info.ProductVersion}} - CFBundleGetInfoString - {{.Info.Comments}} - CFBundleShortVersionString - {{.Info.ProductVersion}} - CFBundleIconFile - iconfile - LSMinimumSystemVersion - 10.13.0 - NSHighResolutionCapable - true - NSHumanReadableCopyright - {{.Info.Copyright}} - {{if .Info.FileAssociations}} - CFBundleDocumentTypes - - {{range .Info.FileAssociations}} - - CFBundleTypeExtensions - - {{.Ext}} - - CFBundleTypeName - {{.Name}} - CFBundleTypeRole - {{.Role}} - CFBundleTypeIconFile - {{.IconName}} - - {{end}} - - {{end}} - {{if .Info.Protocols}} - CFBundleURLTypes - - {{range .Info.Protocols}} - - CFBundleURLName - com.wails.{{.Scheme}} - CFBundleURLSchemes - - {{.Scheme}} - - CFBundleTypeRole - {{.Role}} - - {{end}} - - {{end}} - - diff --git a/build/windows/icon.ico b/build/windows/icon.ico deleted file mode 100644 index f334798..0000000 Binary files a/build/windows/icon.ico and /dev/null differ diff --git a/build/windows/info.json b/build/windows/info.json deleted file mode 100644 index 9727946..0000000 --- a/build/windows/info.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "fixed": { - "file_version": "{{.Info.ProductVersion}}" - }, - "info": { - "0000": { - "ProductVersion": "{{.Info.ProductVersion}}", - "CompanyName": "{{.Info.CompanyName}}", - "FileDescription": "{{.Info.ProductName}}", - "LegalCopyright": "{{.Info.Copyright}}", - "ProductName": "{{.Info.ProductName}}", - "Comments": "{{.Info.Comments}}" - } - } -} \ No newline at end of file diff --git a/build/windows/installer/project.nsi b/build/windows/installer/project.nsi deleted file mode 100644 index 654ae2e..0000000 --- a/build/windows/installer/project.nsi +++ /dev/null @@ -1,114 +0,0 @@ -Unicode true - -#### -## Please note: Template replacements don't work in this file. They are provided with default defines like -## mentioned underneath. -## If the keyword is not defined, "wails_tools.nsh" will populate them with the values from ProjectInfo. -## If they are defined here, "wails_tools.nsh" will not touch them. This allows to use this project.nsi manually -## from outside of Wails for debugging and development of the installer. -## -## For development first make a wails nsis build to populate the "wails_tools.nsh": -## > wails build --target windows/amd64 --nsis -## Then you can call makensis on this file with specifying the path to your binary: -## For a AMD64 only installer: -## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app.exe -## For a ARM64 only installer: -## > makensis -DARG_WAILS_ARM64_BINARY=..\..\bin\app.exe -## For a installer with both architectures: -## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app-amd64.exe -DARG_WAILS_ARM64_BINARY=..\..\bin\app-arm64.exe -#### -## The following information is taken from the ProjectInfo file, but they can be overwritten here. -#### -## !define INFO_PROJECTNAME "MyProject" # Default "{{.Name}}" -## !define INFO_COMPANYNAME "MyCompany" # Default "{{.Info.CompanyName}}" -## !define INFO_PRODUCTNAME "MyProduct" # Default "{{.Info.ProductName}}" -## !define INFO_PRODUCTVERSION "1.0.0" # Default "{{.Info.ProductVersion}}" -## !define INFO_COPYRIGHT "Copyright" # Default "{{.Info.Copyright}}" -### -## !define PRODUCT_EXECUTABLE "Application.exe" # Default "${INFO_PROJECTNAME}.exe" -## !define UNINST_KEY_NAME "UninstKeyInRegistry" # Default "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" -#### -## !define REQUEST_EXECUTION_LEVEL "admin" # Default "admin" see also https://nsis.sourceforge.io/Docs/Chapter4.html -#### -## Include the wails tools -#### -!include "wails_tools.nsh" - -# The version information for this two must consist of 4 parts -VIProductVersion "${INFO_PRODUCTVERSION}.0" -VIFileVersion "${INFO_PRODUCTVERSION}.0" - -VIAddVersionKey "CompanyName" "${INFO_COMPANYNAME}" -VIAddVersionKey "FileDescription" "${INFO_PRODUCTNAME} Installer" -VIAddVersionKey "ProductVersion" "${INFO_PRODUCTVERSION}" -VIAddVersionKey "FileVersion" "${INFO_PRODUCTVERSION}" -VIAddVersionKey "LegalCopyright" "${INFO_COPYRIGHT}" -VIAddVersionKey "ProductName" "${INFO_PRODUCTNAME}" - -# Enable HiDPI support. https://nsis.sourceforge.io/Reference/ManifestDPIAware -ManifestDPIAware true - -!include "MUI.nsh" - -!define MUI_ICON "..\icon.ico" -!define MUI_UNICON "..\icon.ico" -# !define MUI_WELCOMEFINISHPAGE_BITMAP "resources\leftimage.bmp" #Include this to add a bitmap on the left side of the Welcome Page. Must be a size of 164x314 -!define MUI_FINISHPAGE_NOAUTOCLOSE # Wait on the INSTFILES page so the user can take a look into the details of the installation steps -!define MUI_ABORTWARNING # This will warn the user if they exit from the installer. - -!insertmacro MUI_PAGE_WELCOME # Welcome to the installer page. -# !insertmacro MUI_PAGE_LICENSE "resources\eula.txt" # Adds a EULA page to the installer -!insertmacro MUI_PAGE_DIRECTORY # In which folder install page. -!insertmacro MUI_PAGE_INSTFILES # Installing page. -!insertmacro MUI_PAGE_FINISH # Finished installation page. - -!insertmacro MUI_UNPAGE_INSTFILES # Uinstalling page - -!insertmacro MUI_LANGUAGE "English" # Set the Language of the installer - -## The following two statements can be used to sign the installer and the uninstaller. The path to the binaries are provided in %1 -#!uninstfinalize 'signtool --file "%1"' -#!finalize 'signtool --file "%1"' - -Name "${INFO_PRODUCTNAME}" -OutFile "..\..\bin\${INFO_PROJECTNAME}-${ARCH}-installer.exe" # Name of the installer's file. -InstallDir "$PROGRAMFILES64\${INFO_COMPANYNAME}\${INFO_PRODUCTNAME}" # Default installing folder ($PROGRAMFILES is Program Files folder). -ShowInstDetails show # This will always show the installation details. - -Function .onInit - !insertmacro wails.checkArchitecture -FunctionEnd - -Section - !insertmacro wails.setShellContext - - !insertmacro wails.webview2runtime - - SetOutPath $INSTDIR - - !insertmacro wails.files - - CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" - CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" - - !insertmacro wails.associateFiles - !insertmacro wails.associateCustomProtocols - - !insertmacro wails.writeUninstaller -SectionEnd - -Section "uninstall" - !insertmacro wails.setShellContext - - RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath - - RMDir /r $INSTDIR - - Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" - Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk" - - !insertmacro wails.unassociateFiles - !insertmacro wails.unassociateCustomProtocols - - !insertmacro wails.deleteUninstaller -SectionEnd diff --git a/build/windows/installer/wails_tools.nsh b/build/windows/installer/wails_tools.nsh deleted file mode 100644 index f9c0f88..0000000 --- a/build/windows/installer/wails_tools.nsh +++ /dev/null @@ -1,249 +0,0 @@ -# DO NOT EDIT - Generated automatically by `wails build` - -!include "x64.nsh" -!include "WinVer.nsh" -!include "FileFunc.nsh" - -!ifndef INFO_PROJECTNAME - !define INFO_PROJECTNAME "{{.Name}}" -!endif -!ifndef INFO_COMPANYNAME - !define INFO_COMPANYNAME "{{.Info.CompanyName}}" -!endif -!ifndef INFO_PRODUCTNAME - !define INFO_PRODUCTNAME "{{.Info.ProductName}}" -!endif -!ifndef INFO_PRODUCTVERSION - !define INFO_PRODUCTVERSION "{{.Info.ProductVersion}}" -!endif -!ifndef INFO_COPYRIGHT - !define INFO_COPYRIGHT "{{.Info.Copyright}}" -!endif -!ifndef PRODUCT_EXECUTABLE - !define PRODUCT_EXECUTABLE "${INFO_PROJECTNAME}.exe" -!endif -!ifndef UNINST_KEY_NAME - !define UNINST_KEY_NAME "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" -!endif -!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINST_KEY_NAME}" - -!ifndef REQUEST_EXECUTION_LEVEL - !define REQUEST_EXECUTION_LEVEL "admin" -!endif - -RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}" - -!ifdef ARG_WAILS_AMD64_BINARY - !define SUPPORTS_AMD64 -!endif - -!ifdef ARG_WAILS_ARM64_BINARY - !define SUPPORTS_ARM64 -!endif - -!ifdef SUPPORTS_AMD64 - !ifdef SUPPORTS_ARM64 - !define ARCH "amd64_arm64" - !else - !define ARCH "amd64" - !endif -!else - !ifdef SUPPORTS_ARM64 - !define ARCH "arm64" - !else - !error "Wails: Undefined ARCH, please provide at least one of ARG_WAILS_AMD64_BINARY or ARG_WAILS_ARM64_BINARY" - !endif -!endif - -!macro wails.checkArchitecture - !ifndef WAILS_WIN10_REQUIRED - !define WAILS_WIN10_REQUIRED "This product is only supported on Windows 10 (Server 2016) and later." - !endif - - !ifndef WAILS_ARCHITECTURE_NOT_SUPPORTED - !define WAILS_ARCHITECTURE_NOT_SUPPORTED "This product can't be installed on the current Windows architecture. Supports: ${ARCH}" - !endif - - ${If} ${AtLeastWin10} - !ifdef SUPPORTS_AMD64 - ${if} ${IsNativeAMD64} - Goto ok - ${EndIf} - !endif - - !ifdef SUPPORTS_ARM64 - ${if} ${IsNativeARM64} - Goto ok - ${EndIf} - !endif - - IfSilent silentArch notSilentArch - silentArch: - SetErrorLevel 65 - Abort - notSilentArch: - MessageBox MB_OK "${WAILS_ARCHITECTURE_NOT_SUPPORTED}" - Quit - ${else} - IfSilent silentWin notSilentWin - silentWin: - SetErrorLevel 64 - Abort - notSilentWin: - MessageBox MB_OK "${WAILS_WIN10_REQUIRED}" - Quit - ${EndIf} - - ok: -!macroend - -!macro wails.files - !ifdef SUPPORTS_AMD64 - ${if} ${IsNativeAMD64} - File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_AMD64_BINARY}" - ${EndIf} - !endif - - !ifdef SUPPORTS_ARM64 - ${if} ${IsNativeARM64} - File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_ARM64_BINARY}" - ${EndIf} - !endif -!macroend - -!macro wails.writeUninstaller - WriteUninstaller "$INSTDIR\uninstall.exe" - - SetRegView 64 - WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}" - WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}" - WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}" - WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}" - WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" - WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S" - - ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 - IntFmt $0 "0x%08X" $0 - WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0" -!macroend - -!macro wails.deleteUninstaller - Delete "$INSTDIR\uninstall.exe" - - SetRegView 64 - DeleteRegKey HKLM "${UNINST_KEY}" -!macroend - -!macro wails.setShellContext - ${If} ${REQUEST_EXECUTION_LEVEL} == "admin" - SetShellVarContext all - ${else} - SetShellVarContext current - ${EndIf} -!macroend - -# Install webview2 by launching the bootstrapper -# See https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#online-only-deployment -!macro wails.webview2runtime - !ifndef WAILS_INSTALL_WEBVIEW_DETAILPRINT - !define WAILS_INSTALL_WEBVIEW_DETAILPRINT "Installing: WebView2 Runtime" - !endif - - SetRegView 64 - # If the admin key exists and is not empty then webview2 is already installed - ReadRegStr $0 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" - ${If} $0 != "" - Goto ok - ${EndIf} - - ${If} ${REQUEST_EXECUTION_LEVEL} == "user" - # If the installer is run in user level, check the user specific key exists and is not empty then webview2 is already installed - ReadRegStr $0 HKCU "Software\Microsoft\EdgeUpdate\Clients{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" - ${If} $0 != "" - Goto ok - ${EndIf} - ${EndIf} - - SetDetailsPrint both - DetailPrint "${WAILS_INSTALL_WEBVIEW_DETAILPRINT}" - SetDetailsPrint listonly - - InitPluginsDir - CreateDirectory "$pluginsdir\webview2bootstrapper" - SetOutPath "$pluginsdir\webview2bootstrapper" - File "tmp\MicrosoftEdgeWebview2Setup.exe" - ExecWait '"$pluginsdir\webview2bootstrapper\MicrosoftEdgeWebview2Setup.exe" /silent /install' - - SetDetailsPrint both - ok: -!macroend - -# Copy of APP_ASSOCIATE and APP_UNASSOCIATE macros from here https://gist.github.com/nikku/281d0ef126dbc215dd58bfd5b3a5cd5b -!macro APP_ASSOCIATE EXT FILECLASS DESCRIPTION ICON COMMANDTEXT COMMAND - ; Backup the previously associated file class - ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" "" - WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "${FILECLASS}_backup" "$R0" - - WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "${FILECLASS}" - - WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}" "" `${DESCRIPTION}` - WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\DefaultIcon" "" `${ICON}` - WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell" "" "open" - WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open" "" `${COMMANDTEXT}` - WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open\command" "" `${COMMAND}` -!macroend - -!macro APP_UNASSOCIATE EXT FILECLASS - ; Backup the previously associated file class - ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" `${FILECLASS}_backup` - WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "$R0" - - DeleteRegKey SHELL_CONTEXT `Software\Classes\${FILECLASS}` -!macroend - -!macro wails.associateFiles - ; Create file associations - {{range .Info.FileAssociations}} - !insertmacro APP_ASSOCIATE "{{.Ext}}" "{{.Name}}" "{{.Description}}" "$INSTDIR\{{.IconName}}.ico" "Open with ${INFO_PRODUCTNAME}" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\"" - - File "..\{{.IconName}}.ico" - {{end}} -!macroend - -!macro wails.unassociateFiles - ; Delete app associations - {{range .Info.FileAssociations}} - !insertmacro APP_UNASSOCIATE "{{.Ext}}" "{{.Name}}" - - Delete "$INSTDIR\{{.IconName}}.ico" - {{end}} -!macroend - -!macro CUSTOM_PROTOCOL_ASSOCIATE PROTOCOL DESCRIPTION ICON COMMAND - DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}" - WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "" "${DESCRIPTION}" - WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "URL Protocol" "" - WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\DefaultIcon" "" "${ICON}" - WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell" "" "" - WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open" "" "" - WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open\command" "" "${COMMAND}" -!macroend - -!macro CUSTOM_PROTOCOL_UNASSOCIATE PROTOCOL - DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}" -!macroend - -!macro wails.associateCustomProtocols - ; Create custom protocols associations - {{range .Info.Protocols}} - !insertmacro CUSTOM_PROTOCOL_ASSOCIATE "{{.Scheme}}" "{{.Description}}" "$INSTDIR\${PRODUCT_EXECUTABLE},0" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\"" - - {{end}} -!macroend - -!macro wails.unassociateCustomProtocols - ; Delete app custom protocol associations - {{range .Info.Protocols}} - !insertmacro CUSTOM_PROTOCOL_UNASSOCIATE "{{.Scheme}}" - {{end}} -!macroend diff --git a/build/windows/wails.exe.manifest b/build/windows/wails.exe.manifest deleted file mode 100644 index 17e1a23..0000000 --- a/build/windows/wails.exe.manifest +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - true/pm - permonitorv2,permonitor - - - \ No newline at end of file diff --git a/frontend/.gitignore b/frontend/.gitignore deleted file mode 100644 index ae34bd3..0000000 --- a/frontend/.gitignore +++ /dev/null @@ -1,36 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# next.js -/.next/ - - -# production -/build -/dist - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* -.pnpm-debug.log* - -# local env files -.env*.local - -# vercel -.vercel - -# typescript -*.tsbuildinfo \ No newline at end of file diff --git a/frontend/components.json b/frontend/components.json deleted file mode 100644 index 7c79d38..0000000 --- a/frontend/components.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "$schema": "https://ui.shadcn.com/schema.json", - "style": "new-york", - "rsc": false, - "tsx": true, - "tailwind": { - "config": "tailwind.config.js", - "css": "src/globals.css", - "baseColor": "slate", - "cssVariables": true - }, - "aliases": { - "components": "@/components", - "utils": "@/lib/utils" - } -} diff --git a/frontend/index.html b/frontend/index.html deleted file mode 100644 index edbc62d..0000000 --- a/frontend/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - myproject - - -
- - - - diff --git a/frontend/package-lock.json b/frontend/package-lock.json deleted file mode 100644 index 2293397..0000000 --- a/frontend/package-lock.json +++ /dev/null @@ -1,4574 +0,0 @@ -{ - "name": "frontend", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "frontend", - "version": "0.0.0", - "dependencies": { - "@radix-ui/react-checkbox": "^1.1.4", - "@radix-ui/react-dropdown-menu": "^2.1.6", - "@radix-ui/react-icons": "^1.3.2", - "@radix-ui/react-label": "^2.1.0", - "@radix-ui/react-progress": "^1.1.0", - "@radix-ui/react-scroll-area": "^1.1.0", - "@radix-ui/react-separator": "^1.1.0", - "@radix-ui/react-slot": "^1.0.2", - "@radix-ui/react-tabs": "^1.1.0", - "@radix-ui/react-toast": "^1.2.1", - "@radix-ui/react-tooltip": "^1.1.2", - "@types/react-window": "^1.8.8", - "class-variance-authority": "^0.7.0", - "clsx": "^2.0.0", - "lucide-react": "^0.290.0", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-virtualized-auto-sizer": "^1.0.24", - "react-window": "^1.8.10", - "tailwind-merge": "^1.14.0", - "tailwindcss-animate": "^1.0.7" - }, - "devDependencies": { - "@types/node": "^20.8.9", - "@types/react": "^18.0.17", - "@types/react-dom": "^18.0.6", - "@vitejs/plugin-react": "^2.0.1", - "autoprefixer": "^10.4.16", - "postcss": "^8.4.31", - "tailwindcss": "^3.3.5", - "typescript": "^4.6.4", - "vite": "^3.0.7" - } - }, - "node_modules/@alloc/quick-lru": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", - "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", - "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/highlight": "^7.24.7", - "picocolors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.24.9", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.9.tgz", - "integrity": "sha512-e701mcfApCJqMMueQI0Fb68Amflj83+dvAvHawoBpAz+GDjCIyGHzNwnefjsWJ3xiYAqqiQFoWbspGYBdb2/ng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.24.9", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.9.tgz", - "integrity": "sha512-5e3FI4Q3M3Pbr21+5xJwCv6ZT6KmGkI0vw3Tozy5ODAQFTIWe37iT8Cr7Ice2Ntb+M3iSKCEWMB1MBgKrW3whg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.24.9", - "@babel/helper-compilation-targets": "^7.24.8", - "@babel/helper-module-transforms": "^7.24.9", - "@babel/helpers": "^7.24.8", - "@babel/parser": "^7.24.8", - "@babel/template": "^7.24.7", - "@babel/traverse": "^7.24.8", - "@babel/types": "^7.24.9", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.24.10", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.10.tgz", - "integrity": "sha512-o9HBZL1G2129luEUlG1hB4N/nlYNWHnpwlND9eOMclRqqu1YDy2sSYVCFUZwl8I1Gxh+QSRrP2vD7EpUmFVXxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.24.9", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^2.5.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", - "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.8.tgz", - "integrity": "sha512-oU+UoqCHdp+nWVDkpldqIQL/i/bvAv53tRqLG/s+cOXxe66zOYLU7ar/Xs3LdmBihrUMEUhwu6dMZwbNOYDwvw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.24.8", - "@babel/helper-validator-option": "^7.24.8", - "browserslist": "^4.23.1", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", - "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", - "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", - "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", - "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.24.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.9.tgz", - "integrity": "sha512-oYbh+rtFKj/HwBQkFlUzvcybzklmVdVV3UU+mN7n2t/q3yGHbuVdNxyFvSBO1tfvjyArpHNcWMAzsSPdyI46hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-simple-access": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", - "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", - "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", - "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", - "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", - "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.8.tgz", - "integrity": "sha512-gV2265Nkcz7weJJfvDoAEVzC1e2OTDpkGbEsebse8koXUJUXPsCMi7sRo/+SPMuMZ9MtUPnGwITTnQnU5YjyaQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.24.7", - "@babel/types": "^7.24.8" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", - "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.24.7", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.8.tgz", - "integrity": "sha512-WzfbgXOkGzZiXXCqk43kKwZjzwx4oulxZi3nq2TYL9mOjQv6kYwul9mz6ID36njuL7Xkp6nJEfok848Zj10j/w==", - "dev": true, - "license": "MIT", - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz", - "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.24.7.tgz", - "integrity": "sha512-+Dj06GDZEFRYvclU6k4bme55GKBEWUmByM/eoKuqg4zTNQHiApWRhQph5fxQB2wAEFvRzL1tOEj1RJ19wJrhoA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-jsx": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.24.7.tgz", - "integrity": "sha512-QG9EnzoGn+Qar7rxuW+ZOsbWOt56FvvI93xInqsZDC5fsekx1AlIO4KIJ5M+D0p0SqSH156EpmZyXq630B8OlQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.7.tgz", - "integrity": "sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.7.tgz", - "integrity": "sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.8.tgz", - "integrity": "sha512-5F7SDGs1T72ZczbRwbGO9lQi0NLjQxzl6i4lJxLxfW9U5UluCSyEJeniWvnhl3/euNiqQVbo8zruhsDfid0esA==", - "license": "MIT", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", - "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.8.tgz", - "integrity": "sha512-t0P1xxAPzEDcEPmjprAQq19NWum4K0EQPjMwZQZbHt+GiZqvjCHjj755Weq1YRPVzBI+3zSfvScfpnuIecVFJQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.24.8", - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-function-name": "^7.24.7", - "@babel/helper-hoist-variables": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", - "@babel/parser": "^7.24.8", - "@babel/types": "^7.24.8", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.24.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.9.tgz", - "integrity": "sha512-xm8XrMKz0IlUdocVbYJe0Z9xEgidU7msskG8BbhnTPK/HZ2z/7FP7ykqPgrUH+C+r414mNfNWam1f2vqOjqjYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz", - "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz", - "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@floating-ui/core": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.5.tgz", - "integrity": "sha512-8GrTWmoFhm5BsMZOTHeGD2/0FLKLQQHvO/ZmQga4tKempYRLz8aqJGqXVuQgisnMObq2YZ2SgkwctN1LOOxcqA==", - "license": "MIT", - "dependencies": { - "@floating-ui/utils": "^0.2.5" - } - }, - "node_modules/@floating-ui/dom": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.8.tgz", - "integrity": "sha512-kx62rP19VZ767Q653wsP1XZCGIirkE09E0QUGNYTM/ttbbQHqcGPdSfWFxUyyNLc/W6aoJRBajOSXhP6GXjC0Q==", - "license": "MIT", - "dependencies": { - "@floating-ui/core": "^1.6.0", - "@floating-ui/utils": "^0.2.5" - } - }, - "node_modules/@floating-ui/react-dom": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.1.tgz", - "integrity": "sha512-4h84MJt3CHrtG18mGsXuLCHMrug49d7DFkU0RMIyshRveBeyV2hmV/pDaF2Uxtu8kgq5r46llp5E5FQiR0K2Yg==", - "license": "MIT", - "dependencies": { - "@floating-ui/dom": "^1.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@floating-ui/utils": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.5.tgz", - "integrity": "sha512-sTcG+QZ6fdEUObICavU+aB3Mp8HY4n14wYHdxK4fXjPmv3PXZZeY5RaguJmGyeH/CJQhX3fqKUtS4qc1LoHwhQ==", - "license": "MIT" - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@radix-ui/number": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz", - "integrity": "sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==", - "license": "MIT" - }, - "node_modules/@radix-ui/primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", - "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==", - "license": "MIT" - }, - "node_modules/@radix-ui/react-arrow": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz", - "integrity": "sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.0.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-checkbox": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.1.4.tgz", - "integrity": "sha512-wP0CPAHq+P5I4INKe3hJrIa1WoNqqrejzW+zoU0rOvo1b9gDEJJFl2rYfO1PYJUQCc2H1WZxIJmyv9BS8i5fLw==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.1", - "@radix-ui/react-compose-refs": "1.1.1", - "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-presence": "1.1.2", - "@radix-ui/react-primitive": "2.0.2", - "@radix-ui/react-use-controllable-state": "1.1.0", - "@radix-ui/react-use-previous": "1.1.0", - "@radix-ui/react-use-size": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz", - "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==", - "license": "MIT" - }, - "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", - "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-context": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", - "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-presence": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz", - "integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.1", - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-primitive": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz", - "integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-slot": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-slot": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", - "integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-collection": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz", - "integrity": "sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-slot": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", - "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-context": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", - "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-direction": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", - "integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.0.tgz", - "integrity": "sha512-/UovfmmXGptwGcBQawLzvn2jOfM0t4z3/uKffoBlj724+n3FvBbZ7M0aaBOmkp6pqFYpO4yx8tSVJjx3Fl2jig==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-escape-keydown": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dropdown-menu": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.6.tgz", - "integrity": "sha512-no3X7V5fD487wab/ZYSHXq3H37u4NVeLDKI/Ks724X/eEFSSEFYZxWgsIlr1UBeEyDaM29HM5x9p1Nv8DuTYPA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.1", - "@radix-ui/react-compose-refs": "1.1.1", - "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-menu": "2.1.6", - "@radix-ui/react-primitive": "2.0.2", - "@radix-ui/react-use-controllable-state": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz", - "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==", - "license": "MIT" - }, - "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", - "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-context": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", - "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-primitive": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz", - "integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-slot": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-slot": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", - "integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-focus-guards": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz", - "integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-focus-scope": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.2.tgz", - "integrity": "sha512-zxwE80FCU7lcXUGWkdt6XpTTCKPitG1XKOwViTxHVKIJhZl9MvIl2dVHeZENCWD9+EdWv05wlaEkRXUykU27RA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.1", - "@radix-ui/react-primitive": "2.0.2", - "@radix-ui/react-use-callback-ref": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-focus-scope/node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", - "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-focus-scope/node_modules/@radix-ui/react-primitive": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz", - "integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-slot": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-focus-scope/node_modules/@radix-ui/react-slot": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", - "integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-icons": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.2.tgz", - "integrity": "sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==", - "license": "MIT", - "peerDependencies": { - "react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc" - } - }, - "node_modules/@radix-ui/react-id": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", - "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-label": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.0.tgz", - "integrity": "sha512-peLblDlFw/ngk3UWq0VnYaOLy6agTZZ+MUO/WhVfm14vJGML+xH4FAl2XQGLqdefjNb7ApRg6Yn7U42ZhmYXdw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.0.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.6.tgz", - "integrity": "sha512-tBBb5CXDJW3t2mo9WlO7r6GTmWV0F0uzHZVFmlRmYpiSK1CDU5IKojP1pm7oknpBOrFZx/YgBRW9oorPO2S/Lg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.1", - "@radix-ui/react-collection": "1.1.2", - "@radix-ui/react-compose-refs": "1.1.1", - "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-direction": "1.1.0", - "@radix-ui/react-dismissable-layer": "1.1.5", - "@radix-ui/react-focus-guards": "1.1.1", - "@radix-ui/react-focus-scope": "1.1.2", - "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-popper": "1.2.2", - "@radix-ui/react-portal": "1.1.4", - "@radix-ui/react-presence": "1.1.2", - "@radix-ui/react-primitive": "2.0.2", - "@radix-ui/react-roving-focus": "1.1.2", - "@radix-ui/react-slot": "1.1.2", - "@radix-ui/react-use-callback-ref": "1.1.0", - "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.6.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz", - "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==", - "license": "MIT" - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-arrow": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.2.tgz", - "integrity": "sha512-G+KcpzXHq24iH0uGG/pF8LyzpFJYGD4RfLjCIBfGdSLXvjLHST31RUiRVrupIBMvIppMgSzQ6l66iAxl03tdlg==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.0.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-collection": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.2.tgz", - "integrity": "sha512-9z54IEKRxIa9VityapoEYMuByaG42iSy1ZXlY2KcuLSEtq8x4987/N6m15ppoMffgZX72gER2uHe1D9Y6Unlcw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.1", - "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-primitive": "2.0.2", - "@radix-ui/react-slot": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", - "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-context": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", - "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.5.tgz", - "integrity": "sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.1", - "@radix-ui/react-compose-refs": "1.1.1", - "@radix-ui/react-primitive": "2.0.2", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-escape-keydown": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-popper": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.2.tgz", - "integrity": "sha512-Rvqc3nOpwseCyj/rgjlJDYAgyfw7OC1tTkKn2ivhaMGcYt8FSBlahHOZak2i3QwkRXUXgGgzeEe2RuqeEHuHgA==", - "license": "MIT", - "dependencies": { - "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.1.2", - "@radix-ui/react-compose-refs": "1.1.1", - "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-primitive": "2.0.2", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-layout-effect": "1.1.0", - "@radix-ui/react-use-rect": "1.1.0", - "@radix-ui/react-use-size": "1.1.0", - "@radix-ui/rect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-portal": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.4.tgz", - "integrity": "sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.0.2", - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-presence": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz", - "integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.1", - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-primitive": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz", - "integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-slot": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-roving-focus": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.2.tgz", - "integrity": "sha512-zgMQWkNO169GtGqRvYrzb0Zf8NhMHS2DuEB/TiEmVnpr5OqPU3i8lfbxaAmC2J/KYuIQxyoQQ6DxepyXp61/xw==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.1", - "@radix-ui/react-collection": "1.1.2", - "@radix-ui/react-compose-refs": "1.1.1", - "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-direction": "1.1.0", - "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-primitive": "2.0.2", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-controllable-state": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", - "integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popper": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz", - "integrity": "sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==", - "license": "MIT", - "dependencies": { - "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-layout-effect": "1.1.0", - "@radix-ui/react-use-rect": "1.1.0", - "@radix-ui/react-use-size": "1.1.0", - "@radix-ui/rect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-portal": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.1.tgz", - "integrity": "sha512-A3UtLk85UtqhzFqtoC8Q0KvR2GbXF3mtPgACSazajqq6A41mEQgo53iPzY4i6BwDxlIFqWIhiQ2G729n+2aw/g==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-presence": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.0.tgz", - "integrity": "sha512-Gq6wuRN/asf9H/E/VzdKoUtT8GC9PQc9z40/vEr0VCJ4u5XvvhWIrSsCB6vD2/cH7ugTdSfYq9fLJCcM00acrQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", - "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-slot": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-progress": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.0.tgz", - "integrity": "sha512-aSzvnYpP725CROcxAOEBVZZSIQVQdHgBr2QQFKySsaD14u8dNT0batuXI+AAGDdAHfXH8rbnHmjYFqVJ21KkRg==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-context": "1.1.0", - "@radix-ui/react-primitive": "2.0.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-roving-focus": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.0.tgz", - "integrity": "sha512-EA6AMGeq9AEeQDeSH0aZgG198qkfHSbvWTf1HvoDmOB5bBG/qTxjYMWUKMnYiV6J/iP/J8MEFSuB2zRU2n7ODA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-collection": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.0", - "@radix-ui/react-direction": "1.1.0", - "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-controllable-state": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-scroll-area": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.1.0.tgz", - "integrity": "sha512-9ArIZ9HWhsrfqS765h+GZuLoxaRHD/j0ZWOWilsCvYTpYJp8XwCqNG7Dt9Nu/TItKOdgLGkOPCodQvDc+UMwYg==", - "license": "MIT", - "dependencies": { - "@radix-ui/number": "1.1.0", - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.0", - "@radix-ui/react-direction": "1.1.0", - "@radix-ui/react-presence": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.0.tgz", - "integrity": "sha512-3uBAs+egzvJBDZAzvb/n4NxxOYpnspmWxO2u5NbZ8Y6FM/NdrGSF9bop3Cf6F6C71z1rTSn8KV0Fo2ZVd79lGA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.0.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", - "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tabs": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.0.tgz", - "integrity": "sha512-bZgOKB/LtZIij75FSuPzyEti/XBhJH52ExgtdVqjCIh+Nx/FW+LhnbXtbCzIi34ccyMsyOja8T0thCzoHFXNKA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-context": "1.1.0", - "@radix-ui/react-direction": "1.1.0", - "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-presence": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-roving-focus": "1.1.0", - "@radix-ui/react-use-controllable-state": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-toast": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.1.tgz", - "integrity": "sha512-5trl7piMXcZiCq7MW6r8YYmu0bK5qDpTWz+FdEPdKyft2UixkspheYbjbrLXVN5NGKHFbOP7lm8eD0biiSqZqg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-collection": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.0", - "@radix-ui/react-dismissable-layer": "1.1.0", - "@radix-ui/react-portal": "1.1.1", - "@radix-ui/react-presence": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-controllable-state": "1.1.0", - "@radix-ui/react-use-layout-effect": "1.1.0", - "@radix-ui/react-visually-hidden": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.2.tgz", - "integrity": "sha512-9XRsLwe6Yb9B/tlnYCPVUd/TFS4J7HuOZW345DCeC6vKIxQGMZdx21RK4VoZauPD5frgkXTYVS5y90L+3YBn4w==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.0", - "@radix-ui/react-dismissable-layer": "1.1.0", - "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-popper": "1.2.0", - "@radix-ui/react-portal": "1.1.1", - "@radix-ui/react-presence": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-slot": "1.1.0", - "@radix-ui/react-use-controllable-state": "1.1.0", - "@radix-ui/react-visually-hidden": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", - "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", - "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-escape-keydown": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz", - "integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz", - "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-previous": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz", - "integrity": "sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-rect": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz", - "integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/rect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-size": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz", - "integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-visually-hidden": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.0.tgz", - "integrity": "sha512-N8MDZqtgCgG5S3aV60INAB475osJousYpZ4cTJ2cFbMpdHS5Y6loLTH8LPtkj2QN0x93J30HT/M3qJXM0+lyeQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.0.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/rect": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz", - "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==", - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "20.14.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.12.tgz", - "integrity": "sha512-r7wNXakLeSsGT0H1AU863vS2wa5wBOK4bWMjZz2wj+8nBx+m5PeIn0k8AloSLpRuiwdRQZwarZqHE4FNArPuJQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/prop-types": { - "version": "15.7.12", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", - "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==", - "license": "MIT" - }, - "node_modules/@types/react": { - "version": "18.3.3", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", - "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", - "license": "MIT", - "dependencies": { - "@types/prop-types": "*", - "csstype": "^3.0.2" - } - }, - "node_modules/@types/react-dom": { - "version": "18.3.0", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", - "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/react-window": { - "version": "1.8.8", - "resolved": "https://registry.npmjs.org/@types/react-window/-/react-window-1.8.8.tgz", - "integrity": "sha512-8Ls660bHR1AUA2kuRvVG9D/4XpRC6wjAaPT9dil7Ckc76eP9TKWZwwmgfq8Q1LANX3QNDnoU4Zp48A3w+zK69Q==", - "license": "MIT", - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@vitejs/plugin-react": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-2.2.0.tgz", - "integrity": "sha512-FFpefhvExd1toVRlokZgxgy2JtnBOdp4ZDsq7ldCWaqGSGn9UhWMAVm/1lxPL14JfNS5yGz+s9yFrQY6shoStA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.19.6", - "@babel/plugin-transform-react-jsx": "^7.19.0", - "@babel/plugin-transform-react-jsx-development": "^7.18.6", - "@babel/plugin-transform-react-jsx-self": "^7.18.6", - "@babel/plugin-transform-react-jsx-source": "^7.19.6", - "magic-string": "^0.26.7", - "react-refresh": "^0.14.0" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "peerDependencies": { - "vite": "^3.0.0" - } - }, - "node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true, - "license": "MIT" - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "dev": true, - "license": "MIT" - }, - "node_modules/aria-hidden": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz", - "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/autoprefixer": { - "version": "10.4.19", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", - "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.23.0", - "caniuse-lite": "^1.0.30001599", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.2.tgz", - "integrity": "sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001640", - "electron-to-chromium": "^1.4.820", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.1.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/camelcase-css": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001700", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001700.tgz", - "integrity": "sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/class-variance-authority": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.0.tgz", - "integrity": "sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==", - "license": "Apache-2.0", - "dependencies": { - "clsx": "2.0.0" - }, - "funding": { - "url": "https://joebell.co.uk" - } - }, - "node_modules/class-variance-authority/node_modules/clsx": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", - "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "license": "MIT" - }, - "node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/detect-node-es": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", - "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", - "license": "MIT" - }, - "node_modules/didyoumean": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true, - "license": "MIT" - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, - "node_modules/electron-to-chromium": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.0.tgz", - "integrity": "sha512-Vb3xHHYnLseK8vlMJQKJYXJ++t4u1/qJ3vykuVrVjvdiOEhYyT1AuP4x03G8EnPmYvYOhe9T+dADTmthjRQMkA==", - "dev": true, - "license": "ISC" - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/esbuild": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz", - "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/android-arm": "0.15.18", - "@esbuild/linux-loong64": "0.15.18", - "esbuild-android-64": "0.15.18", - "esbuild-android-arm64": "0.15.18", - "esbuild-darwin-64": "0.15.18", - "esbuild-darwin-arm64": "0.15.18", - "esbuild-freebsd-64": "0.15.18", - "esbuild-freebsd-arm64": "0.15.18", - "esbuild-linux-32": "0.15.18", - "esbuild-linux-64": "0.15.18", - "esbuild-linux-arm": "0.15.18", - "esbuild-linux-arm64": "0.15.18", - "esbuild-linux-mips64le": "0.15.18", - "esbuild-linux-ppc64le": "0.15.18", - "esbuild-linux-riscv64": "0.15.18", - "esbuild-linux-s390x": "0.15.18", - "esbuild-netbsd-64": "0.15.18", - "esbuild-openbsd-64": "0.15.18", - "esbuild-sunos-64": "0.15.18", - "esbuild-windows-32": "0.15.18", - "esbuild-windows-64": "0.15.18", - "esbuild-windows-arm64": "0.15.18" - } - }, - "node_modules/esbuild-android-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz", - "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-android-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz", - "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-darwin-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz", - "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-darwin-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz", - "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-freebsd-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz", - "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-freebsd-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz", - "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-32": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz", - "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz", - "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-arm": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz", - "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz", - "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-mips64le": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz", - "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-ppc64le": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz", - "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-riscv64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz", - "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-s390x": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz", - "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-netbsd-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz", - "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-openbsd-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz", - "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-sunos-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz", - "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-windows-32": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz", - "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-windows-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz", - "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-windows-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz", - "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/foreground-child": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", - "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-nonce": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", - "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-core-module": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz", - "integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/jiti": { - "version": "1.21.6", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", - "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", - "dev": true, - "license": "MIT", - "bin": { - "jiti": "bin/jiti.js" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT" - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/lucide-react": { - "version": "0.290.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.290.0.tgz", - "integrity": "sha512-CBDPRLOPjdo+bVlxhaa7FVWaB8OrZZQ34mwm0Fsz9ut6JltN/Td55640ur8bRWSJuz6+nX2klKrpBpV7ktwD3Q==", - "license": "ISC", - "peerDependencies": { - "react": "^16.5.1 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/magic-string": { - "version": "0.26.7", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.7.tgz", - "integrity": "sha512-hX9XH3ziStPoPhJxLq1syWuZMxbDvGNbVchfrdCtanC7D13888bMFow61x8axrx+GfHLtVeAx2kxL7tTGRl+Ow==", - "dev": true, - "license": "MIT", - "dependencies": { - "sourcemap-codec": "^1.4.8" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/memoize-one": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", - "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", - "license": "MIT" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, - "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", - "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pirates": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", - "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/postcss": { - "version": "8.4.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz", - "integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.1", - "source-map-js": "^1.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-import": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", - "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "postcss": "^8.0.0" - } - }, - "node_modules/postcss-js": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", - "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase-css": "^2.0.1" - }, - "engines": { - "node": "^12 || ^14 || >= 16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.4.21" - } - }, - "node_modules/postcss-load-config": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", - "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "lilconfig": "^3.0.0", - "yaml": "^2.3.4" - }, - "engines": { - "node": ">= 14" - }, - "peerDependencies": { - "postcss": ">=8.0.9", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "postcss": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/postcss-load-config/node_modules/lilconfig": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", - "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, - "node_modules/postcss-nested": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", - "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^6.1.1" - }, - "engines": { - "node": ">=12.0" - }, - "peerDependencies": { - "postcss": "^8.2.14" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.1.tgz", - "integrity": "sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" - }, - "peerDependencies": { - "react": "^18.3.1" - } - }, - "node_modules/react-refresh": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", - "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-remove-scroll": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.3.tgz", - "integrity": "sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ==", - "license": "MIT", - "dependencies": { - "react-remove-scroll-bar": "^2.3.7", - "react-style-singleton": "^2.2.3", - "tslib": "^2.1.0", - "use-callback-ref": "^1.3.3", - "use-sidecar": "^1.1.3" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-remove-scroll-bar": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", - "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", - "license": "MIT", - "dependencies": { - "react-style-singleton": "^2.2.2", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-style-singleton": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", - "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", - "license": "MIT", - "dependencies": { - "get-nonce": "^1.0.0", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-virtualized-auto-sizer": { - "version": "1.0.24", - "resolved": "https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.24.tgz", - "integrity": "sha512-3kCn7N9NEb3FlvJrSHWGQ4iVl+ydQObq2fHMn12i5wbtm74zHOPhz/i64OL3c1S1vi9i2GXtZqNqUJTQ+BnNfg==", - "license": "MIT", - "peerDependencies": { - "react": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0", - "react-dom": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/react-window": { - "version": "1.8.10", - "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.10.tgz", - "integrity": "sha512-Y0Cx+dnU6NLa5/EvoHukUD0BklJ8qITCtVEPY1C/nL8wwoZ0b5aEw8Ff1dOVHw7fCzMt55XfJDd8S8W8LCaUCg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.0.0", - "memoize-one": ">=3.1.1 <6" - }, - "engines": { - "node": ">8.0.0" - }, - "peerDependencies": { - "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/read-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pify": "^2.3.0" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "license": "MIT" - }, - "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rollup": { - "version": "2.79.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", - "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", - "dev": true, - "license": "MIT", - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=10.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "deprecated": "Please use @jridgewell/sourcemap-codec instead", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/sucrase": { - "version": "3.35.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", - "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.2", - "commander": "^4.0.0", - "glob": "^10.3.10", - "lines-and-columns": "^1.1.6", - "mz": "^2.7.0", - "pirates": "^4.0.1", - "ts-interface-checker": "^0.1.9" - }, - "bin": { - "sucrase": "bin/sucrase", - "sucrase-node": "bin/sucrase-node" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tailwind-merge": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.14.0.tgz", - "integrity": "sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/dcastil" - } - }, - "node_modules/tailwindcss": { - "version": "3.4.6", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.6.tgz", - "integrity": "sha512-1uRHzPB+Vzu57ocybfZ4jh5Q3SdlH7XW23J5sQoM9LhE9eIOlzxer/3XPSsycvih3rboRsvt0QCmzSrqyOYUIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@alloc/quick-lru": "^5.2.0", - "arg": "^5.0.2", - "chokidar": "^3.5.3", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.3.0", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "jiti": "^1.21.0", - "lilconfig": "^2.1.0", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "object-hash": "^3.0.0", - "picocolors": "^1.0.0", - "postcss": "^8.4.23", - "postcss-import": "^15.1.0", - "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.1", - "postcss-nested": "^6.0.1", - "postcss-selector-parser": "^6.0.11", - "resolve": "^1.22.2", - "sucrase": "^3.32.0" - }, - "bin": { - "tailwind": "lib/cli.js", - "tailwindcss": "lib/cli.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tailwindcss-animate": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", - "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", - "license": "MIT", - "peerDependencies": { - "tailwindcss": ">=3.0.0 || insiders" - } - }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/ts-interface-checker": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true, - "license": "MIT" - }, - "node_modules/update-browserslist-db": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", - "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.1.2", - "picocolors": "^1.0.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/use-callback-ref": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", - "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/use-sidecar": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", - "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", - "license": "MIT", - "dependencies": { - "detect-node-es": "^1.1.0", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, - "license": "MIT" - }, - "node_modules/vite": { - "version": "3.2.10", - "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.10.tgz", - "integrity": "sha512-Dx3olBo/ODNiMVk/cA5Yft9Ws+snLOXrhLtrI3F4XLt4syz2Yg8fayZMWScPKoz12v5BUv7VEmQHnsfpY80fYw==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.15.9", - "postcss": "^8.4.18", - "resolve": "^1.22.1", - "rollup": "^2.79.1" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - }, - "peerDependencies": { - "@types/node": ">= 14", - "less": "*", - "sass": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, - "node_modules/yaml": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz", - "integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==", - "dev": true, - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14" - } - } - } -} diff --git a/frontend/package.json b/frontend/package.json deleted file mode 100644 index 10affba..0000000 --- a/frontend/package.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "frontend", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview" - }, - "dependencies": { - "@radix-ui/react-checkbox": "^1.1.4", - "@radix-ui/react-dropdown-menu": "^2.1.6", - "@radix-ui/react-icons": "^1.3.2", - "@radix-ui/react-label": "^2.1.0", - "@radix-ui/react-progress": "^1.1.0", - "@radix-ui/react-scroll-area": "^1.1.0", - "@radix-ui/react-separator": "^1.1.0", - "@radix-ui/react-slot": "^1.0.2", - "@radix-ui/react-tabs": "^1.1.0", - "@radix-ui/react-toast": "^1.2.1", - "@radix-ui/react-tooltip": "^1.1.2", - "@types/react-window": "^1.8.8", - "class-variance-authority": "^0.7.0", - "clsx": "^2.0.0", - "lucide-react": "^0.290.0", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-virtualized-auto-sizer": "^1.0.24", - "react-window": "^1.8.10", - "tailwind-merge": "^1.14.0", - "tailwindcss-animate": "^1.0.7" - }, - "devDependencies": { - "@types/node": "^20.8.9", - "@types/react": "^18.0.17", - "@types/react-dom": "^18.0.6", - "@vitejs/plugin-react": "^2.0.1", - "autoprefixer": "^10.4.16", - "postcss": "^8.4.31", - "tailwindcss": "^3.3.5", - "typescript": "^4.6.4", - "vite": "^3.0.7" - } -} diff --git a/frontend/package.json.md5 b/frontend/package.json.md5 deleted file mode 100755 index 44dddfb..0000000 --- a/frontend/package.json.md5 +++ /dev/null @@ -1 +0,0 @@ -dba055dd0a545865cffe1a57e682c54b \ No newline at end of file diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js deleted file mode 100644 index 2e7af2b..0000000 --- a/frontend/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -export default { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx deleted file mode 100644 index 6ebebc9..0000000 --- a/frontend/src/App.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import React, { useState } from "react"; -import { ThemeProvider } from "./components/ui/theme-provider"; -import Navbar from "./components/Navbar"; -import Home from "./components/Home"; -import Search from "./components/Search"; -import Install from "./components/Installed"; -import { Toaster } from "./components/ui/toaster"; -import Updates from "./components/Updates"; - -type PageType = "home" | "search" | "install" | "updates"; - -const App: React.FC = () => { - const [currentPage, setCurrentPage] = useState("home"); - const renderPage = () => { - switch (currentPage) { - case "home": - return ; - case "search": - return ; - case "install": - return ; - case "updates": - return ; - default: - return ; - } - }; - - return ( - -
- -
{renderPage()}
- -
-
- ); -}; - -export default App; diff --git a/frontend/src/assets/fonts/OFL.txt b/frontend/src/assets/fonts/OFL.txt deleted file mode 100644 index 9cac04c..0000000 --- a/frontend/src/assets/fonts/OFL.txt +++ /dev/null @@ -1,93 +0,0 @@ -Copyright 2016 The Nunito Project Authors (contact@sansoxygen.com), - -This Font Software is licensed under the SIL Open Font License, Version 1.1. -This license is copied below, and is also available with a FAQ at: -http://scripts.sil.org/OFL - - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2 b/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2 deleted file mode 100644 index 2f9cc59..0000000 Binary files a/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2 and /dev/null differ diff --git a/frontend/src/assets/icon/appicon.png b/frontend/src/assets/icon/appicon.png deleted file mode 100644 index a456330..0000000 Binary files a/frontend/src/assets/icon/appicon.png and /dev/null differ diff --git a/frontend/src/assets/images/logo-universal.png b/frontend/src/assets/images/logo-universal.png deleted file mode 100644 index 99ac71f..0000000 Binary files a/frontend/src/assets/images/logo-universal.png and /dev/null differ diff --git a/frontend/src/components/ErrorBoundary.tsx b/frontend/src/components/ErrorBoundary.tsx deleted file mode 100644 index 542f34c..0000000 --- a/frontend/src/components/ErrorBoundary.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React, { Component, ErrorInfo, ReactNode } from "react"; - -interface Props { - children: ReactNode; -} - -interface State { - hasError: boolean; -} - -class ErrorBoundary extends Component { - public state: State = { - hasError: false, - }; - - public static getDerivedStateFromError(_: Error): State { - return { hasError: true }; - } - - public componentDidCatch(error: Error, errorInfo: ErrorInfo) { - console.error("Uncaught error:", error, errorInfo); - } - - public render() { - if (this.state.hasError) { - return

Sorry, there was an error

; - } - - return this.props.children; - } -} - -export default ErrorBoundary; diff --git a/frontend/src/components/Home.tsx b/frontend/src/components/Home.tsx deleted file mode 100644 index 621de13..0000000 --- a/frontend/src/components/Home.tsx +++ /dev/null @@ -1,536 +0,0 @@ -import { useCallback, useEffect, useState } from "react"; -import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from "../components/ui/card"; -import { Badge } from "../components/ui/badge"; -import { ScrollArea } from "../components/ui/scroll-area"; -import PackageDetails from "./PackageDetails"; -import ErrorBoundary from "./ErrorBoundary"; -import { CheckPackageInstalled } from "../../wailsjs/go/main/App"; - -interface AppInfo { - name: string; - version: string; - description: string; - repository: string; - maintainer: string; - upstreamurl: string; - dependlist: string[]; - lastupdated: string; -} - -const featuredApps: AppInfo[] = [ - { - name: "firefox", - version: "128.0.2-1", - description: "Fast, Private \u0026 Safe Web Browser", - repository: "extra", - maintainer: - "Jan Alexander Steffens (heftig) \u003cheftig@archlinux.org\u003e", - upstreamurl: "https://www.mozilla.org/firefox/", - dependlist: [ - "alsa-lib", - "at-spi2-core", - "bash", - "cairo", - "dbus", - "ffmpeg", - "fontconfig", - "freetype2", - "gcc-libs", - "gdk-pixbuf2", - "glib2", - "glibc", - "gtk3", - "hicolor-icon-theme", - "libpulse", - "libx11", - "libxcb", - "libxcomposite", - "libxdamage", - "libxext", - "libxfixes", - "libxrandr", - "libxss", - "libxt", - "mime-types", - "nspr", - "nss", - "pango", - "ttf-font", - ], - lastupdated: "Jul. 23, 2024, 4 p.m. UTC", - }, - { - name: "gimp", - description: "GNU Image Manipulation Program", - repository: "extra", - version: "2.10.38-1", - maintainer: "Christian Hesse, Christian Heusel", - lastupdated: "May 3, 2024, 9:48 a.m. UTC", - dependlist: [ - "aalib", - "babl", - "bzip2", - "cairo", - "fontconfig", - "freetype2", - "gcc-libs", - "gdk-pixbuf2", - "gegl", - "glib2", - "glibc", - "gtk2", - "harfbuzz", - "hicolor-icon-theme", - "iso-codes", - "json-glib", - "lcms2", - "libgexiv2", - "libgudev", - "libheif", - "libjpeg-turbo", - "libjxl", - "libmng", - "libmypaint", - "libpng", - "librsvg", - "libtiff", - "libunwind", - "libwebp", - "libwmf", - "libx11", - "libxcursor", - "libxext", - "libxfixes", - "libxmu", - "libxpm", - "mypaint-brushes1", - "openexr", - "openjpeg2", - "pango", - "poppler-data", - "poppler-glib", - "xz", - "zlib", - ], - upstreamurl: "https://www.gimp.org/", - }, - { - name: "vlc", - version: "3.0.21-1", - description: "Multi-platform MPEG, VCD/DVD, and DivX player", - repository: "extra", - maintainer: "Antonio Rojas \u003carojas@archlinux.org\u003e", - upstreamurl: "https://www.videolan.org/vlc/", - dependlist: [ - "a52dec", - "abseil-cpp", - "aribb24", - "bash", - "cairo", - "dbus", - "faad2", - "ffmpeg4.4", - "fontconfig", - "freetype2", - "fribidi", - "gcc-libs", - "gdk-pixbuf2", - "glib2", - "glibc", - "gnutls", - "harfbuzz", - "hicolor-icon-theme", - "libarchive", - "libdca", - "libdvbpsi", - "libglvnd", - "libidn", - "libmad", - "libmatroska", - "libmpcdec", - "libmpeg2", - "libproxy", - "libsecret", - "libtar", - "libupnp", - "libixml.so", - "libupnp.so", - "libva", - "libx11", - "libxcb", - "libxinerama", - "libxml2", - "libxpm", - "lua", - "qt5-base", - "qt5-svg", - "qt5-x11extras", - "taglib", - "wayland", - "xcb-util-keysyms", - "zlib", - ], - lastupdated: "Jun. 16, 2024, 8 p.m. UTC", - }, - { - name: "visual-studio-code-bin", - description: - "Visual Studio Code (vscode): Editor for building and debugging modern web and cloud applications (official binary version)", - repository: "AUR", - version: "1.57.0", - maintainer: "Microsoft", - lastupdated: "2023-06-10", - dependlist: [ - "libxkbfile", - "gnupg", - "gtk3", - "libsecret", - "nss", - "gcc-libs", - "libnotify", - "libxss", - "glibc", - "lsof", - "shared-mime-info", - "xdg-utils", - "alsa-lib", - ], - upstreamurl: "", - }, - { - name: "libreoffice-still", - version: "7.6.7-1", - description: "LibreOffice maintenance branch", - repository: "extra", - maintainer: "Andreas Radke \u003candyrtr@archlinux.org\u003e", - upstreamurl: "https://www.libreoffice.org/", - dependlist: [ - "curl", - "hunspell", - "python", - "libwpd", - "libwps", - "neon", - "pango", - "nspr", - "libjpeg", - "libxrandr", - "libgl", - "redland", - "hyphen", - "lpsolve", - "gcc-libs", - "sh", - "graphite", - "icu", - "libxslt", - "lcms2", - "libvisio", - "libetonyek", - "libodfgen", - "libcdr", - "libmspub", - "harfbuzz-icu", - "nss", - "clucene", - "hicolor-icon-theme", - "desktop-file-utils", - "shared-mime-info", - "libpagemaker", - "libxinerama", - "libabw", - "libmwaw", - "libe-book", - "libcups", - "liblangtag", - "libexttextcat", - "liborcus", - "libwebp", - "libcmis", - "libtommath", - "libzmf", - "libatomic_ops", - "libnumbertext", - "gpgme", - "libfreehand", - "libstaroffice", - "libepubgen", - "libqxp", - "libepoxy", - "box2d", - "zxing-cpp", - "xdg-utils", - "libldap", - "fontconfig", - "zlib", - "libpng", - "freetype2", - "raptor", - "libxml2", - "cairo", - "libx11", - "expat", - "glib2", - "boost-libs", - "libtiff", - "dbus", - "glibc", - "librevenge", - "libxext", - "openjpeg2", - ], - lastupdated: "May. 19, 2024, 7 p.m. UTC", - }, - { - name: "blender", - version: "17:4.2.0-3", - description: "A fully integrated 3D graphics creation suite", - repository: "extra", - maintainer: "Sven-Hendrik Haase \u003csvenstaro@archlinux.org\u003e", - upstreamurl: "https://www.blender.org", - dependlist: [ - "alembic", - "bash", - "boost-libs", - "draco", - "embree", - "expat", - "ffmpeg", - "fftw", - "freetype2", - "gcc-libs", - "glew", - "glibc", - "gmp", - "hicolor-icon-theme", - "imath", - "intel-oneapi-compiler-dpcpp-cpp-runtime-libs", - "intel-oneapi-compiler-shared-runtime-libs", - "jack", - "jemalloc", - "level-zero-loader", - "libepoxy", - "libharu", - "libjpeg-turbo", - "libpng", - "libsndfile", - "libspnav", - "libtiff", - "libwebp", - "libx11", - "libxfixes", - "libxi", - "libxkbcommon", - "libxml2", - "libxrender", - "libxxf86vm", - "llvm-libs", - "materialx", - "onetbb", - "openal", - "opencollada", - "opencolorio", - "openexr", - "openimagedenoise", - "openimageio", - "openjpeg2", - "openpgl", - "openshadinglanguage", - "opensubdiv", - "openvdb", - "openxr", - "potrace", - "pugixml", - "pystring", - "python", - "python-numpy", - "python-requests", - "sdl2", - "shared-mime-info", - "usd", - "xdg-utils", - "yaml-cpp", - "zlib", - "zstd", - ], - lastupdated: "Jul. 17, 2024, 12 p.m. UTC", - }, - { - name: "zed", - version: "0.144.4-1", - description: - "A high-performance, multiplayer code editor from the creators of Atom and Tree-sitter", - repository: "extra", - maintainer: "Caleb Maclennan \u003calerque@archlinux.org\u003e", - upstreamurl: "https://zed.dev", - dependlist: [ - "alsa-lib", - "libasound.so", - "fontconfig", - "gcc-libs", - "glibc", - "libxcb", - "libxkbcommon", - "libxkbcommon-x11", - "openssl", - "libcrypto.so", - "libssl.so", - "sqlite", - "vulkan-driver", - "vulkan-icd-loader", - "vulkan-tools", - "wayland", - "zlib", - "libz.so", - ], - lastupdated: "Jul. 19, 2024, 9 p.m. UTC", - }, - { - name: "git", - version: "2.45.2-1", - description: "the fast distributed version control system", - repository: "extra", - maintainer: "Christian Hesse \u003ceworm@archlinux.org\u003e", - upstreamurl: "https://git-scm.com/", - dependlist: [ - "curl", - "expat", - "perl", - "perl-error", - "perl-mailtools", - "openssl", - "pcre2", - "grep", - "shadow", - "zlib", - ], - lastupdated: "Jun. 1, 2024, 9 p.m. UTC", - }, - { - name: "google-chrome", - version: "127.0.6533.72-1", - description: "The popular web browser by Google (Stable Channel)", - repository: "AUR", - maintainer: "gromit", - upstreamurl: "https://www.google.com/chrome", - dependlist: [ - "alsa-lib", - "gtk3", - "libcups", - "libxss", - "libxtst", - "nss", - "ttf-liberation", - "xdg-utils", - ], - lastupdated: "23-07-2024", - }, -]; - -const Home = () => { - const [selectedApp, setSelectedApp] = useState(null); - const [installedApps, setInstalledApps] = useState>(new Set()); - - const checkInstalledApps = useCallback(async () => { - try { - const installedSet = new Set(); - for (const app of featuredApps) { - try { - const isInstalled = await CheckPackageInstalled(app.name); - if (isInstalled) { - installedSet.add(app.name); - } - } catch (error) { - console.error(`Error checking if ${app.name} is installed:`, error); - } - } - setInstalledApps(installedSet); - } catch (error) { - console.error("Error fetching package information:", error); - } - }, []); - - useEffect(() => { - checkInstalledApps(); - const interval = setInterval(checkInstalledApps, 3000); - return () => clearInterval(interval); - }, [checkInstalledApps]); - - const handleInstallStateChange = useCallback(() => { - checkInstalledApps(); - }, [checkInstalledApps]); - - const handleBack = useCallback(() => { - setSelectedApp(null); - }, []); - - const handleSelectApp = useCallback((app: AppInfo) => { - setSelectedApp({ ...app }); - }, []); - - if (selectedApp) { - return ( - - - - ); - } - - return ( - -
-

Mentioned Packages

- -
- {featuredApps.map((app, index) => ( - handleSelectApp(app)} - > - -
- {app.name} - {installedApps.has(app.name) && ( - - Installed - - )} -
-
- - - {app.description && app.description.length > 70 - ? `${app.description.substring(0, 80)}...` - : app.description} - -

- Version: {app.version} -

-
- - - {app.repository} - - -
- ))} -
-
-
-
- ); -}; - -export default Home; diff --git a/frontend/src/components/Installed.tsx b/frontend/src/components/Installed.tsx deleted file mode 100644 index 3e78d9b..0000000 --- a/frontend/src/components/Installed.tsx +++ /dev/null @@ -1,214 +0,0 @@ -import React, { useState, useEffect, useCallback, useMemo } from "react"; -import { FixedSizeGrid as Grid } from "react-window"; -import AutoSizer from "react-virtualized-auto-sizer"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { GetInstalledPackages } from "../../wailsjs/go/main/App"; -import { main } from "wailsjs/go/models"; -import { Badge } from "./ui/badge"; -import { Skeleton } from "./ui/skeleton"; -import PackageDetails from "./PackageDetails"; -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; -import { AlertCircle } from "lucide-react"; - -const Installed = () => { - const [installedPackages, setInstalledPackages] = useState< - main.PackageInfo[] - >([]); - const [isLoading, setIsLoading] = useState(true); - const [error, setError] = useState(null); - const [searchTerm, setSearchTerm] = useState(""); - const [selectedPackage, setSelectedPackage] = - useState(null); - - const fetchInstalledPackages = useCallback(async () => { - setIsLoading(true); - try { - const packages = await GetInstalledPackages(); - setInstalledPackages(packages); - setError(null); - } catch (err) { - console.error("Error fetching installed packages:", err); - setError("Failed to fetch installed packages. Please try again."); - } finally { - setIsLoading(false); - } - }, []); - - useEffect(() => { - fetchInstalledPackages(); - const intervalId = setInterval(fetchInstalledPackages, 5000); - return () => clearInterval(intervalId); - }, [fetchInstalledPackages]); - - const filteredPackages = useMemo(() => { - return installedPackages.filter((pkg) => - pkg.name.toLowerCase().includes(searchTerm.toLowerCase()) - ); - }, [installedPackages, searchTerm]); - - const PackageItem = useCallback( - ({ - columnIndex, - rowIndex, - style, - }: { - columnIndex: number; - rowIndex: number; - style: React.CSSProperties; - }) => { - const index = rowIndex * 3 + columnIndex; - const pkg = filteredPackages[index]; - if (!pkg) return null; - - return ( -
- setSelectedPackage(pkg)} - > - - {pkg.name} - - - - {pkg.description && pkg.description.length > 70 - ? `${pkg.description.substring(0, 80)}...` - : pkg.description} - -

- Last Updated: {pkg.lastupdated} -

-
- - - {pkg.version} - - -
-
- ); - }, - [filteredPackages] - ); - - const renderContent = useMemo(() => { - if (isLoading && installedPackages.length === 0) { - return ( -
- {Array.from({ length: 9 }, (_, i) => ( - - - - - - - - - - - - - ))} -
- ); - } - - if (error) { - return ( - - - Error - {error} - - ); - } - - if (filteredPackages.length === 0) { - return ( -
- - - No packages found - - - {searchTerm - ? "No packages match your search." - : "No installed packages found."} - - -
- ); - } - - return ( -
- - {({ height, width }: { height: number; width: number }) => { - const columnCount = width >= 1024 ? 3 : width >= 768 ? 2 : 1; - const columnWidth = width / columnCount; - const rowCount = Math.ceil(filteredPackages.length / columnCount); - return ( - - {PackageItem} - - ); - }} - -
- ); - }, [ - isLoading, - error, - filteredPackages, - PackageItem, - installedPackages.length, - searchTerm, - ]); - - return ( -
- {selectedPackage ? ( - setSelectedPackage(null)} - onInstallStateChange={fetchInstalledPackages} - /> - ) : ( - <> -

Installed Packages

-
- ) => - setSearchTerm(e.target.value) - } - className="flex-grow" - aria-label="Search installed packages" - /> -
- {renderContent} - - )} -
- ); -}; - -export default React.memo(Installed); diff --git a/frontend/src/components/Navbar.tsx b/frontend/src/components/Navbar.tsx deleted file mode 100644 index 0e0da85..0000000 --- a/frontend/src/components/Navbar.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import React from "react"; -import { Search, Package, PackagePlus, Home, Moon, Sun } from "lucide-react"; -import { Button } from "@/components/ui/button"; -import { useTheme } from "@/components/ui/theme-provider"; - -type PageType = "home" | "search" | "install" | "updates"; - -interface NavbarProps { - setCurrentPage: (page: PageType) => void; -} - -const Navbar: React.FC = ({ setCurrentPage }) => { - const { setTheme, theme } = useTheme(); - - return ( - - ); -}; - -export default Navbar; diff --git a/frontend/src/components/PackageDetails.tsx b/frontend/src/components/PackageDetails.tsx deleted file mode 100644 index 53c1135..0000000 --- a/frontend/src/components/PackageDetails.tsx +++ /dev/null @@ -1,399 +0,0 @@ -import React, { - useEffect, - useState, - useCallback, - useMemo, - useRef, -} from "react"; -import { Button } from "@/components/ui/button"; -import { ArrowLeft, Download, Check, Copy, Trash2 } from "lucide-react"; -import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { Badge } from "@/components/ui/badge"; -import { Separator } from "@/components/ui/separator"; -import { ScrollArea } from "@/components/ui/scroll-area"; -import { main } from "wailsjs/go/models"; -import { - CheckPackageInstalled, - Install, - Uninstall, -} from "../../wailsjs/go/main/App"; -import ErrorBoundary from "./ErrorBoundary"; -import { Skeleton } from "./ui/skeleton"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "./ui/tabs"; -import CircularProgress from "./ui/circular-progress"; - -interface PackageDetailsProps { - app: main.PackageInfo | null; - onBack: () => void; - onInstallStateChange: () => void; -} - -const PackageDetails: React.FC = ({ - app, - onBack, - onInstallStateChange, -}) => { - const [isInstalled, setIsInstalled] = useState(false); - const [isCheckingInstall, setIsCheckingInstall] = useState(false); - const [isInstalling, setIsInstalling] = useState(false); - const [installProgress, setInstallProgress] = useState(0); - const [error, setError] = useState(null); - const [isLoading, setIsLoading] = useState(true); - const installInstructionsRef = useRef(null); - const [copiedCommand, setCopiedCommand] = useState(null); - - const handleCopyCommand = (command: string) => { - navigator.clipboard.writeText(command).then(() => { - setCopiedCommand(command); - setTimeout(() => setCopiedCommand(null), 2000); - }); - }; - - const checkIfInstalled = useCallback(async (appName: string) => { - try { - setIsCheckingInstall(true); - setError(null); - const isExist = await CheckPackageInstalled(appName); - setIsInstalled(isExist); - } catch (err) { - console.error("Error checking package installation:", err); - } finally { - setIsCheckingInstall(false); - } - }, []); - - useEffect(() => { - if (app?.name) { - setIsLoading(true); - checkIfInstalled(app.name).finally(() => setIsLoading(false)); - } - }, [app, checkIfInstalled]); - - const handleInstall = useCallback(async () => { - if (!app?.name) return; - - try { - setIsInstalling(true); - setInstallProgress(0); - setError(null); - - const installationInterval = setInterval(() => { - setInstallProgress((prev) => Math.min(prev + 10, 90)); - }, 500); - - await Install(app.name); - - clearInterval(installationInterval); - setInstallProgress(100); - await checkIfInstalled(app.name); - onInstallStateChange(); - } catch (err) { - setError("Failed to install package"); - console.error("Error installing package:", err); - } finally { - const isExist = await CheckPackageInstalled(app.name); - setIsInstalled(isExist); - setIsInstalling(false); - } - }, [app, onInstallStateChange]); - - const handleUninstall = async () => { - if (!app?.name) return; - - try { - setIsInstalling(true); - setInstallProgress(0); - setError(null); - - const installationInterval = setInterval(() => { - setInstallProgress((prev) => Math.min(prev + 10, 90)); - }, 500); - - await Uninstall(app.name); - - clearInterval(installationInterval); - setInstallProgress(100); - await checkIfInstalled(app.name); - onInstallStateChange(); - } catch (err) { - setError("Failed to install package"); - console.error("Error installing package:", err); - } finally { - const isExist = await CheckPackageInstalled(app.name); - setIsInstalled(isExist); - setIsInstalling(false); - } - }; - - const renderDependencies = useMemo(() => { - const dependencies = app?.dependlist; - if (!dependencies || dependencies.length === 0) { - return

No dependencies listed.

; - } - return ( -
    - {dependencies.map((dependency) => ( -
  • {dependency}
  • - ))} -
- ); - }, [app?.dependlist]); - - const renderInstallButton = useMemo(() => { - if (isInstalled) { - return ( - - ); - } - - if (isInstalling) { - return ( - - ); - } - return ( - - ); - }, [ - isCheckingInstall, - isInstalled, - isInstalling, - installProgress, - handleInstall, - ]); - - if (!app) { - return
No package information available
; - } - - if (isLoading) { - return ( -
- {Array.from({ length: 9 }, (_, i) => ( - - - - - - - - - - - - - ))} -
- ); - } - - const renderInstallationInstructions = () => { - const isOfficialRepo = - app.repository.includes("core") || - app.repository.includes("extra") || - app.repository.includes("local"); - const installCommand = `${isOfficialRepo ? "sudo pacman" : "yay"} -S ${ - app.name - }`; - - const uninstallCommand = `sudo pacman -Rdd ${app.name}`; - - return ( -
- - - {isOfficialRepo ? ( - pacman - ) : ( - <> - yay - paru - - )} - - {isOfficialRepo ? ( -
- -
-                  
-                    {app.repository.includes("local")
-                      ? uninstallCommand
-                      : installCommand}
-                  
-                
- -
-
- ) : ( -
- -
-                  {installCommand}
-                
- -
- -
-                  {`paru -S ${app.name}`}
-                
- -
- {/* - - - - - -

Install {app.name}

-
-
-
*/} -
- )} -
-
- ); - }; - - return ( - - -
-
- - {renderInstallButton} -
- {error && ( -
- Error: - {error} -
- )} - - -
-
- - {app.name || "N/A"} - - - {app.description || "No description available."} - -
- - {app.repository || "Unknown"} - -
-
- -
- {[ - { label: "Version", value: app.version }, - { label: "Maintainer", value: app.maintainer }, - { label: "Last Updated", value: app.lastupdated }, - { label: "Upstream URL", value: app.upstreamurl }, - ].map(({ label, value }) => ( -
-

{label}

-

{value || "N/A"}

-
- ))} -
-
-
- - - Details - - -

{app.description || "No description available."}

- -

Dependencies

- {renderDependencies} - -

Command

- {renderInstallationInstructions()} -
- -

- Please ensure your system meets the minimum requirements before - installation. -

-
-
-
-
-
- ); -}; - -interface CopyButtonProps { - command: string; - copiedCommand: string | null; - onCopy: (command: string) => void; -} - -const CopyButton: React.FC = ({ - command, - copiedCommand, - onCopy, -}) => ( - -); - -export default PackageDetails; diff --git a/frontend/src/components/Search.tsx b/frontend/src/components/Search.tsx deleted file mode 100644 index f1b9564..0000000 --- a/frontend/src/components/Search.tsx +++ /dev/null @@ -1,348 +0,0 @@ -import React, { useState, useCallback, useRef, useEffect } from "react"; -import { FixedSizeGrid as Grid } from "react-window"; -import AutoSizer from "react-virtualized-auto-sizer"; -import { Input } from "@/components/ui/input"; -import { Button } from "@/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { Badge } from "./ui/badge"; -import { SearchPackage, SearchLocalPackage } from "../../wailsjs/go/main/App"; -import { main } from "wailsjs/go/models"; -import PackageDetails from "./PackageDetails"; -import ErrorBoundary from "./ErrorBoundary"; -import { Skeleton } from "./ui/skeleton"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuTrigger, - DropdownMenuItem, - DropdownMenuSeparator, -} from "./ui/dropdown-menu"; -import { Checkbox } from "@/components/ui/checkbox"; - -const Search: React.FC = () => { - const [searchTerm, setSearchTerm] = useState(""); - const [searchResults, setSearchResults] = useState([]); - const [allResults, setAllResults] = useState([]); - const [isLoading, setIsLoading] = useState(false); - const [selectedApp, setSelectedApp] = useState(null); - const [error, setError] = useState(null); - const [installedApps, setInstalledApps] = useState>(new Set()); - const [activeFilters, setActiveFilters] = useState([]); - - const handleSearch = async (): Promise => { - if (!searchTerm.trim()) { - setError("Please enter a search term"); - return; - } - - setSearchResults([]); - setAllResults([]); - setSelectedApp(null); - setIsLoading(true); - setError(null); - - try { - const res = await SearchPackage( - searchTerm.toLowerCase().trim().replace(/\s+/g, "-") - ); - setAllResults(res); - - if (activeFilters.length > 0) { - const filtered = res.filter((pkg) => - activeFilters.includes(pkg.repository) - ); - setSearchResults(filtered); - if (filtered.length === 0) { - setError("No results found for selected filters!"); - } - } else { - setSearchResults(res); - } - - if (res.length === 0) { - setError("No results found"); - } else { - checkInstalledApps(res); - } - } catch (error) { - console.error("Error searching packages:", error); - setError("An error occurred while searching. Please try again."); - } finally { - setIsLoading(false); - } - }; - - const checkInstalledApps = useCallback(async (apps: main.PackageInfo[]) => { - const installedSet = new Set(); - for (const app of apps) { - try { - const isInstalled = await SearchLocalPackage(app.name); - if (isInstalled) { - installedSet.add(app.name); - } - } catch (error) { - console.error(`Error checking if ${app.name} is installed:`, error); - } - } - setInstalledApps(installedSet); - }, []); - - const handleKeyPress = (event: React.KeyboardEvent) => { - if (event.key === "Enter") { - handleSearch(); - } - }; - - const updateFilters = (filter: string, checked: boolean) => { - let newFilters: string[]; - - if (filter === "CLEAR_ALL") { - newFilters = []; - } else { - newFilters = checked - ? [...activeFilters, filter] - : activeFilters.filter((f) => f !== filter); - } - - setActiveFilters(newFilters); - - if (newFilters.length === 0) { - setSearchResults(allResults); - setError(null); - } else { - const filteredResults = allResults.filter((pkg) => - newFilters.includes(pkg.repository) - ); - - if (filteredResults.length === 0) { - setError("No results found for selected filters!"); - } else { - setError(null); - setSearchResults(filteredResults); - } - } - }; - - const handleInstallStateChange = useCallback(() => { - if (selectedApp) { - checkInstalledApps([selectedApp]); - } - }, [selectedApp, checkInstalledApps]); - - const CardItem = useCallback( - ({ - columnIndex, - rowIndex, - style, - }: { - columnIndex: number; - rowIndex: number; - style: React.CSSProperties; - }) => { - const index = rowIndex * 3 + columnIndex; - const result = searchResults[index]; - if (!result) return null; - - const isInstalled = installedApps.has(result.name); - - return ( -
- setSelectedApp({ ...result })} - > - -
- {result.name} - {isInstalled && ( - - Installed - - )} -
-
- - - {result.description && result.description.length > 70 - ? `${result.description.substring(0, 80)}...` - : result.description} - -

- Version: {result.version} -

-
- - - {result.repository} - - -
-
- ); - }, - [installedApps, searchResults] - ); - - const renderContent = () => { - if (isLoading) { - return ( -
- {Array.from({ length: 9 }, (_, i) => ( - - - - - - - - - - - - - ))} -
- ); - } - - if (error) { - return
{error}
; - } - - return ( -
- - {({ height, width }: { height: number; width: number }) => { - const columnCount = width >= 1024 ? 3 : width >= 768 ? 2 : 1; - const columnWidth = width / columnCount; - const rowCount = Math.ceil(searchResults.length / columnCount); - return ( - - {CardItem} - - ); - }} - -
- ); - }; - - return ( - -
- {selectedApp ? ( - setSelectedApp(null)} - onInstallStateChange={handleInstallStateChange} - /> - ) : ( - <> -

Search Packages

-
- ) => - setSearchTerm(e.target.value) - } - onKeyDown={handleKeyPress} - className="flex-grow" - /> - - -
- {renderContent()} - - )} -
-
- ); -}; - -type FilterDropdownProps = { - activeFilters: string[]; - onFilterChange: (filter: string, checked: boolean) => void; -}; - -const FilterDropdown: React.FC = ({ - activeFilters, - onFilterChange, -}) => { - const repositories = ["core", "extra", "AUR"]; - - return ( - - - - - - {repositories.map((repo) => ( - { - e.preventDefault(); - }} - className="flex items-center justify-between cursor-pointer" - > - - onFilterChange(repo, checked === true) - } - aria-label={`Filter by ${repo}`} - /> - {repo} - - ))} - {activeFilters.length > 0 && ( - <> - - { - activeFilters.forEach((filter) => - onFilterChange("CLEAR_ALL", false) - ); - }} - className="text-center text-xs cursor-pointer text-gray-500 hover:text-gray-700" - > - Clear all - - - )} - - - ); -}; - -export default Search; diff --git a/frontend/src/components/Updates.tsx b/frontend/src/components/Updates.tsx deleted file mode 100644 index 9448c68..0000000 --- a/frontend/src/components/Updates.tsx +++ /dev/null @@ -1,298 +0,0 @@ -import React, { useState, useEffect, useCallback, useMemo } from "react"; -import { FixedSizeGrid as Grid } from "react-window"; -import AutoSizer from "react-virtualized-auto-sizer"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { - GetAvailableUpdates, - HumanReadableSize, - UpdateAllPkg, - UpdateSinglePkg, -} from "../../wailsjs/go/main/App"; -import { main } from "wailsjs/go/models"; -import { Badge } from "./ui/badge"; -import { Skeleton } from "./ui/skeleton"; -import { ArrowUpCircle, AlertCircle } from "lucide-react"; -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; - -const useAvailableUpdates = () => { - const [availableUpdates, setAvailableUpdates] = useState( - [] - ); - const [isLoading, setIsLoading] = useState(true); - const [error, setError] = useState(null); - const [totalDownloadSize, setTotalDownloadSize] = useState(""); - - const fetchAvailableUpdates = useCallback(async () => { - setIsLoading(true); - try { - const updates = await GetAvailableUpdates(); - setAvailableUpdates(updates); - const totalSize = updates.reduce( - (acc, update) => acc + update.downloadSize, - 0 - ); - const readableSize = await HumanReadableSize(totalSize); - setTotalDownloadSize(readableSize); - setError(null); - } catch (err) { - console.error("Error fetching available updates:", err); - setError("Failed to fetch available updates. Please try again."); - } finally { - setIsLoading(false); - } - }, []); - - useEffect(() => { - fetchAvailableUpdates(); - const intervalId = setInterval(fetchAvailableUpdates, 5000); - return () => clearInterval(intervalId); - }, [fetchAvailableUpdates]); - - return { - availableUpdates, - isLoading, - error, - totalDownloadSize, - fetchAvailableUpdates, - }; -}; - -const useReadableSizes = (availableUpdates: main.UpdateInfo[]) => { - const [readableSizes, setReadableSizes] = useState<{ [key: string]: string }>( - {} - ); - - useEffect(() => { - const fetchSizes = async () => { - const sizes: { [key: string]: string } = {}; - for (const update of availableUpdates) { - sizes[update.name] = await HumanReadableSize(update.downloadSize); - } - setReadableSizes(sizes); - }; - fetchSizes(); - }, [availableUpdates]); - - return readableSizes; -}; - -const Updates: React.FC = () => { - const { - availableUpdates, - isLoading, - error, - totalDownloadSize, - fetchAvailableUpdates, - } = useAvailableUpdates(); - const readableSizes = useReadableSizes(availableUpdates); - const [searchTerm, setSearchTerm] = useState(""); - const [updatingAll, setUpdatingAll] = useState(false); - const [updatingPackages, setUpdatingPackages] = useState>( - new Set() - ); - - const filteredUpdates = useMemo(() => { - return availableUpdates.filter((update) => - update.name.toLowerCase().includes(searchTerm.toLowerCase()) - ); - }, [availableUpdates, searchTerm]); - - const handleSinglePackageUpdate = async (pkg: string) => { - setUpdatingPackages((prev) => new Set(prev).add(pkg)); - try { - await UpdateSinglePkg(pkg); - } catch (err) { - console.error(`Error updating package ${pkg}:`, err); - } finally { - setUpdatingPackages((prev) => { - const newSet = new Set(prev); - newSet.delete(pkg); - return newSet; - }); - fetchAvailableUpdates(); - } - }; - - const handleAllPackageUpdate = async () => { - setUpdatingAll(true); - try { - await UpdateAllPkg(); - } catch (err) { - console.error("Error updating all packages:", err); - } finally { - setUpdatingAll(false); - fetchAvailableUpdates(); - } - }; - - const UpdateItem = useCallback( - ({ - columnIndex, - rowIndex, - style, - }: { - columnIndex: number; - rowIndex: number; - style: React.CSSProperties; - }) => { - const index = rowIndex * 3 + columnIndex; - const update = filteredUpdates[index]; - if (!update) return null; - const isUpdating = updatingPackages.has(update.name) || updatingAll; - return ( -
- - - {update.name} - - - - Update available: {update.oldVersion} → {update.newVersion} - -

- Repository: {update.repository} -

-
- - - {readableSizes[update.name] || "Calculating..."} - - - -
-
- ); - }, - [filteredUpdates, readableSizes, updatingPackages, updatingAll] - ); - - const renderContent = useMemo(() => { - if (isLoading && availableUpdates.length === 0) { - return ( -
- {Array.from({ length: 9 }, (_, i) => ( - - - - - - - - - - - - - - ))} -
- ); - } - - if (error) { - return ( - - - Error - {error} - - ); - } - - if (filteredUpdates.length === 0) { - return ( - - - No updates available - - - {searchTerm - ? "No packages match your search." - : "All packages are up to date!"} - - - ); - } - - return ( -
- - {({ height, width }: { height: number; width: number }) => { - const columnCount = width >= 1024 ? 3 : width >= 768 ? 2 : 1; - const columnWidth = width / columnCount; - const rowCount = Math.ceil(filteredUpdates.length / columnCount); - return ( - - {UpdateItem} - - ); - }} - -
- ); - }, [ - isLoading, - error, - filteredUpdates, - UpdateItem, - availableUpdates.length, - searchTerm, - ]); - - return ( -
-

Available Updates

-
- ) => - setSearchTerm(e.target.value) - } - className="flex-grow" - aria-label="Search available updates" - /> - -
- {totalDownloadSize && ( -

- Total download size: {totalDownloadSize} -

- )} - {renderContent} -
- ); -}; - -export default React.memo(Updates); diff --git a/frontend/src/components/ui/alert.tsx b/frontend/src/components/ui/alert.tsx deleted file mode 100644 index 5afd41d..0000000 --- a/frontend/src/components/ui/alert.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import * as React from "react" -import { cva, type VariantProps } from "class-variance-authority" - -import { cn } from "@/lib/utils" - -const alertVariants = cva( - "relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7", - { - variants: { - variant: { - default: "bg-background text-foreground", - destructive: - "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", - }, - }, - defaultVariants: { - variant: "default", - }, - } -) - -const Alert = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes & VariantProps ->(({ className, variant, ...props }, ref) => ( -
-)) -Alert.displayName = "Alert" - -const AlertTitle = React.forwardRef< - HTMLParagraphElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)) -AlertTitle.displayName = "AlertTitle" - -const AlertDescription = React.forwardRef< - HTMLParagraphElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)) -AlertDescription.displayName = "AlertDescription" - -export { Alert, AlertTitle, AlertDescription } diff --git a/frontend/src/components/ui/badge.tsx b/frontend/src/components/ui/badge.tsx deleted file mode 100644 index f000e3e..0000000 --- a/frontend/src/components/ui/badge.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import * as React from "react" -import { cva, type VariantProps } from "class-variance-authority" - -import { cn } from "@/lib/utils" - -const badgeVariants = cva( - "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", - { - variants: { - variant: { - default: - "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", - secondary: - "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", - destructive: - "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", - outline: "text-foreground", - }, - }, - defaultVariants: { - variant: "default", - }, - } -) - -export interface BadgeProps - extends React.HTMLAttributes, - VariantProps {} - -function Badge({ className, variant, ...props }: BadgeProps) { - return ( -
- ) -} - -export { Badge, badgeVariants } diff --git a/frontend/src/components/ui/button.tsx b/frontend/src/components/ui/button.tsx deleted file mode 100644 index 0ba4277..0000000 --- a/frontend/src/components/ui/button.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import * as React from "react" -import { Slot } from "@radix-ui/react-slot" -import { cva, type VariantProps } from "class-variance-authority" - -import { cn } from "@/lib/utils" - -const buttonVariants = cva( - "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", - { - variants: { - variant: { - default: "bg-primary text-primary-foreground hover:bg-primary/90", - destructive: - "bg-destructive text-destructive-foreground hover:bg-destructive/90", - outline: - "border border-input bg-background hover:bg-accent hover:text-accent-foreground", - secondary: - "bg-secondary text-secondary-foreground hover:bg-secondary/80", - ghost: "hover:bg-accent hover:text-accent-foreground", - link: "text-primary underline-offset-4 hover:underline", - }, - size: { - default: "h-10 px-4 py-2", - sm: "h-9 rounded-md px-3", - lg: "h-11 rounded-md px-8", - icon: "h-10 w-10", - }, - }, - defaultVariants: { - variant: "default", - size: "default", - }, - } -) - -export interface ButtonProps - extends React.ButtonHTMLAttributes, - VariantProps { - asChild?: boolean -} - -const Button = React.forwardRef( - ({ className, variant, size, asChild = false, ...props }, ref) => { - const Comp = asChild ? Slot : "button" - return ( - - ) - } -) -Button.displayName = "Button" - -export { Button, buttonVariants } diff --git a/frontend/src/components/ui/card.tsx b/frontend/src/components/ui/card.tsx deleted file mode 100644 index 4cb56a6..0000000 --- a/frontend/src/components/ui/card.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import * as React from "react"; - -import { cn } from "@/lib/utils"; - -const Card = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)); -Card.displayName = "Card"; - -const CardHeader = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)); -CardHeader.displayName = "CardHeader"; - -const CardTitle = React.forwardRef< - HTMLParagraphElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -

-)); -CardTitle.displayName = "CardTitle"; - -const CardDescription = React.forwardRef< - HTMLParagraphElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -

-)); -CardDescription.displayName = "CardDescription"; - -const CardContent = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -

-)); -CardContent.displayName = "CardContent"; - -const CardFooter = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)); -CardFooter.displayName = "CardFooter"; - -export { - Card, - CardHeader, - CardFooter, - CardTitle, - CardDescription, - CardContent, -}; diff --git a/frontend/src/components/ui/checkbox.tsx b/frontend/src/components/ui/checkbox.tsx deleted file mode 100644 index 8d02b28..0000000 --- a/frontend/src/components/ui/checkbox.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import * as React from "react" -import * as CheckboxPrimitive from "@radix-ui/react-checkbox" -import { cn } from "@/lib/utils" -import { CheckIcon } from "@radix-ui/react-icons" - -const Checkbox = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - - - - - -)) -Checkbox.displayName = CheckboxPrimitive.Root.displayName - -export { Checkbox } diff --git a/frontend/src/components/ui/circular-progress.tsx b/frontend/src/components/ui/circular-progress.tsx deleted file mode 100644 index f6f8e5e..0000000 --- a/frontend/src/components/ui/circular-progress.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { cn } from "@/lib/utils"; -import React from "react"; - -interface CircularProgressProps { - size?: number; - strokeWidth?: number; - percentage: number; -} - -const CircularProgress: React.FC = ({ - size = 24, - strokeWidth = 2, - percentage, -}) => { - const radius = (size - strokeWidth) / 2; - const circumference = radius * 2 * Math.PI; - const strokeDashoffset = circumference - (percentage / 100) * circumference; - - return ( - - - - - ); -}; - -export default CircularProgress; diff --git a/frontend/src/components/ui/dropdown-menu.tsx b/frontend/src/components/ui/dropdown-menu.tsx deleted file mode 100644 index d26ae38..0000000 --- a/frontend/src/components/ui/dropdown-menu.tsx +++ /dev/null @@ -1,198 +0,0 @@ -import * as React from "react" -import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" -import { cn } from "@/lib/utils" -import { CheckIcon, ChevronRightIcon, DotFilledIcon } from "@radix-ui/react-icons" - -const DropdownMenu = DropdownMenuPrimitive.Root - -const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger - -const DropdownMenuGroup = DropdownMenuPrimitive.Group - -const DropdownMenuPortal = DropdownMenuPrimitive.Portal - -const DropdownMenuSub = DropdownMenuPrimitive.Sub - -const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup - -const DropdownMenuSubTrigger = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & { - inset?: boolean - } ->(({ className, inset, children, ...props }, ref) => ( - - {children} - - -)) -DropdownMenuSubTrigger.displayName = - DropdownMenuPrimitive.SubTrigger.displayName - -const DropdownMenuSubContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -DropdownMenuSubContent.displayName = - DropdownMenuPrimitive.SubContent.displayName - -const DropdownMenuContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, sideOffset = 4, ...props }, ref) => ( - - - -)) -DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName - -const DropdownMenuItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & { - inset?: boolean - } ->(({ className, inset, ...props }, ref) => ( - svg]:size-4 [&>svg]:shrink-0", - inset && "pl-8", - className - )} - {...props} - /> -)) -DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName - -const DropdownMenuCheckboxItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, checked, ...props }, ref) => ( - - - - - - - {children} - -)) -DropdownMenuCheckboxItem.displayName = - DropdownMenuPrimitive.CheckboxItem.displayName - -const DropdownMenuRadioItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - - - - - - - {children} - -)) -DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName - -const DropdownMenuLabel = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & { - inset?: boolean - } ->(({ className, inset, ...props }, ref) => ( - -)) -DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName - -const DropdownMenuSeparator = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName - -const DropdownMenuShortcut = ({ - className, - ...props -}: React.HTMLAttributes) => { - return ( - - ) -} -DropdownMenuShortcut.displayName = "DropdownMenuShortcut" - -export { - DropdownMenu, - DropdownMenuTrigger, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuCheckboxItem, - DropdownMenuRadioItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuShortcut, - DropdownMenuGroup, - DropdownMenuPortal, - DropdownMenuSub, - DropdownMenuSubContent, - DropdownMenuSubTrigger, - DropdownMenuRadioGroup, -} diff --git a/frontend/src/components/ui/input.tsx b/frontend/src/components/ui/input.tsx deleted file mode 100644 index 677d05f..0000000 --- a/frontend/src/components/ui/input.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import * as React from "react" - -import { cn } from "@/lib/utils" - -export interface InputProps - extends React.InputHTMLAttributes {} - -const Input = React.forwardRef( - ({ className, type, ...props }, ref) => { - return ( - - ) - } -) -Input.displayName = "Input" - -export { Input } diff --git a/frontend/src/components/ui/label.tsx b/frontend/src/components/ui/label.tsx deleted file mode 100644 index 683faa7..0000000 --- a/frontend/src/components/ui/label.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import * as React from "react" -import * as LabelPrimitive from "@radix-ui/react-label" -import { cva, type VariantProps } from "class-variance-authority" - -import { cn } from "@/lib/utils" - -const labelVariants = cva( - "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" -) - -const Label = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & - VariantProps ->(({ className, ...props }, ref) => ( - -)) -Label.displayName = LabelPrimitive.Root.displayName - -export { Label } diff --git a/frontend/src/components/ui/progress.tsx b/frontend/src/components/ui/progress.tsx deleted file mode 100644 index 105fb65..0000000 --- a/frontend/src/components/ui/progress.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import * as React from "react" -import * as ProgressPrimitive from "@radix-ui/react-progress" - -import { cn } from "@/lib/utils" - -const Progress = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, value, ...props }, ref) => ( - - - -)) -Progress.displayName = ProgressPrimitive.Root.displayName - -export { Progress } diff --git a/frontend/src/components/ui/scroll-area.tsx b/frontend/src/components/ui/scroll-area.tsx deleted file mode 100644 index cf253cf..0000000 --- a/frontend/src/components/ui/scroll-area.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import * as React from "react" -import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" - -import { cn } from "@/lib/utils" - -const ScrollArea = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - - - {children} - - - - -)) -ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName - -const ScrollBar = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, orientation = "vertical", ...props }, ref) => ( - - - -)) -ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName - -export { ScrollArea, ScrollBar } diff --git a/frontend/src/components/ui/separator.tsx b/frontend/src/components/ui/separator.tsx deleted file mode 100644 index 6d7f122..0000000 --- a/frontend/src/components/ui/separator.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import * as React from "react" -import * as SeparatorPrimitive from "@radix-ui/react-separator" - -import { cn } from "@/lib/utils" - -const Separator = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->( - ( - { className, orientation = "horizontal", decorative = true, ...props }, - ref - ) => ( - - ) -) -Separator.displayName = SeparatorPrimitive.Root.displayName - -export { Separator } diff --git a/frontend/src/components/ui/skeleton.tsx b/frontend/src/components/ui/skeleton.tsx deleted file mode 100644 index 01b8b6d..0000000 --- a/frontend/src/components/ui/skeleton.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { cn } from "@/lib/utils" - -function Skeleton({ - className, - ...props -}: React.HTMLAttributes) { - return ( -
- ) -} - -export { Skeleton } diff --git a/frontend/src/components/ui/tabs.tsx b/frontend/src/components/ui/tabs.tsx deleted file mode 100644 index 85d83be..0000000 --- a/frontend/src/components/ui/tabs.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import * as React from "react" -import * as TabsPrimitive from "@radix-ui/react-tabs" - -import { cn } from "@/lib/utils" - -const Tabs = TabsPrimitive.Root - -const TabsList = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -TabsList.displayName = TabsPrimitive.List.displayName - -const TabsTrigger = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -TabsTrigger.displayName = TabsPrimitive.Trigger.displayName - -const TabsContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -TabsContent.displayName = TabsPrimitive.Content.displayName - -export { Tabs, TabsList, TabsTrigger, TabsContent } diff --git a/frontend/src/components/ui/theme-provider.tsx b/frontend/src/components/ui/theme-provider.tsx deleted file mode 100644 index 7b9eeb2..0000000 --- a/frontend/src/components/ui/theme-provider.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { createContext, useContext, useEffect, useState } from "react"; - -type Theme = "dark" | "light" | "system"; - -type ThemeProviderProps = { - children: React.ReactNode; - defaultTheme?: Theme; - storageKey?: string; -}; - -type ThemeProviderState = { - theme: Theme; - setTheme: (theme: Theme) => void; -}; - -const initialState: ThemeProviderState = { - theme: "system", - setTheme: () => null, -}; - -const ThemeProviderContext = createContext(initialState); - -export function ThemeProvider({ - children, - defaultTheme = "system", - storageKey = "vite-ui-theme", - ...props -}: ThemeProviderProps) { - const [theme, setTheme] = useState( - () => (localStorage.getItem(storageKey) as Theme) || defaultTheme - ); - - useEffect(() => { - const root = window.document.documentElement; - - root.classList.remove("light", "dark"); - - if (theme === "system") { - const systemTheme = window.matchMedia("(prefers-color-scheme: dark)") - .matches - ? "dark" - : "light"; - - root.classList.add(systemTheme); - return; - } - - root.classList.add(theme); - }, [theme]); - - const value = { - theme, - setTheme: (theme: Theme) => { - localStorage.setItem(storageKey, theme); - setTheme(theme); - }, - }; - - return ( - - {children} - - ); -} - -export const useTheme = () => { - const context = useContext(ThemeProviderContext); - - if (context === undefined) - throw new Error("useTheme must be used within a ThemeProvider"); - - return context; -}; diff --git a/frontend/src/components/ui/toast.tsx b/frontend/src/components/ui/toast.tsx deleted file mode 100644 index a822477..0000000 --- a/frontend/src/components/ui/toast.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import * as React from "react" -import * as ToastPrimitives from "@radix-ui/react-toast" -import { cva, type VariantProps } from "class-variance-authority" -import { X } from "lucide-react" - -import { cn } from "@/lib/utils" - -const ToastProvider = ToastPrimitives.Provider - -const ToastViewport = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -ToastViewport.displayName = ToastPrimitives.Viewport.displayName - -const toastVariants = cva( - "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full", - { - variants: { - variant: { - default: "border bg-background text-foreground", - destructive: - "destructive group border-destructive bg-destructive text-destructive-foreground", - }, - }, - defaultVariants: { - variant: "default", - }, - } -) - -const Toast = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & - VariantProps ->(({ className, variant, ...props }, ref) => { - return ( - - ) -}) -Toast.displayName = ToastPrimitives.Root.displayName - -const ToastAction = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -ToastAction.displayName = ToastPrimitives.Action.displayName - -const ToastClose = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - - - -)) -ToastClose.displayName = ToastPrimitives.Close.displayName - -const ToastTitle = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -ToastTitle.displayName = ToastPrimitives.Title.displayName - -const ToastDescription = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -ToastDescription.displayName = ToastPrimitives.Description.displayName - -type ToastProps = React.ComponentPropsWithoutRef - -type ToastActionElement = React.ReactElement - -export { - type ToastProps, - type ToastActionElement, - ToastProvider, - ToastViewport, - Toast, - ToastTitle, - ToastDescription, - ToastClose, - ToastAction, -} diff --git a/frontend/src/components/ui/toaster.tsx b/frontend/src/components/ui/toaster.tsx deleted file mode 100644 index a2209ba..0000000 --- a/frontend/src/components/ui/toaster.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { - Toast, - ToastClose, - ToastDescription, - ToastProvider, - ToastTitle, - ToastViewport, -} from "@/components/ui/toast" -import { useToast } from "@/components/ui/use-toast" - -export function Toaster() { - const { toasts } = useToast() - - return ( - - {toasts.map(function ({ id, title, description, action, ...props }) { - return ( - -
- {title && {title}} - {description && ( - {description} - )} -
- {action} - -
- ) - })} - -
- ) -} diff --git a/frontend/src/components/ui/tooltip.tsx b/frontend/src/components/ui/tooltip.tsx deleted file mode 100644 index a9c71ba..0000000 --- a/frontend/src/components/ui/tooltip.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import * as React from "react" -import * as TooltipPrimitive from "@radix-ui/react-tooltip" - -import { cn } from "@/lib/utils" - -const TooltipProvider = TooltipPrimitive.Provider - -const Tooltip = TooltipPrimitive.Root - -const TooltipTrigger = TooltipPrimitive.Trigger - -const TooltipContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, sideOffset = 4, ...props }, ref) => ( - -)) -TooltipContent.displayName = TooltipPrimitive.Content.displayName - -export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } diff --git a/frontend/src/components/ui/use-toast.ts b/frontend/src/components/ui/use-toast.ts deleted file mode 100644 index 1671307..0000000 --- a/frontend/src/components/ui/use-toast.ts +++ /dev/null @@ -1,192 +0,0 @@ -// Inspired by react-hot-toast library -import * as React from "react" - -import type { - ToastActionElement, - ToastProps, -} from "@/components/ui/toast" - -const TOAST_LIMIT = 1 -const TOAST_REMOVE_DELAY = 1000000 - -type ToasterToast = ToastProps & { - id: string - title?: React.ReactNode - description?: React.ReactNode - action?: ToastActionElement -} - -const actionTypes = { - ADD_TOAST: "ADD_TOAST", - UPDATE_TOAST: "UPDATE_TOAST", - DISMISS_TOAST: "DISMISS_TOAST", - REMOVE_TOAST: "REMOVE_TOAST", -} as const - -let count = 0 - -function genId() { - count = (count + 1) % Number.MAX_SAFE_INTEGER - return count.toString() -} - -type ActionType = typeof actionTypes - -type Action = - | { - type: ActionType["ADD_TOAST"] - toast: ToasterToast - } - | { - type: ActionType["UPDATE_TOAST"] - toast: Partial - } - | { - type: ActionType["DISMISS_TOAST"] - toastId?: ToasterToast["id"] - } - | { - type: ActionType["REMOVE_TOAST"] - toastId?: ToasterToast["id"] - } - -interface State { - toasts: ToasterToast[] -} - -const toastTimeouts = new Map>() - -const addToRemoveQueue = (toastId: string) => { - if (toastTimeouts.has(toastId)) { - return - } - - const timeout = setTimeout(() => { - toastTimeouts.delete(toastId) - dispatch({ - type: "REMOVE_TOAST", - toastId: toastId, - }) - }, TOAST_REMOVE_DELAY) - - toastTimeouts.set(toastId, timeout) -} - -export const reducer = (state: State, action: Action): State => { - switch (action.type) { - case "ADD_TOAST": - return { - ...state, - toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), - } - - case "UPDATE_TOAST": - return { - ...state, - toasts: state.toasts.map((t) => - t.id === action.toast.id ? { ...t, ...action.toast } : t - ), - } - - case "DISMISS_TOAST": { - const { toastId } = action - - // ! Side effects ! - This could be extracted into a dismissToast() action, - // but I'll keep it here for simplicity - if (toastId) { - addToRemoveQueue(toastId) - } else { - state.toasts.forEach((toast) => { - addToRemoveQueue(toast.id) - }) - } - - return { - ...state, - toasts: state.toasts.map((t) => - t.id === toastId || toastId === undefined - ? { - ...t, - open: false, - } - : t - ), - } - } - case "REMOVE_TOAST": - if (action.toastId === undefined) { - return { - ...state, - toasts: [], - } - } - return { - ...state, - toasts: state.toasts.filter((t) => t.id !== action.toastId), - } - } -} - -const listeners: Array<(state: State) => void> = [] - -let memoryState: State = { toasts: [] } - -function dispatch(action: Action) { - memoryState = reducer(memoryState, action) - listeners.forEach((listener) => { - listener(memoryState) - }) -} - -type Toast = Omit - -function toast({ ...props }: Toast) { - const id = genId() - - const update = (props: ToasterToast) => - dispatch({ - type: "UPDATE_TOAST", - toast: { ...props, id }, - }) - const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }) - - dispatch({ - type: "ADD_TOAST", - toast: { - ...props, - id, - open: true, - onOpenChange: (open) => { - if (!open) dismiss() - }, - }, - }) - - return { - id: id, - dismiss, - update, - } -} - -function useToast() { - const [state, setState] = React.useState(memoryState) - - React.useEffect(() => { - listeners.push(setState) - return () => { - const index = listeners.indexOf(setState) - if (index > -1) { - listeners.splice(index, 1) - } - } - }, [state]) - - return { - ...state, - toast, - dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), - } -} - -export { useToast, toast } diff --git a/frontend/src/globals.css b/frontend/src/globals.css deleted file mode 100644 index 3a62e20..0000000 --- a/frontend/src/globals.css +++ /dev/null @@ -1,101 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; - -body { - font-family: "Geist Sans", sans-serif; -} - -@layer base { - :root { - --background: 0 0% 100%; - --foreground: 222.2 84% 4.9%; - - --card: 0 0% 100%; - --card-foreground: 222.2 84% 4.9%; - - --popover: 0 0% 100%; - --popover-foreground: 222.2 84% 4.9%; - - --primary: 222.2 47.4% 11.2%; - --primary-foreground: 210 40% 98%; - - --secondary: 210 40% 96.1%; - --secondary-foreground: 222.2 47.4% 11.2%; - - --muted: 210 40% 96.1%; - --muted-foreground: 215.4 16.3% 46.9%; - - --accent: 210 40% 96.1%; - --accent-foreground: 222.2 47.4% 11.2%; - - --destructive: 0 84.2% 60.2%; - --destructive-foreground: 210 40% 98%; - - --border: 214.3 31.8% 91.4%; - --input: 214.3 31.8% 91.4%; - --ring: 222.2 84% 4.9%; - - --radius: 0.5rem; - - --chart-1: 12 76% 61%; - --chart-2: 173 58% 39%; - --chart-3: 197 37% 24%; - --chart-4: 43 74% 66%; - --chart-5: 27 87% 67%; - } - - .dark { - --background: 240 14.3% 3.5%; - --foreground: 210 40% 98%; - - --card: 240 14.3% 5.5%; - --card-foreground: 210 40% 98%; - - --popover: 222.2 84% 4.9%; - --popover-foreground: 210 40% 98%; - - --primary: 210 40% 98%; - --primary-foreground: 222.2 47.4% 11.2%; - - --secondary: 217.2 32.6% 17.5%; - --secondary-foreground: 210 40% 98%; - - --muted: 217.2 32.6% 17.5%; - --muted-foreground: 215 20.2% 65.1%; - - --accent: 217.2 32.6% 17.5%; - --accent-foreground: 210 40% 98%; - - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 210 40% 98%; - - --border: 217.2 32.6% 17.5%; - --input: 217.2 32.6% 17.5%; - --ring: 212.7 26.8% 83.9%; - - --chart-1: 220 70% 50%; - --chart-2: 160 60% 45%; - --chart-3: 30 80% 55%; - --chart-4: 280 65% 60%; - --chart-5: 340 75% 55%; - } -} - -@layer base { - * { - @apply border-border; - } - body { - @apply bg-background text-foreground; - } -} - -html { - height: 100%; - overflow: hidden; -} - -.nodrag { - --wails-draggable: no-drag; -} diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts deleted file mode 100644 index ec79801..0000000 --- a/frontend/src/lib/utils.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { type ClassValue, clsx } from "clsx" -import { twMerge } from "tailwind-merge" - -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)) -} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx deleted file mode 100644 index 277011d..0000000 --- a/frontend/src/main.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react' -import {createRoot} from 'react-dom/client' -import './globals.css' -import App from './App' - -const container = document.getElementById('root') - -const root = createRoot(container!) - -root.render( - - - -) diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts deleted file mode 100644 index 11f02fe..0000000 --- a/frontend/src/vite-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/frontend/tailwind.config.cjs b/frontend/tailwind.config.cjs deleted file mode 100644 index 36a84d5..0000000 --- a/frontend/tailwind.config.cjs +++ /dev/null @@ -1,77 +0,0 @@ -/** @type {import('tailwindcss').Config} */ -module.exports = { - darkMode: ["class"], - content: [ - "./index.html", - './pages/**/*.{ts,tsx}', - './components/**/*.{ts,tsx}', - './app/**/*.{ts,tsx}', - './src/**/*.{ts,tsx}', - ], - theme: { - container: { - center: true, - padding: "2rem", - screens: { - "2xl": "1400px", - }, - }, - extend: { - colors: { - border: "hsl(var(--border))", - input: "hsl(var(--input))", - ring: "hsl(var(--ring))", - background: "hsl(var(--background))", - foreground: "hsl(var(--foreground))", - primary: { - DEFAULT: "hsl(var(--primary))", - foreground: "hsl(var(--primary-foreground))", - }, - secondary: { - DEFAULT: "hsl(var(--secondary))", - foreground: "hsl(var(--secondary-foreground))", - }, - destructive: { - DEFAULT: "hsl(var(--destructive))", - foreground: "hsl(var(--destructive-foreground))", - }, - muted: { - DEFAULT: "hsl(var(--muted))", - foreground: "hsl(var(--muted-foreground))", - }, - accent: { - DEFAULT: "hsl(var(--accent))", - foreground: "hsl(var(--accent-foreground))", - }, - popover: { - DEFAULT: "hsl(var(--popover))", - foreground: "hsl(var(--popover-foreground))", - }, - card: { - DEFAULT: "hsl(var(--card))", - foreground: "hsl(var(--card-foreground))", - }, - }, - borderRadius: { - lg: "var(--radius)", - md: "calc(var(--radius) - 2px)", - sm: "calc(var(--radius) - 4px)", - }, - keyframes: { - "accordion-down": { - from: { height: 0 }, - to: { height: "var(--radix-accordion-content-height)" }, - }, - "accordion-up": { - from: { height: "var(--radix-accordion-content-height)" }, - to: { height: 0 }, - }, - }, - animation: { - "accordion-down": "accordion-down 0.2s ease-out", - "accordion-up": "accordion-up 0.2s ease-out", - }, - }, - }, - plugins: [require("tailwindcss-animate")], -} \ No newline at end of file diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json deleted file mode 100644 index ffd60f8..0000000 --- a/frontend/tsconfig.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "compilerOptions": { - "baseUrl": ".", - "paths": { - "@/*": [ - "./src/*" - ] - }, - "target": "ESNext", - "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], - "allowJs": false, - "skipLibCheck": true, - "esModuleInterop": false, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "ESNext", - "moduleResolution": "Node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx" - }, - "include": [ - "src" - ], - "references": [ - { - "path": "./tsconfig.node.json" - } - ] -} diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json deleted file mode 100644 index b8afcc8..0000000 --- a/frontend/tsconfig.node.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "compilerOptions": { - "composite": true, - "module": "ESNext", - "moduleResolution": "Node", - "allowSyntheticDefaultImports": true - }, - "include": [ - "vite.config.ts" - ] -} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts deleted file mode 100644 index d36c010..0000000 --- a/frontend/vite.config.ts +++ /dev/null @@ -1,12 +0,0 @@ -import path from "path" -import react from "@vitejs/plugin-react" -import { defineConfig } from "vite" - -export default defineConfig({ - plugins: [react()], - resolve: { - alias: { - "@": path.resolve(__dirname, "./src"), - }, - }, -}) \ No newline at end of file diff --git a/frontend/wailsjs/go/main/App.d.ts b/frontend/wailsjs/go/main/App.d.ts deleted file mode 100755 index a58abf5..0000000 --- a/frontend/wailsjs/go/main/App.d.ts +++ /dev/null @@ -1,25 +0,0 @@ -// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL -// This file is automatically generated. DO NOT EDIT -import {main} from '../models'; - -export function CheckPackageInstalled(arg1:string):Promise; - -export function GetAvailableUpdates():Promise>; - -export function GetInstalledPackages():Promise>; - -export function GetMultiplePackageInfo(arg1:Array):Promise>; - -export function HumanReadableSize(arg1:number):Promise; - -export function Install(arg1:string):Promise; - -export function SearchLocalPackage(arg1:string):Promise; - -export function SearchPackage(arg1:string):Promise>; - -export function Uninstall(arg1:string):Promise; - -export function UpdateAllPkg():Promise; - -export function UpdateSinglePkg(arg1:string):Promise; diff --git a/frontend/wailsjs/go/main/App.js b/frontend/wailsjs/go/main/App.js deleted file mode 100755 index 65ab958..0000000 --- a/frontend/wailsjs/go/main/App.js +++ /dev/null @@ -1,47 +0,0 @@ -// @ts-check -// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL -// This file is automatically generated. DO NOT EDIT - -export function CheckPackageInstalled(arg1) { - return window['go']['main']['App']['CheckPackageInstalled'](arg1); -} - -export function GetAvailableUpdates() { - return window['go']['main']['App']['GetAvailableUpdates'](); -} - -export function GetInstalledPackages() { - return window['go']['main']['App']['GetInstalledPackages'](); -} - -export function GetMultiplePackageInfo(arg1) { - return window['go']['main']['App']['GetMultiplePackageInfo'](arg1); -} - -export function HumanReadableSize(arg1) { - return window['go']['main']['App']['HumanReadableSize'](arg1); -} - -export function Install(arg1) { - return window['go']['main']['App']['Install'](arg1); -} - -export function SearchLocalPackage(arg1) { - return window['go']['main']['App']['SearchLocalPackage'](arg1); -} - -export function SearchPackage(arg1) { - return window['go']['main']['App']['SearchPackage'](arg1); -} - -export function Uninstall(arg1) { - return window['go']['main']['App']['Uninstall'](arg1); -} - -export function UpdateAllPkg() { - return window['go']['main']['App']['UpdateAllPkg'](); -} - -export function UpdateSinglePkg(arg1) { - return window['go']['main']['App']['UpdateSinglePkg'](arg1); -} diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts deleted file mode 100755 index 4977036..0000000 --- a/frontend/wailsjs/go/models.ts +++ /dev/null @@ -1,51 +0,0 @@ -export namespace main { - - export class PackageInfo { - name: string; - version: string; - description: string; - repository: string; - maintainer: string; - upstreamurl: string; - dependlist: string[]; - lastupdated: string; - - static createFrom(source: any = {}) { - return new PackageInfo(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.name = source["name"]; - this.version = source["version"]; - this.description = source["description"]; - this.repository = source["repository"]; - this.maintainer = source["maintainer"]; - this.upstreamurl = source["upstreamurl"]; - this.dependlist = source["dependlist"]; - this.lastupdated = source["lastupdated"]; - } - } - export class UpdateInfo { - name: string; - oldVersion: string; - newVersion: string; - repository: string; - downloadSize: number; - - static createFrom(source: any = {}) { - return new UpdateInfo(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.name = source["name"]; - this.oldVersion = source["oldVersion"]; - this.newVersion = source["newVersion"]; - this.repository = source["repository"]; - this.downloadSize = source["downloadSize"]; - } - } - -} - diff --git a/frontend/wailsjs/runtime/package.json b/frontend/wailsjs/runtime/package.json deleted file mode 100644 index 1e7c8a5..0000000 --- a/frontend/wailsjs/runtime/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "@wailsapp/runtime", - "version": "2.0.0", - "description": "Wails Javascript runtime library", - "main": "runtime.js", - "types": "runtime.d.ts", - "scripts": { - }, - "repository": { - "type": "git", - "url": "git+https://github.com/wailsapp/wails.git" - }, - "keywords": [ - "Wails", - "Javascript", - "Go" - ], - "author": "Lea Anthony ", - "license": "MIT", - "bugs": { - "url": "https://github.com/wailsapp/wails/issues" - }, - "homepage": "https://github.com/wailsapp/wails#readme" -} diff --git a/frontend/wailsjs/runtime/runtime.d.ts b/frontend/wailsjs/runtime/runtime.d.ts deleted file mode 100644 index 94778df..0000000 --- a/frontend/wailsjs/runtime/runtime.d.ts +++ /dev/null @@ -1,249 +0,0 @@ -/* - _ __ _ __ -| | / /___ _(_) /____ -| | /| / / __ `/ / / ___/ -| |/ |/ / /_/ / / (__ ) -|__/|__/\__,_/_/_/____/ -The electron alternative for Go -(c) Lea Anthony 2019-present -*/ - -export interface Position { - x: number; - y: number; -} - -export interface Size { - w: number; - h: number; -} - -export interface Screen { - isCurrent: boolean; - isPrimary: boolean; - width : number - height : number -} - -// Environment information such as platform, buildtype, ... -export interface EnvironmentInfo { - buildType: string; - platform: string; - arch: string; -} - -// [EventsEmit](https://wails.io/docs/reference/runtime/events#eventsemit) -// emits the given event. Optional data may be passed with the event. -// This will trigger any event listeners. -export function EventsEmit(eventName: string, ...data: any): void; - -// [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name. -export function EventsOn(eventName: string, callback: (...data: any) => void): () => void; - -// [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple) -// sets up a listener for the given event name, but will only trigger a given number times. -export function EventsOnMultiple(eventName: string, callback: (...data: any) => void, maxCallbacks: number): () => void; - -// [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce) -// sets up a listener for the given event name, but will only trigger once. -export function EventsOnce(eventName: string, callback: (...data: any) => void): () => void; - -// [EventsOff](https://wails.io/docs/reference/runtime/events#eventsoff) -// unregisters the listener for the given event name. -export function EventsOff(eventName: string, ...additionalEventNames: string[]): void; - -// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall) -// unregisters all listeners. -export function EventsOffAll(): void; - -// [LogPrint](https://wails.io/docs/reference/runtime/log#logprint) -// logs the given message as a raw message -export function LogPrint(message: string): void; - -// [LogTrace](https://wails.io/docs/reference/runtime/log#logtrace) -// logs the given message at the `trace` log level. -export function LogTrace(message: string): void; - -// [LogDebug](https://wails.io/docs/reference/runtime/log#logdebug) -// logs the given message at the `debug` log level. -export function LogDebug(message: string): void; - -// [LogError](https://wails.io/docs/reference/runtime/log#logerror) -// logs the given message at the `error` log level. -export function LogError(message: string): void; - -// [LogFatal](https://wails.io/docs/reference/runtime/log#logfatal) -// logs the given message at the `fatal` log level. -// The application will quit after calling this method. -export function LogFatal(message: string): void; - -// [LogInfo](https://wails.io/docs/reference/runtime/log#loginfo) -// logs the given message at the `info` log level. -export function LogInfo(message: string): void; - -// [LogWarning](https://wails.io/docs/reference/runtime/log#logwarning) -// logs the given message at the `warning` log level. -export function LogWarning(message: string): void; - -// [WindowReload](https://wails.io/docs/reference/runtime/window#windowreload) -// Forces a reload by the main application as well as connected browsers. -export function WindowReload(): void; - -// [WindowReloadApp](https://wails.io/docs/reference/runtime/window#windowreloadapp) -// Reloads the application frontend. -export function WindowReloadApp(): void; - -// [WindowSetAlwaysOnTop](https://wails.io/docs/reference/runtime/window#windowsetalwaysontop) -// Sets the window AlwaysOnTop or not on top. -export function WindowSetAlwaysOnTop(b: boolean): void; - -// [WindowSetSystemDefaultTheme](https://wails.io/docs/next/reference/runtime/window#windowsetsystemdefaulttheme) -// *Windows only* -// Sets window theme to system default (dark/light). -export function WindowSetSystemDefaultTheme(): void; - -// [WindowSetLightTheme](https://wails.io/docs/next/reference/runtime/window#windowsetlighttheme) -// *Windows only* -// Sets window to light theme. -export function WindowSetLightTheme(): void; - -// [WindowSetDarkTheme](https://wails.io/docs/next/reference/runtime/window#windowsetdarktheme) -// *Windows only* -// Sets window to dark theme. -export function WindowSetDarkTheme(): void; - -// [WindowCenter](https://wails.io/docs/reference/runtime/window#windowcenter) -// Centers the window on the monitor the window is currently on. -export function WindowCenter(): void; - -// [WindowSetTitle](https://wails.io/docs/reference/runtime/window#windowsettitle) -// Sets the text in the window title bar. -export function WindowSetTitle(title: string): void; - -// [WindowFullscreen](https://wails.io/docs/reference/runtime/window#windowfullscreen) -// Makes the window full screen. -export function WindowFullscreen(): void; - -// [WindowUnfullscreen](https://wails.io/docs/reference/runtime/window#windowunfullscreen) -// Restores the previous window dimensions and position prior to full screen. -export function WindowUnfullscreen(): void; - -// [WindowIsFullscreen](https://wails.io/docs/reference/runtime/window#windowisfullscreen) -// Returns the state of the window, i.e. whether the window is in full screen mode or not. -export function WindowIsFullscreen(): Promise; - -// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize) -// Sets the width and height of the window. -export function WindowSetSize(width: number, height: number): Promise; - -// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize) -// Gets the width and height of the window. -export function WindowGetSize(): Promise; - -// [WindowSetMaxSize](https://wails.io/docs/reference/runtime/window#windowsetmaxsize) -// Sets the maximum window size. Will resize the window if the window is currently larger than the given dimensions. -// Setting a size of 0,0 will disable this constraint. -export function WindowSetMaxSize(width: number, height: number): void; - -// [WindowSetMinSize](https://wails.io/docs/reference/runtime/window#windowsetminsize) -// Sets the minimum window size. Will resize the window if the window is currently smaller than the given dimensions. -// Setting a size of 0,0 will disable this constraint. -export function WindowSetMinSize(width: number, height: number): void; - -// [WindowSetPosition](https://wails.io/docs/reference/runtime/window#windowsetposition) -// Sets the window position relative to the monitor the window is currently on. -export function WindowSetPosition(x: number, y: number): void; - -// [WindowGetPosition](https://wails.io/docs/reference/runtime/window#windowgetposition) -// Gets the window position relative to the monitor the window is currently on. -export function WindowGetPosition(): Promise; - -// [WindowHide](https://wails.io/docs/reference/runtime/window#windowhide) -// Hides the window. -export function WindowHide(): void; - -// [WindowShow](https://wails.io/docs/reference/runtime/window#windowshow) -// Shows the window, if it is currently hidden. -export function WindowShow(): void; - -// [WindowMaximise](https://wails.io/docs/reference/runtime/window#windowmaximise) -// Maximises the window to fill the screen. -export function WindowMaximise(): void; - -// [WindowToggleMaximise](https://wails.io/docs/reference/runtime/window#windowtogglemaximise) -// Toggles between Maximised and UnMaximised. -export function WindowToggleMaximise(): void; - -// [WindowUnmaximise](https://wails.io/docs/reference/runtime/window#windowunmaximise) -// Restores the window to the dimensions and position prior to maximising. -export function WindowUnmaximise(): void; - -// [WindowIsMaximised](https://wails.io/docs/reference/runtime/window#windowismaximised) -// Returns the state of the window, i.e. whether the window is maximised or not. -export function WindowIsMaximised(): Promise; - -// [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise) -// Minimises the window. -export function WindowMinimise(): void; - -// [WindowUnminimise](https://wails.io/docs/reference/runtime/window#windowunminimise) -// Restores the window to the dimensions and position prior to minimising. -export function WindowUnminimise(): void; - -// [WindowIsMinimised](https://wails.io/docs/reference/runtime/window#windowisminimised) -// Returns the state of the window, i.e. whether the window is minimised or not. -export function WindowIsMinimised(): Promise; - -// [WindowIsNormal](https://wails.io/docs/reference/runtime/window#windowisnormal) -// Returns the state of the window, i.e. whether the window is normal or not. -export function WindowIsNormal(): Promise; - -// [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour) -// Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels. -export function WindowSetBackgroundColour(R: number, G: number, B: number, A: number): void; - -// [ScreenGetAll](https://wails.io/docs/reference/runtime/window#screengetall) -// Gets the all screens. Call this anew each time you want to refresh data from the underlying windowing system. -export function ScreenGetAll(): Promise; - -// [BrowserOpenURL](https://wails.io/docs/reference/runtime/browser#browseropenurl) -// Opens the given URL in the system browser. -export function BrowserOpenURL(url: string): void; - -// [Environment](https://wails.io/docs/reference/runtime/intro#environment) -// Returns information about the environment -export function Environment(): Promise; - -// [Quit](https://wails.io/docs/reference/runtime/intro#quit) -// Quits the application. -export function Quit(): void; - -// [Hide](https://wails.io/docs/reference/runtime/intro#hide) -// Hides the application. -export function Hide(): void; - -// [Show](https://wails.io/docs/reference/runtime/intro#show) -// Shows the application. -export function Show(): void; - -// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext) -// Returns the current text stored on clipboard -export function ClipboardGetText(): Promise; - -// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext) -// Sets a text on the clipboard -export function ClipboardSetText(text: string): Promise; - -// [OnFileDrop](https://wails.io/docs/reference/runtime/draganddrop#onfiledrop) -// OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings. -export function OnFileDrop(callback: (x: number, y: number ,paths: string[]) => void, useDropTarget: boolean) :void - -// [OnFileDropOff](https://wails.io/docs/reference/runtime/draganddrop#dragandddropoff) -// OnFileDropOff removes the drag and drop listeners and handlers. -export function OnFileDropOff() :void - -// Check if the file path resolver is available -export function CanResolveFilePaths(): boolean; - -// Resolves file paths for an array of files -export function ResolveFilePaths(files: File[]): void \ No newline at end of file diff --git a/frontend/wailsjs/runtime/runtime.js b/frontend/wailsjs/runtime/runtime.js deleted file mode 100644 index 623397b..0000000 --- a/frontend/wailsjs/runtime/runtime.js +++ /dev/null @@ -1,238 +0,0 @@ -/* - _ __ _ __ -| | / /___ _(_) /____ -| | /| / / __ `/ / / ___/ -| |/ |/ / /_/ / / (__ ) -|__/|__/\__,_/_/_/____/ -The electron alternative for Go -(c) Lea Anthony 2019-present -*/ - -export function LogPrint(message) { - window.runtime.LogPrint(message); -} - -export function LogTrace(message) { - window.runtime.LogTrace(message); -} - -export function LogDebug(message) { - window.runtime.LogDebug(message); -} - -export function LogInfo(message) { - window.runtime.LogInfo(message); -} - -export function LogWarning(message) { - window.runtime.LogWarning(message); -} - -export function LogError(message) { - window.runtime.LogError(message); -} - -export function LogFatal(message) { - window.runtime.LogFatal(message); -} - -export function EventsOnMultiple(eventName, callback, maxCallbacks) { - return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks); -} - -export function EventsOn(eventName, callback) { - return EventsOnMultiple(eventName, callback, -1); -} - -export function EventsOff(eventName, ...additionalEventNames) { - return window.runtime.EventsOff(eventName, ...additionalEventNames); -} - -export function EventsOnce(eventName, callback) { - return EventsOnMultiple(eventName, callback, 1); -} - -export function EventsEmit(eventName) { - let args = [eventName].slice.call(arguments); - return window.runtime.EventsEmit.apply(null, args); -} - -export function WindowReload() { - window.runtime.WindowReload(); -} - -export function WindowReloadApp() { - window.runtime.WindowReloadApp(); -} - -export function WindowSetAlwaysOnTop(b) { - window.runtime.WindowSetAlwaysOnTop(b); -} - -export function WindowSetSystemDefaultTheme() { - window.runtime.WindowSetSystemDefaultTheme(); -} - -export function WindowSetLightTheme() { - window.runtime.WindowSetLightTheme(); -} - -export function WindowSetDarkTheme() { - window.runtime.WindowSetDarkTheme(); -} - -export function WindowCenter() { - window.runtime.WindowCenter(); -} - -export function WindowSetTitle(title) { - window.runtime.WindowSetTitle(title); -} - -export function WindowFullscreen() { - window.runtime.WindowFullscreen(); -} - -export function WindowUnfullscreen() { - window.runtime.WindowUnfullscreen(); -} - -export function WindowIsFullscreen() { - return window.runtime.WindowIsFullscreen(); -} - -export function WindowGetSize() { - return window.runtime.WindowGetSize(); -} - -export function WindowSetSize(width, height) { - window.runtime.WindowSetSize(width, height); -} - -export function WindowSetMaxSize(width, height) { - window.runtime.WindowSetMaxSize(width, height); -} - -export function WindowSetMinSize(width, height) { - window.runtime.WindowSetMinSize(width, height); -} - -export function WindowSetPosition(x, y) { - window.runtime.WindowSetPosition(x, y); -} - -export function WindowGetPosition() { - return window.runtime.WindowGetPosition(); -} - -export function WindowHide() { - window.runtime.WindowHide(); -} - -export function WindowShow() { - window.runtime.WindowShow(); -} - -export function WindowMaximise() { - window.runtime.WindowMaximise(); -} - -export function WindowToggleMaximise() { - window.runtime.WindowToggleMaximise(); -} - -export function WindowUnmaximise() { - window.runtime.WindowUnmaximise(); -} - -export function WindowIsMaximised() { - return window.runtime.WindowIsMaximised(); -} - -export function WindowMinimise() { - window.runtime.WindowMinimise(); -} - -export function WindowUnminimise() { - window.runtime.WindowUnminimise(); -} - -export function WindowSetBackgroundColour(R, G, B, A) { - window.runtime.WindowSetBackgroundColour(R, G, B, A); -} - -export function ScreenGetAll() { - return window.runtime.ScreenGetAll(); -} - -export function WindowIsMinimised() { - return window.runtime.WindowIsMinimised(); -} - -export function WindowIsNormal() { - return window.runtime.WindowIsNormal(); -} - -export function BrowserOpenURL(url) { - window.runtime.BrowserOpenURL(url); -} - -export function Environment() { - return window.runtime.Environment(); -} - -export function Quit() { - window.runtime.Quit(); -} - -export function Hide() { - window.runtime.Hide(); -} - -export function Show() { - window.runtime.Show(); -} - -export function ClipboardGetText() { - return window.runtime.ClipboardGetText(); -} - -export function ClipboardSetText(text) { - return window.runtime.ClipboardSetText(text); -} - -/** - * Callback for OnFileDrop returns a slice of file path strings when a drop is finished. - * - * @export - * @callback OnFileDropCallback - * @param {number} x - x coordinate of the drop - * @param {number} y - y coordinate of the drop - * @param {string[]} paths - A list of file paths. - */ - -/** - * OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings. - * - * @export - * @param {OnFileDropCallback} callback - Callback for OnFileDrop returns a slice of file path strings when a drop is finished. - * @param {boolean} [useDropTarget=true] - Only call the callback when the drop finished on an element that has the drop target style. (--wails-drop-target) - */ -export function OnFileDrop(callback, useDropTarget) { - return window.runtime.OnFileDrop(callback, useDropTarget); -} - -/** - * OnFileDropOff removes the drag and drop listeners and handlers. - */ -export function OnFileDropOff() { - return window.runtime.OnFileDropOff(); -} - -export function CanResolveFilePaths() { - return window.runtime.CanResolveFilePaths(); -} - -export function ResolveFilePaths(files) { - return window.runtime.ResolveFilePaths(files); -} \ No newline at end of file diff --git a/go.mod b/go.mod deleted file mode 100644 index 4f7a7fc..0000000 --- a/go.mod +++ /dev/null @@ -1,41 +0,0 @@ -module alg-app-store - -go 1.21 - -require ( - github.com/Jguer/go-alpm/v2 v2.2.2 - github.com/Morganamilo/go-pacmanconf v0.0.0-20210502114700-cff030e927a5 - github.com/wailsapp/wails/v2 v2.10.2 -) - -require ( - github.com/bep/debounce v1.2.1 // indirect - github.com/go-ole/go-ole v1.2.6 // indirect - github.com/godbus/dbus/v5 v5.1.0 // indirect - github.com/google/uuid v1.3.0 // indirect - github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect - github.com/labstack/echo/v4 v4.10.2 // indirect - github.com/labstack/gommon v0.4.0 // indirect - github.com/leaanthony/go-ansi-parser v1.6.0 // indirect - github.com/leaanthony/gosod v1.0.3 // indirect - github.com/leaanthony/slicer v1.6.0 // indirect - github.com/leaanthony/u v1.1.0 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect - github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/rivo/uniseg v0.4.4 // indirect - github.com/samber/lo v1.38.1 // indirect - github.com/tkrajina/go-reflector v0.5.6 // indirect - github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasttemplate v1.2.2 // indirect - github.com/wailsapp/go-webview2 v1.0.10 // indirect - github.com/wailsapp/mimetype v1.4.1 // indirect - golang.org/x/crypto v0.23.0 // indirect - golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect - golang.org/x/net v0.25.0 // indirect - golang.org/x/sys v0.20.0 // indirect - golang.org/x/text v0.15.0 // indirect -) - -// replace github.com/wailsapp/wails/v2 v2.8.2 => /home/harsh/go/pkg/mod diff --git a/go.sum b/go.sum deleted file mode 100644 index 56f3f23..0000000 --- a/go.sum +++ /dev/null @@ -1,98 +0,0 @@ -github.com/Jguer/go-alpm/v2 v2.2.2 h1:sPwUoZp1X5Tw6K6Ba1lWvVJfcgVNEGVcxARLBttZnC0= -github.com/Jguer/go-alpm/v2 v2.2.2/go.mod h1:lfe8gSe83F/KERaQvEfrSqQ4n+8bES+ZIyKWR/gm3MI= -github.com/Morganamilo/go-pacmanconf v0.0.0-20210502114700-cff030e927a5 h1:TMscPjkb1ThXN32LuFY5bEYIcXZx3YlwzhS1GxNpn/c= -github.com/Morganamilo/go-pacmanconf v0.0.0-20210502114700-cff030e927a5/go.mod h1:Hk55m330jNiwxRodIlMCvw5iEyoRUCIY64W1p9D+tHc= -github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= -github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= -github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= -github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck= -github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= -github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M= -github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k= -github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= -github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= -github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc= -github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA= -github.com/leaanthony/go-ansi-parser v1.6.0 h1:T8TuMhFB6TUMIUm0oRrSbgJudTFw9csT3ZK09w0t4Pg= -github.com/leaanthony/go-ansi-parser v1.6.0/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= -github.com/leaanthony/gosod v1.0.3 h1:Fnt+/B6NjQOVuCWOKYRREZnjGyvg+mEhd1nkkA04aTQ= -github.com/leaanthony/gosod v1.0.3/go.mod h1:BJ2J+oHsQIyIQpnLPjnqFGTMnOZXDbvWtRCSG7jGxs4= -github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY= -github.com/leaanthony/slicer v1.6.0 h1:1RFP5uiPJvT93TAHi+ipd3NACobkW53yUiBqZheE/Js= -github.com/leaanthony/slicer v1.6.0/go.mod h1:o/Iz29g7LN0GqH3aMjWAe90381nyZlDNquK+mtH2Fj8= -github.com/leaanthony/u v1.1.0 h1:2n0d2BwPVXSUq5yhe8lJPHdxevE2qK5G99PMStMZMaI= -github.com/leaanthony/u v1.1.0/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= -github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= -github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= -github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= -github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= -github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= -github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/tkrajina/go-reflector v0.5.6 h1:hKQ0gyocG7vgMD2M3dRlYN6WBBOmdoOzJ6njQSepKdE= -github.com/tkrajina/go-reflector v0.5.6/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4= -github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= -github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/wailsapp/go-webview2 v1.0.10 h1:PP5Hug6pnQEAhfRzLCoOh2jJaPdrqeRgJKZhyYyDV/w= -github.com/wailsapp/go-webview2 v1.0.10/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo= -github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= -github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= -github.com/wailsapp/wails/v2 v2.9.1 h1:irsXnoQrCpeKzKTYZ2SUVlRRyeMR6I0vCO9Q1cvlEdc= -github.com/wailsapp/wails/v2 v2.9.1/go.mod h1:7maJV2h+Egl11Ak8QZN/jlGLj2wg05bsQS+ywJPT0gI= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= -golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= -golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go deleted file mode 100644 index 09f1ec9..0000000 --- a/main.go +++ /dev/null @@ -1,96 +0,0 @@ -package main - -import ( - "embed" - "log" - - "github.com/wailsapp/wails/v2" - "github.com/wailsapp/wails/v2/pkg/logger" - "github.com/wailsapp/wails/v2/pkg/options" - "github.com/wailsapp/wails/v2/pkg/options/assetserver" - "github.com/wailsapp/wails/v2/pkg/options/linux" - "github.com/wailsapp/wails/v2/pkg/options/mac" - "github.com/wailsapp/wails/v2/pkg/options/windows" -) - -//go:embed all:frontend/dist -var assets embed.FS - -//go:embed frontend/src/assets/icon/appicon.png -var icon []byte - -func main() { - // Create an instance of the app structure - app := NewApp() - - // Create application with options - err := wails.Run(&options.App{ - Title: "ALG App Store (Beta)", - Width: 1124, - Height: 868, - MinWidth: 1024, - MinHeight: 768, - MaxWidth: 0, - MaxHeight: 0, - DisableResize: false, - Fullscreen: true, - Frameless: false, - StartHidden: false, - HideWindowOnClose: false, - BackgroundColour: &options.RGBA{R: 255, G: 255, B: 255, A: 255}, - CSSDragProperty: "none", - AssetServer: &assetserver.Options{ - Assets: assets, - }, - Menu: nil, - Logger: nil, - LogLevel: logger.DEBUG, - OnStartup: app.startup, - OnDomReady: app.domReady, - OnBeforeClose: app.beforeClose, - OnShutdown: app.shutdown, - WindowStartState: options.Maximised, - Bind: []interface{}{ - app, - }, - Debug: options.Debug{ - OpenInspectorOnStartup: true, - }, - // Windows platform specific options - Windows: &windows.Options{ - WebviewIsTransparent: false, - WindowIsTranslucent: false, - DisableWindowIcon: false, - // DisableFramelessWindowDecorations: false, - WebviewUserDataPath: "", - ZoomFactor: 1.0, - }, - // Mac platform specific options - Mac: &mac.Options{ - TitleBar: &mac.TitleBar{ - TitlebarAppearsTransparent: true, - HideTitle: false, - HideTitleBar: false, - FullSizeContent: false, - UseToolbar: false, - HideToolbarSeparator: true, - }, - Appearance: mac.NSAppearanceNameDarkAqua, - WebviewIsTransparent: true, - WindowIsTranslucent: true, - About: &mac.AboutInfo{ - Title: "alg-app-store", - Message: "", - Icon: icon, - }, - }, - Linux: &linux.Options{ - Icon: icon, - WebviewGpuPolicy: linux.WebviewGpuPolicyOnDemand, - }, - }) - - if err != nil { - log.Fatal(err) - } -} diff --git a/scripts/build-macos-arm.sh b/scripts/build-macos-arm.sh deleted file mode 100644 index bc6ee0a..0000000 --- a/scripts/build-macos-arm.sh +++ /dev/null @@ -1,9 +0,0 @@ -#! /bin/bash - -echo -e "Start running the script..." -cd ../ - -echo -e "Start building the app for macos platform..." -wails build --clean --platform darwin/arm64 - -echo -e "End running the script!" diff --git a/scripts/build-macos-intel.sh b/scripts/build-macos-intel.sh deleted file mode 100644 index f359f63..0000000 --- a/scripts/build-macos-intel.sh +++ /dev/null @@ -1,9 +0,0 @@ -#! /bin/bash - -echo -e "Start running the script..." -cd ../ - -echo -e "Start building the app for macos platform..." -wails build --clean --platform darwin - -echo -e "End running the script!" diff --git a/scripts/build-macos.sh b/scripts/build-macos.sh deleted file mode 100644 index d61531f..0000000 --- a/scripts/build-macos.sh +++ /dev/null @@ -1,9 +0,0 @@ -#! /bin/bash - -echo -e "Start running the script..." -cd ../ - -echo -e "Start building the app for macos platform..." -wails build --clean --platform darwin/universal - -echo -e "End running the script!" diff --git a/scripts/build-windows.sh b/scripts/build-windows.sh deleted file mode 100644 index 47b7789..0000000 --- a/scripts/build-windows.sh +++ /dev/null @@ -1,9 +0,0 @@ -#! /bin/bash - -echo -e "Start running the script..." -cd ../ - -echo -e "Start building the app for windows platform..." -wails build --clean --platform windows/amd64 - -echo -e "End running the script!" diff --git a/scripts/build.sh b/scripts/build.sh deleted file mode 100644 index 20ab7eb..0000000 --- a/scripts/build.sh +++ /dev/null @@ -1,9 +0,0 @@ -#! /bin/bash - -echo -e "Start running the script..." -cd ../ - -echo -e "Start building the app..." -wails build --clean - -echo -e "End running the script!" diff --git a/scripts/install-wails-cli.sh b/scripts/install-wails-cli.sh deleted file mode 100644 index 7539d8e..0000000 --- a/scripts/install-wails-cli.sh +++ /dev/null @@ -1,14 +0,0 @@ -#! /bin/bash - -echo -e "Start running the script..." -cd ../ - -echo -e "Current Go version: \c" -go version - -echo -e "Install the Wails command line tool..." -go install github.com/wailsapp/wails/v2/cmd/wails@latest - -echo -e "Successful installation!" - -echo -e "End running the script!" diff --git a/src/core/alpm_wrapper.cpp b/src/core/alpm_wrapper.cpp new file mode 100644 index 0000000..3fc92b7 --- /dev/null +++ b/src/core/alpm_wrapper.cpp @@ -0,0 +1,348 @@ +#include "alpm_wrapper.h" +#include "../utils/logger.h" +#include +#include +#include +#include + +AlpmWrapper& AlpmWrapper::instance() { + static AlpmWrapper instance; + return instance; +} + +AlpmWrapper::AlpmWrapper() { + // Member initialization is done in header file +} + +AlpmWrapper::~AlpmWrapper() { + release(); +} + +bool AlpmWrapper::initialize() { + std::lock_guard lock(m_mutex); + + if (m_initialized) { + return true; + } + + alpm_errno_t err; + m_handle = alpm_initialize("/", "/var/lib/pacman", &err); + + if (!m_handle) { + Logger::error(QString("Failed to initialize ALPM: %1") + .arg(alpm_strerror(err))); + return false; + } + + // Register sync databases - read from enabled repositories + QStringList repos = getEnabledRepositories(); + for (const auto& repo : repos) { + alpm_db_t* db = alpm_register_syncdb(m_handle, + repo.toStdString().c_str(), + ALPM_SIG_USE_DEFAULT); + if (!db) { + Logger::warning(QString("Failed to register sync db: %1").arg(repo)); + } else { + Logger::info(QString("Registered sync db: %1").arg(repo)); + } + } + + m_syncDbs = alpm_get_syncdbs(m_handle); + m_initialized = true; + + Logger::info("ALPM initialized successfully"); + return true; +} + +void AlpmWrapper::release() { + std::lock_guard lock(m_mutex); + + if (m_handle) { + alpm_release(m_handle); + m_handle = nullptr; + m_syncDbs = nullptr; + m_initialized = false; + Logger::info("ALPM released"); + } +} + +QVector AlpmWrapper::searchPackages(const QString& query) { + std::lock_guard lock(m_mutex); + + if (!m_initialized) { + Logger::error("ALPM not initialized"); + return {}; + } + + QVector results; + + // Search in sync databases + for (alpm_list_t* i = m_syncDbs; i; i = i->next) { + auto* db = static_cast(i->data); + searchInDatabase(db, query, results); + } + + return results; +} + +void AlpmWrapper::searchInDatabase(alpm_db_t* db, const QString& query, + QVector& results) { + if (!db) return; + + alpm_list_t* pkgs = alpm_db_get_pkgcache(db); + QString lowerQuery = query.toLower(); + + for (alpm_list_t* i = pkgs; i; i = i->next) { + auto* pkg = static_cast(i->data); + QString pkgName = QString::fromUtf8(alpm_pkg_get_name(pkg)); + + if (pkgName.toLower().contains(lowerQuery)) { + PackageInfo info; + info.name = pkgName; + info.version = QString::fromUtf8(alpm_pkg_get_version(pkg)); + info.description = QString::fromUtf8(alpm_pkg_get_desc(pkg)); + info.repository = QString::fromUtf8(alpm_db_get_name(db)); + info.maintainer = QString::fromUtf8(alpm_pkg_get_packager(pkg)); + info.upstreamUrl = QString::fromUtf8(alpm_pkg_get_url(pkg)); + info.dependList = convertDependList(alpm_pkg_get_depends(pkg)); + + alpm_time_t buildDate = alpm_pkg_get_builddate(pkg); + info.lastUpdated = QDateTime::fromSecsSinceEpoch(buildDate); + + results.push_back(std::move(info)); + } + } +} + +QVector AlpmWrapper::getInstalledPackages() { + std::lock_guard lock(m_mutex); + + if (!m_initialized) { + Logger::error("ALPM not initialized"); + return {}; + } + + QVector packages; + alpm_db_t* localDb = alpm_get_localdb(m_handle); + + if (!localDb) { + Logger::error("Failed to get local database"); + return {}; + } + + alpm_list_t* pkgs = alpm_db_get_pkgcache(localDb); + + for (alpm_list_t* i = pkgs; i; i = i->next) { + auto* pkg = static_cast(i->data); + + PackageInfo info; + info.name = QString::fromUtf8(alpm_pkg_get_name(pkg)); + info.version = QString::fromUtf8(alpm_pkg_get_version(pkg)); + info.description = QString::fromUtf8(alpm_pkg_get_desc(pkg)); + info.repository = QString::fromUtf8(alpm_db_get_name(alpm_pkg_get_db(pkg))); + info.maintainer = QString::fromUtf8(alpm_pkg_get_packager(pkg)); + info.upstreamUrl = QString::fromUtf8(alpm_pkg_get_url(pkg)); + info.dependList = convertDependList(alpm_pkg_get_depends(pkg)); + + alpm_time_t buildDate = alpm_pkg_get_builddate(pkg); + info.lastUpdated = QDateTime::fromSecsSinceEpoch(buildDate); + + packages.push_back(std::move(info)); + } + + Logger::info(QString("Found %1 installed packages").arg(packages.size())); + return packages; +} + +bool AlpmWrapper::isPackageInstalled(const QString& packageName) { + std::lock_guard lock(m_mutex); + + if (!m_initialized) { + return false; + } + + alpm_db_t* localDb = alpm_get_localdb(m_handle); + if (!localDb) { + return false; + } + + alpm_pkg_t* pkg = alpm_db_get_pkg(localDb, packageName.toStdString().c_str()); + return pkg != nullptr; +} + +PackageInfo AlpmWrapper::getPackageInfo(const QString& packageName) { + std::lock_guard lock(m_mutex); + + PackageInfo info; + if (!m_initialized) { + return info; + } + + // First check local database + alpm_db_t* localDb = alpm_get_localdb(m_handle); + if (localDb) { + alpm_pkg_t* pkg = alpm_db_get_pkg(localDb, packageName.toStdString().c_str()); + if (pkg) { + info.name = QString::fromUtf8(alpm_pkg_get_name(pkg)); + info.version = QString::fromUtf8(alpm_pkg_get_version(pkg)); + info.description = QString::fromUtf8(alpm_pkg_get_desc(pkg)); + info.repository = QString::fromUtf8(alpm_db_get_name(alpm_pkg_get_db(pkg))); + info.maintainer = QString::fromUtf8(alpm_pkg_get_packager(pkg)); + info.upstreamUrl = QString::fromUtf8(alpm_pkg_get_url(pkg)); + info.dependList = convertDependList(alpm_pkg_get_depends(pkg)); + + alpm_time_t buildDate = alpm_pkg_get_builddate(pkg); + info.lastUpdated = QDateTime::fromSecsSinceEpoch(buildDate); + + return info; + } + } + + // Check sync databases + for (alpm_list_t* i = m_syncDbs; i; i = i->next) { + auto* db = static_cast(i->data); + alpm_pkg_t* pkg = alpm_db_get_pkg(db, packageName.toStdString().c_str()); + + if (pkg) { + info.name = QString::fromUtf8(alpm_pkg_get_name(pkg)); + info.version = QString::fromUtf8(alpm_pkg_get_version(pkg)); + info.description = QString::fromUtf8(alpm_pkg_get_desc(pkg)); + info.repository = QString::fromUtf8(alpm_db_get_name(db)); + info.maintainer = QString::fromUtf8(alpm_pkg_get_packager(pkg)); + info.upstreamUrl = QString::fromUtf8(alpm_pkg_get_url(pkg)); + info.dependList = convertDependList(alpm_pkg_get_depends(pkg)); + + alpm_time_t buildDate = alpm_pkg_get_builddate(pkg); + info.lastUpdated = QDateTime::fromSecsSinceEpoch(buildDate); + + return info; + } + } + + return info; +} + +QVector AlpmWrapper::getAvailableUpdates() { + std::lock_guard lock(m_mutex); + + QVector updates; + + if (!m_initialized) { + Logger::error("ALPM not initialized"); + return updates; + } + + alpm_db_t* localDb = alpm_get_localdb(m_handle); + if (!localDb) { + return updates; + } + + alpm_list_t* pkgs = alpm_db_get_pkgcache(localDb); + + for (alpm_list_t* i = pkgs; i; i = i->next) { + auto* localPkg = static_cast(i->data); + const char* pkgName = alpm_pkg_get_name(localPkg); + + // Check each sync database for newer version + for (alpm_list_t* j = m_syncDbs; j; j = j->next) { + auto* syncDb = static_cast(j->data); + alpm_pkg_t* syncPkg = alpm_db_get_pkg(syncDb, pkgName); + + if (syncPkg) { + int cmp = alpm_pkg_vercmp(alpm_pkg_get_version(syncPkg), + alpm_pkg_get_version(localPkg)); + + if (cmp > 0) { + UpdateInfo update; + update.name = QString::fromUtf8(pkgName); + update.oldVersion = QString::fromUtf8(alpm_pkg_get_version(localPkg)); + update.newVersion = QString::fromUtf8(alpm_pkg_get_version(syncPkg)); + update.repository = QString::fromUtf8(alpm_db_get_name(syncDb)); + update.downloadSize = alpm_pkg_get_size(syncPkg); + + updates.push_back(std::move(update)); + break; + } + } + } + } + + Logger::info(QString("Found %1 available updates").arg(updates.size())); + return updates; +} + +QStringList AlpmWrapper::convertDependList(alpm_list_t* deps) { + QStringList result; + + for (alpm_list_t* i = deps; i; i = i->next) { + auto* dep = static_cast(i->data); + result.append(QString::fromUtf8(dep->name)); + } + + return result; +} + +QStringList AlpmWrapper::getEnabledRepositories() const { + QStringList repos; + + // Read /etc/pacman.conf to find enabled repositories + QFile file("/etc/pacman.conf"); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + Logger::error("Failed to open /etc/pacman.conf"); + // Return default repositories + return {"core", "extra"}; + } + + QTextStream in(&file); + while (!in.atEnd()) { + QString line = in.readLine().trimmed(); + + // Check for repository sections (not commented out) + if (line.startsWith("[") && line.endsWith("]") && !line.startsWith("#")) { + QString repo = line.mid(1, line.length() - 2); + + // Filter out non-repository sections + if (repo != "options" && repo != "testing" && repo != "core-testing" && + repo != "extra-testing" && repo != "multilib-testing") { + repos.append(repo); + } + } + } + + file.close(); + + // Ensure core and extra are always present + if (!repos.contains("core")) { + repos.prepend("core"); + } + if (!repos.contains("extra")) { + repos.insert(1, "extra"); + } + + Logger::info(QString("Enabled repositories: %1").arg(repos.join(", "))); + return repos; +} + +void AlpmWrapper::refreshDatabases() { + std::lock_guard lock(m_mutex); + + if (!m_initialized) { + Logger::error("ALPM not initialized"); + return; + } + + // Release current handle + if (m_handle) { + alpm_release(m_handle); + m_handle = nullptr; + m_syncDbs = nullptr; + m_initialized = false; + } + + // Re-initialize to pick up new repositories + m_mutex.unlock(); + initialize(); + m_mutex.lock(); + + Logger::info("ALPM databases refreshed"); +} diff --git a/src/core/alpm_wrapper.h b/src/core/alpm_wrapper.h new file mode 100644 index 0000000..b1f8906 --- /dev/null +++ b/src/core/alpm_wrapper.h @@ -0,0 +1,59 @@ +#ifndef ALPM_WRAPPER_H +#define ALPM_WRAPPER_H + +#include +#include +#include +#include +#include +#include +#include "../utils/types.h" + +/** + * @brief Singleton wrapper for libalpm (Arch Linux Package Manager library). + * + * Memory Management: + * - m_handle: Raw pointer to libalpm handle, manually managed via initialize()/release() + * - m_syncDbs: Raw pointer to libalpm list, managed by libalpm internally + * - Thread-safe via m_mutex + * + * Note: libalpm uses C-style memory management, so smart pointers are not + * directly applicable to the alpm types. + */ +class AlpmWrapper { +public: + static AlpmWrapper& instance(); + + ~AlpmWrapper(); + + // Disable copy and move + AlpmWrapper(const AlpmWrapper&) = delete; + AlpmWrapper& operator=(const AlpmWrapper&) = delete; + AlpmWrapper(AlpmWrapper&&) = delete; + AlpmWrapper& operator=(AlpmWrapper&&) = delete; + + bool initialize(); + void release(); + void refreshDatabases(); + + QVector searchPackages(const QString& query); + QVector getInstalledPackages(); + bool isPackageInstalled(const QString& packageName); + PackageInfo getPackageInfo(const QString& packageName); + QVector getAvailableUpdates(); + +private: + AlpmWrapper(); + + alpm_handle_t* m_handle = nullptr; + alpm_list_t* m_syncDbs = nullptr; + std::mutex m_mutex; + bool m_initialized = false; + + QStringList convertDependList(alpm_list_t* deps); + void searchInDatabase(alpm_db_t* db, const QString& query, + QVector& results); + QStringList getEnabledRepositories() const; +}; + +#endif // ALPM_WRAPPER_H diff --git a/src/core/aur_helper.cpp b/src/core/aur_helper.cpp new file mode 100644 index 0000000..bdce2a3 --- /dev/null +++ b/src/core/aur_helper.cpp @@ -0,0 +1,206 @@ +#include "aur_helper.h" +#include "../utils/logger.h" +#include +#include +#include +#include +#include +#include +#include +#include + +AurHelper::AurHelper(QObject* parent) + : QObject(parent) + , m_networkManager(std::make_unique(this)) { +} + +AurHelper::~AurHelper() = default; + +void AurHelper::searchPackages(const QString& query) { + Logger::debug(QString("Searching AUR for: %1").arg(query)); + + QUrl url("https://aur.archlinux.org/rpc/"); + QUrlQuery urlQuery; + urlQuery.addQueryItem("v", "5"); + urlQuery.addQueryItem("type", "search"); + urlQuery.addQueryItem("arg", query); + url.setQuery(urlQuery); + + QNetworkRequest request(url); + auto* reply = m_networkManager->get(request); + + connect(reply, &QNetworkReply::finished, this, &AurHelper::onSearchFinished); +} + +void AurHelper::onSearchFinished() { + auto* reply = qobject_cast(sender()); + if (!reply) return; + + reply->deleteLater(); + + if (reply->error() != QNetworkReply::NoError) { + Logger::error(QString("AUR search error: %1").arg(reply->errorString())); + emit error(reply->errorString()); + return; + } + + QByteArray data = reply->readAll(); + QJsonDocument doc = QJsonDocument::fromJson(data); + + if (!doc.isObject()) { + Logger::error("Invalid AUR response format"); + emit error("Invalid response from AUR"); + return; + } + + QJsonObject root = doc.object(); + QJsonArray results = root["results"].toArray(); + + QVector packages; + for (const auto& result : results) { + packages.push_back(parseAurPackage(result.toObject())); + } + + Logger::info(QString("Found %1 AUR packages").arg(packages.size())); + emit searchCompleted(packages); +} + +void AurHelper::getPackageInfo(const QString& packageName) { + Logger::debug(QString("Getting AUR package info for: %1").arg(packageName)); + + QUrl url("https://aur.archlinux.org/rpc/"); + QUrlQuery urlQuery; + urlQuery.addQueryItem("v", "5"); + urlQuery.addQueryItem("type", "info"); + urlQuery.addQueryItem("arg", packageName); + url.setQuery(urlQuery); + + QNetworkRequest request(url); + auto* reply = m_networkManager->get(request); + + connect(reply, &QNetworkReply::finished, this, &AurHelper::onPackageInfoFinished); +} + +void AurHelper::onPackageInfoFinished() { + auto* reply = qobject_cast(sender()); + if (!reply) return; + + reply->deleteLater(); + + if (reply->error() != QNetworkReply::NoError) { + Logger::error(QString("AUR package info error: %1").arg(reply->errorString())); + emit error(reply->errorString()); + return; + } + + QByteArray data = reply->readAll(); + QJsonDocument doc = QJsonDocument::fromJson(data); + + if (!doc.isObject()) { + emit error("Invalid response from AUR"); + return; + } + + QJsonObject root = doc.object(); + QJsonArray results = root["results"].toArray(); + + if (results.isEmpty()) { + emit error("Package not found in AUR"); + return; + } + + PackageInfo info = parseAurPackage(results[0].toObject()); + emit packageInfoReceived(info); +} + +PackageInfo AurHelper::parseAurPackage(const QJsonObject& obj) { + PackageInfo info; + info.name = obj["Name"].toString(); + info.version = obj["Version"].toString(); + info.description = obj["Description"].toString(); + info.repository = "AUR"; + info.maintainer = obj["Maintainer"].toString(); + info.upstreamUrl = obj["URL"].toString(); + + qint64 lastModified = obj["LastModified"].toInteger(); + info.lastUpdated = QDateTime::fromSecsSinceEpoch(lastModified); + + // Parse dependencies + QJsonArray depends = obj["Depends"].toArray(); + for (const auto& dep : depends) { + info.dependList.append(dep.toString()); + } + + // Also add make dependencies if available + QJsonArray makeDepends = obj["MakeDepends"].toArray(); + for (const auto& dep : makeDepends) { + QString depStr = dep.toString() + " (make)"; + info.dependList.append(depStr); + } + + return info; +} + +QVector AurHelper::checkAurUpdates() { + QVector updates; + + // Get list of foreign (AUR) packages + QProcess process; + process.start("pacman", QStringList() << "-Qm"); + process.waitForFinished(); + + if (process.exitCode() != 0) { + Logger::warning("Failed to get list of foreign packages"); + return updates; + } + + QString output = process.readAllStandardOutput(); + QStringList lines = output.split('\n', Qt::SkipEmptyParts); + + for (const auto& line : lines) { + QStringList parts = line.split(' ', Qt::SkipEmptyParts); + if (parts.size() < 2) continue; + + QString name = parts[0]; + QString version = parts[1]; + + // Query AUR for latest version + QUrl url("https://aur.archlinux.org/rpc/"); + QUrlQuery urlQuery; + urlQuery.addQueryItem("v", "5"); + urlQuery.addQueryItem("type", "info"); + urlQuery.addQueryItem("arg", name); + url.setQuery(urlQuery); + + QNetworkRequest request(url); + auto reply = m_networkManager->get(request); + + QEventLoop loop; + connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); + loop.exec(); + + if (reply->error() == QNetworkReply::NoError) { + QByteArray data = reply->readAll(); + QJsonDocument doc = QJsonDocument::fromJson(data); + QJsonObject root = doc.object(); + QJsonArray results = root["results"].toArray(); + + if (!results.isEmpty()) { + QString newVersion = results[0].toObject()["Version"].toString(); + if (newVersion != version) { + UpdateInfo update; + update.name = name; + update.oldVersion = version; + update.newVersion = newVersion; + update.repository = "AUR"; + update.downloadSize = 0; + updates.push_back(std::move(update)); + } + } + } + + reply->deleteLater(); + } + + return updates; +} diff --git a/src/core/aur_helper.h b/src/core/aur_helper.h new file mode 100644 index 0000000..e29321f --- /dev/null +++ b/src/core/aur_helper.h @@ -0,0 +1,45 @@ +#ifndef AUR_HELPER_H +#define AUR_HELPER_H + +#include +#include +#include +#include +#include +#include +#include "../utils/types.h" + +/** + * @brief Helper class for interacting with the Arch User Repository (AUR). + * + * Memory Management: + * - m_networkManager: Owned by std::unique_ptr for RAII-style cleanup + * - Network replies are managed via Qt parent-child and deleteLater() + */ +class AurHelper : public QObject { + Q_OBJECT + +public: + explicit AurHelper(QObject* parent = nullptr); + ~AurHelper() override; + + void searchPackages(const QString& query); + void getPackageInfo(const QString& packageName); + QVector checkAurUpdates(); + +signals: + void searchCompleted(const QVector& results); + void packageInfoReceived(const PackageInfo& info); + void error(const QString& message); + +private slots: + void onSearchFinished(); + void onPackageInfoFinished(); + +private: + std::unique_ptr m_networkManager; + + PackageInfo parseAurPackage(const QJsonObject& obj); +}; + +#endif // AUR_HELPER_H diff --git a/src/core/package_manager.cpp b/src/core/package_manager.cpp new file mode 100644 index 0000000..5b50364 --- /dev/null +++ b/src/core/package_manager.cpp @@ -0,0 +1,226 @@ +#include "package_manager.h" +#include "../utils/logger.h" +#include +#include + +PackageManager& PackageManager::instance() { + static PackageManager instance; + return instance; +} + +PackageManager::PackageManager() + : QObject(nullptr) + , m_process(std::make_unique()) { + + detectHelper(); + + connect(m_process.get(), &QProcess::finished, + this, &PackageManager::onProcessFinished); + connect(m_process.get(), &QProcess::errorOccurred, + this, &PackageManager::onProcessError); + connect(m_process.get(), &QProcess::readyReadStandardOutput, + this, &PackageManager::onProcessOutput); + connect(m_process.get(), &QProcess::readyReadStandardError, + this, &PackageManager::onProcessOutput); +} + +PackageManager::~PackageManager() { + if (m_process && m_process->state() != QProcess::NotRunning) { + m_process->terminate(); + m_process->waitForFinished(3000); + } +} + +void PackageManager::detectHelper() { + // Check for yay first + QString yayPath = QStandardPaths::findExecutable("yay"); + if (!yayPath.isEmpty()) { + m_helper = Helper::Yay; + Logger::info("Using yay as package helper"); + return; + } + + // Check for paru - deprecate because paru doesn't allow running with pkexec + // QString paruPath = QStandardPaths::findExecutable("paru"); + // if (!paruPath.isEmpty()) { + // m_helper = Helper::Paru; + // Logger::info("Using paru as package helper"); + // return; + // } + + // Default to pacman + m_helper = Helper::Pacman; + Logger::info("Using pacman as package helper"); +} + +QString PackageManager::getHelperName() const { + switch (m_helper) { + case Helper::Yay: return "yay"; + case Helper::Pacman: return "pacman"; + default: return "pacman"; + } +} + +void PackageManager::installPackage(const QString& packageName, const QString& repository) { + std::lock_guard lock(m_mutex); + + Logger::info(QString("Installing package: %1 from %2").arg(packageName, repository.isEmpty() ? "default" : repository)); + emit operationStarted(QString("Installing %1...").arg(packageName)); + + // Determine if this is an AUR package (not from official repos or chaotic-aur) + QString repoLower = repository.toLower(); + bool isAUR = repoLower == "aur"; + QString helper = getHelperName(); + + QString command; + if (isAUR && (m_helper == Helper::Yay)) { + // AUR packages - use pkexec to get userpassword before hand + // Paru has a problem here, so default to yay + command = QString("pkexec %1 -S %2 --noconfirm").arg(helper, packageName); + } else { + // Official repos and chaotic-aur need root access and use pacman + command = QString("pkexec pacman -S %1 --noconfirm").arg(packageName); + } + + executeCommand("sh", QStringList() << "-c" << command); +} + +void PackageManager::uninstallPackage(const QString& packageName, const QString& repository) { + std::lock_guard lock(m_mutex); + + Logger::info(QString("Uninstalling package: %1 from %2").arg(packageName, repository.isEmpty() ? "default" : repository)); + emit operationStarted(QString("Uninstalling %1...").arg(packageName)); + + // Uninstall always needs root (even for AUR packages, they're in the system db once installed) + QString command = QString("pkexec pacman -Rdd %1 --noconfirm").arg(packageName); + + executeCommand("sh", QStringList() << "-c" << command); +} + +void PackageManager::updatePackage(const QString& packageName, const QString& repository) { + std::lock_guard lock(m_mutex); + + Logger::info(QString("Updating package: %1 from %2").arg(packageName, repository.isEmpty() ? "default" : repository)); + emit operationStarted(QString("Updating %1...").arg(packageName)); + + // Determine if this is an AUR package (not from official repos or chaotic-aur) + QString repoLower = repository.toLower(); + bool isAUR = repoLower == "aur"; + QString helper = getHelperName(); + + QString command; + if (isAUR && (m_helper == Helper::Yay)) { + // AUR packages - run helper as regular user (no pkexec) + command = QString("%1 -S %2 --noconfirm").arg(helper, packageName); + } else { + // Official repos and chaotic-aur need root access and use pacman + command = QString("pkexec pacman -S %1 --noconfirm").arg(packageName); + } + + executeCommand("sh", QStringList() << "-c" << command); +} + +void PackageManager::updateAllPackages() { + std::lock_guard lock(m_mutex); + + Logger::info("Updating all packages"); + emit operationStarted("Updating all packages..."); + + QString command = QString("pkexec %1 -Syu --noconfirm") + .arg(getHelperName()); + + executeCommand("sh", QStringList() << "-c" << command); +} + +void PackageManager::executeCommand(const QString& command, const QStringList& args) { + if (m_process->state() != QProcess::NotRunning) { + Logger::warning("Another operation is already running"); + emit operationError("Another operation is already in progress"); + return; + } + + // Merge stdout and stderr so we capture all output + m_process->setProcessChannelMode(QProcess::MergedChannels); + + Logger::debug(QString("Executing: %1 %2").arg(command, args.join(" "))); + + // Emit the actual command being executed to the UI for visibility + QString fullCommand = command + " " + args.join(" "); + emit operationOutput(QString(">> Executing: %1\n").arg(fullCommand)); + + m_process->start(command, args); + + // Check if process started successfully + if (!m_process->waitForStarted(3000)) { + QString error = QString("Failed to start process: %1").arg(m_process->errorString()); + Logger::error(error); + emit operationError(error); + } +} + +void PackageManager::onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) { + QString output = m_process->readAllStandardOutput(); + QString error = m_process->readAllStandardError(); + + if (exitStatus == QProcess::NormalExit && exitCode == 0) { + Logger::info("Operation completed successfully"); + emit operationCompleted(true, "Operation completed successfully"); + } else { + Logger::error(QString("Operation failed with exit code %1").arg(exitCode)); + Logger::error(QString("Error output: %1").arg(error)); + emit operationCompleted(false, QString("Operation failed: %1").arg(error)); + } +} + +void PackageManager::onProcessError(QProcess::ProcessError /*error*/) { + QString errorString = m_process->errorString(); + Logger::error(QString("Process error: %1").arg(errorString)); + emit operationError(errorString); +} + +void PackageManager::onProcessOutput() { + // Since we merged channels, only read stdout (which includes stderr) + QString output = m_process->readAll(); + if (!output.isEmpty()) { + Logger::debug(QString("Process output: %1").arg(output.trimmed())); + emit operationOutput(output); + } +} + +void PackageManager::cancelRunningOperation() { + if (m_process && m_process->state() != QProcess::NotRunning) { + Logger::warning("Cancelling running operation..."); + emit operationOutput("\n>>> Operation cancelled by user <<<\n"); + + // When using pkexec, we need to kill the actual pacman/yay/paru process + // not just the pkexec wrapper. Use pkill to terminate all package manager processes. + QProcess killProcess; + killProcess.start("pkexec", QStringList() << "bash" << "-c" + << "pkill -TERM pacman; pkill -TERM yay; pkill -TERM paru"); + killProcess.waitForFinished(2000); + + // Also terminate the QProcess wrapper + m_process->terminate(); + + // Wait up to 3 seconds for graceful termination + if (!m_process->waitForFinished(3000)) { + // Force kill if still running + Logger::warning("Process did not terminate gracefully, forcing kill..."); + killProcess.start("pkexec", QStringList() << "bash" << "-c" + << "pkill -KILL pacman; pkill -KILL yay; pkill -KILL paru"); + killProcess.waitForFinished(2000); + + m_process->kill(); + m_process->waitForFinished(1000); + } + + emit operationCompleted(false, "Operation cancelled by user"); + Logger::info("Operation cancelled successfully"); + } else { + Logger::warning("No operation is currently running"); + } +} + +bool PackageManager::isOperationRunning() const { + return m_process && m_process->state() != QProcess::NotRunning; +} diff --git a/src/core/package_manager.h b/src/core/package_manager.h new file mode 100644 index 0000000..0e86a9b --- /dev/null +++ b/src/core/package_manager.h @@ -0,0 +1,69 @@ +#ifndef PACKAGE_MANAGER_H +#define PACKAGE_MANAGER_H + +#include +#include +#include +#include +#include + +/** + * @brief Singleton class for managing package operations (install, uninstall, update). + * + * Memory Management: + * - m_process: Owned by std::unique_ptr for RAII-style cleanup and clear ownership + * - Thread-safe via m_mutex for operation serialization + */ +class PackageManager : public QObject { + Q_OBJECT + +public: + enum class Helper { + Pacman, + Yay, + Paru + }; + + static PackageManager& instance(); + + ~PackageManager() override; + + // Disable copy and move + PackageManager(const PackageManager&) = delete; + PackageManager& operator=(const PackageManager&) = delete; + PackageManager(PackageManager&&) = delete; + PackageManager& operator=(PackageManager&&) = delete; + + void installPackage(const QString& packageName, const QString& repository = QString()); + void uninstallPackage(const QString& packageName, const QString& repository = QString()); + void updatePackage(const QString& packageName, const QString& repository = QString()); + void updateAllPackages(); + void cancelRunningOperation(); + bool isOperationRunning() const; + + Helper getHelper() const { return m_helper; } + QString getHelperName() const; + +signals: + void operationStarted(const QString& message); + void operationOutput(const QString& output); + void operationCompleted(bool success, const QString& message); + void operationError(const QString& error); + +private: + PackageManager(); + + void detectHelper(); + void executeCommand(const QString& command, const QStringList& args); + + Helper m_helper = Helper::Pacman; + std::unique_ptr m_process; + mutable std::mutex m_mutex; + +private slots: + void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); + void onProcessError(QProcess::ProcessError error); + void onProcessOutput(); +}; + +#endif // PACKAGE_MANAGER_H diff --git a/src/gui/home_widget.cpp b/src/gui/home_widget.cpp new file mode 100644 index 0000000..cba1315 --- /dev/null +++ b/src/gui/home_widget.cpp @@ -0,0 +1,170 @@ +#include "home_widget.h" +#include "package_card.h" +#include "package_details_dialog.h" +#include "../core/alpm_wrapper.h" +#include "../core/aur_helper.h" +#include "../utils/logger.h" +#include +#include +#include + +HomeWidget::HomeWidget(QWidget* parent) + : QWidget(parent) + , m_scrollArea(new QScrollArea(this)) + , m_contentWidget(new QWidget()) + , m_gridLayout(new QGridLayout(m_contentWidget)) + , m_updateTimer(new QTimer(this)) { + + setupUi(); + loadFeaturedPackages(); + createPackageCards(); + + // Setup timer to periodically check installation status + connect(m_updateTimer, &QTimer::timeout, this, &HomeWidget::onUpdateTimer); + m_updateTimer->start(3000); // Check every 3 seconds + + // Initial check + checkInstalledPackages(); +} + +void HomeWidget::setupUi() { + auto* mainLayout = new QVBoxLayout(this); + + auto* titleLabel = new QLabel("Featured Packages", this); + auto titleFont = titleLabel->font(); + titleFont.setPointSize(24); + titleFont.setBold(true); + titleLabel->setFont(titleFont); + mainLayout->addWidget(titleLabel); + + m_scrollArea->setWidget(m_contentWidget); + m_scrollArea->setWidgetResizable(true); + m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + m_gridLayout->setSpacing(15); + m_gridLayout->setContentsMargins(10, 10, 10, 10); + + mainLayout->addWidget(m_scrollArea); + setLayout(mainLayout); +} + +void HomeWidget::loadFeaturedPackages() { + // Featured packages list with initial repositories + m_featuredPackages = { + {"firefox", "Latest", "Fast, Private & Safe Web Browser", "extra"}, + {"gimp", "Latest", "GNU Image Manipulation Program", "extra"}, + {"vlc", "Latest", "Multi-platform MPEG, VCD/DVD, and DivX player", "extra"}, + {"telegram-desktop", "Latest", "Official Telegram Desktop client", "extra"}, + {"obs-studio", "Latest", "Free, open source software for live streaming and recording", "extra"}, + {"blender", "Latest", "A fully integrated 3D graphics creation suite", "extra"}, + {"spotify", "Latest", "A proprietary music streaming service", "AUR"}, + {"discord", "Latest", "All-in-one voice and text chat for gamers", "extra"}, + {"google-chrome", "Latest", "The popular web browser by Google", "AUR"}, + {"visual-studio-code-bin", "Latest", "Visual Studio Code (official binary version)", "AUR"}, + {"libreoffice-still", "Latest", "Free and Open Source Office Suite", "extra"}, + {"zoom", "Latest", "Video Conferencing and Web Conferencing Service", "AUR"} + }; + + // Fetch actual package information from repositories + for (auto& pkg : m_featuredPackages) { + PackageInfo repoInfo = AlpmWrapper::instance().getPackageInfo(pkg.name); + + if (!repoInfo.name.isEmpty() && !repoInfo.repository.isEmpty()) { + // Package found in official repos (including chaotic-aur), update with actual information + pkg.repository = repoInfo.repository; + pkg.version = repoInfo.version; + pkg.description = repoInfo.description; + + if (pkg.repository.toLower() != "aur") { + Logger::debug(QString("Package %1 found in %2 repository with version %3") + .arg(pkg.name, pkg.repository, pkg.version)); + } + } else if (pkg.repository.toLower() == "aur") { + // Package not found in official repos (including chaotic-aur) + // Default to AUR helper (yay/paru) since chaotic-aur is not enabled or doesn't have this package + pkg.repository = "aur"; + Logger::debug(QString("Package %1 not found in enabled repositories, defaulting to AUR helper").arg(pkg.name)); + } + } + + Logger::info(QString("Loaded %1 featured packages").arg(m_featuredPackages.size())); +} + +void HomeWidget::createPackageCards() { + int row = 0; + int col = 0; + const int columns = 3; + + for (const auto& pkg : m_featuredPackages) { + auto* card = new PackageCard(pkg, m_contentWidget); + connect(card, &PackageCard::clicked, this, &HomeWidget::onPackageClicked); + + m_gridLayout->addWidget(card, row, col); + m_packageCards.append(card); + + col++; + if (col >= columns) { + col = 0; + row++; + } + } + + // Add stretch to push cards to the top + m_gridLayout->setRowStretch(row + 1, 1); +} + +void HomeWidget::checkInstalledPackages() { + for (auto* card : m_packageCards) { + card->checkInstallStatus(); + } +} + +void HomeWidget::onUpdateTimer() { + checkInstalledPackages(); +} + +void HomeWidget::onPackageClicked(const PackageInfo& info) { + Logger::info(QString("Package clicked: %1").arg(info.name)); + + // Fetch full package details including dependencies + PackageInfo fullInfo; + + if (info.repository.toLower() == "aur") { + // For AUR packages, query AUR API for full details + AurHelper aurHelper; + QEventLoop loop; + + connect(&aurHelper, &AurHelper::packageInfoReceived, [&fullInfo, &loop](const PackageInfo& aurInfo) { + fullInfo = aurInfo; + loop.quit(); + }); + + connect(&aurHelper, &AurHelper::error, [&fullInfo, &info, &loop](const QString& error) { + Logger::warning(QString("Failed to fetch AUR package info: %1").arg(error)); + fullInfo = info; // Fallback to basic info + loop.quit(); + }); + + aurHelper.getPackageInfo(info.name); + loop.exec(); // Wait for response + + // If we didn't get full info, use the basic info + if (fullInfo.name.isEmpty()) { + fullInfo = info; + } + } else { + // For official repos, fetch full details from ALPM + fullInfo = AlpmWrapper::instance().getPackageInfo(info.name); + // If not found, use the basic info + if (fullInfo.name.isEmpty()) { + fullInfo = info; + } + } + + auto* dialog = new PackageDetailsDialog(fullInfo, this); + dialog->exec(); + dialog->deleteLater(); + + // Update installation status after dialog closes + checkInstalledPackages(); +} diff --git a/src/gui/home_widget.h b/src/gui/home_widget.h new file mode 100644 index 0000000..08684a2 --- /dev/null +++ b/src/gui/home_widget.h @@ -0,0 +1,47 @@ +#ifndef HOME_WIDGET_H +#define HOME_WIDGET_H + +#include +#include +#include +#include +#include +#include "../utils/types.h" + +class PackageCard; + +/** + * @brief Widget displaying featured packages on the home screen. + * + * Memory Management: + * - All Qt widget members use Qt parent-child ownership (raw pointers are non-owning) + * - m_packageCards contains non-owning pointers to cards owned by m_contentWidget + */ +class HomeWidget : public QWidget { + Q_OBJECT + +public: + explicit HomeWidget(QWidget* parent = nullptr); + ~HomeWidget() override = default; + +private: + void setupUi(); + void loadFeaturedPackages(); + void createPackageCards(); + void checkInstalledPackages(); + + QVector m_featuredPackages; + QVector m_packageCards; // Non-owning pointers, owned by m_contentWidget + + // Qt parent-child managed widgets (non-owning pointers) + QScrollArea* m_scrollArea = nullptr; + QWidget* m_contentWidget = nullptr; + QGridLayout* m_gridLayout = nullptr; + QTimer* m_updateTimer = nullptr; + +private slots: + void onPackageClicked(const PackageInfo& info); + void onUpdateTimer(); +}; + +#endif // HOME_WIDGET_H diff --git a/src/gui/installed_widget.cpp b/src/gui/installed_widget.cpp new file mode 100644 index 0000000..075b404 --- /dev/null +++ b/src/gui/installed_widget.cpp @@ -0,0 +1,174 @@ +#include "installed_widget.h" +#include "package_card.h" +#include "package_details_dialog.h" +#include "../core/alpm_wrapper.h" +#include "../utils/logger.h" +#include +#include +#include +#include + +InstalledWidget::InstalledWidget(QWidget* parent) + : QWidget(parent) + , m_filterInput(new QLineEdit(this)) + , m_scrollArea(new QScrollArea(this)) + , m_contentWidget(new QWidget()) + , m_gridLayout(new QGridLayout(m_contentWidget)) + , m_statusLabel(new QLabel(this)) + , m_countLabel(new QLabel(this)) { + + setupUi(); + loadInstalledPackages(); +} + +void InstalledWidget::setupUi() { + auto* mainLayout = new QVBoxLayout(this); + + // Header + auto* headerLayout = new QHBoxLayout(); + + auto* titleLabel = new QLabel("Installed Packages", this); + auto titleFont = titleLabel->font(); + titleFont.setPointSize(24); + titleFont.setBold(true); + titleLabel->setFont(titleFont); + headerLayout->addWidget(titleLabel); + + headerLayout->addStretch(); + + m_countLabel->setStyleSheet("font-size: 14px; color: #888;"); + headerLayout->addWidget(m_countLabel); + + auto* refreshButton = new QPushButton("Refresh", this); + connect(refreshButton, &QPushButton::clicked, this, &InstalledWidget::refreshPackages); + headerLayout->addWidget(refreshButton); + + mainLayout->addLayout(headerLayout); + + // Filter + m_filterInput->setPlaceholderText("Filter installed packages..."); + m_filterInput->setMinimumHeight(35); + m_filterInput->setClearButtonEnabled(true); + connect(m_filterInput, &QLineEdit::textChanged, + this, &InstalledWidget::onFilterTextChanged); + mainLayout->addWidget(m_filterInput); + + // Status label + m_statusLabel->setAlignment(Qt::AlignCenter); + m_statusLabel->setText("Loading installed packages..."); + mainLayout->addWidget(m_statusLabel); + + // Results area + m_scrollArea->setWidget(m_contentWidget); + m_scrollArea->setWidgetResizable(true); + m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + m_gridLayout->setSpacing(15); + m_gridLayout->setContentsMargins(10, 10, 10, 10); + + mainLayout->addWidget(m_scrollArea); + setLayout(mainLayout); +} + +void InstalledWidget::loadInstalledPackages() { + m_statusLabel->setText("Loading installed packages..."); + m_statusLabel->show(); + + (void)QtConcurrent::run([this]() { + auto packages = AlpmWrapper::instance().getInstalledPackages(); + + QMetaObject::invokeMethod(this, [this, packages]() { + m_allPackages = packages; + m_filteredPackages = packages; + + m_statusLabel->hide(); + m_countLabel->setText(QString("%1 packages installed") + .arg(packages.size())); + + displayPackages(packages); + + Logger::info(QString("Loaded %1 installed packages").arg(packages.size())); + }, Qt::QueuedConnection); + }); +} + +void InstalledWidget::refreshPackages() { + clearResults(); + loadInstalledPackages(); +} + +void InstalledWidget::displayPackages(const QVector& packages) { + clearResults(); + + if (packages.isEmpty()) { + m_statusLabel->setText("No packages found"); + m_statusLabel->show(); + return; + } + + int row = 0; + int col = 0; + const int columns = 3; + + for (const auto& pkg : packages) { + auto* card = new PackageCard(pkg, m_contentWidget); + card->updateInstallStatus(true); + connect(card, &PackageCard::clicked, this, &InstalledWidget::onPackageClicked); + + m_gridLayout->addWidget(card, row, col); + + col++; + if (col >= columns) { + col = 0; + row++; + } + } + + m_gridLayout->setRowStretch(row + 1, 1); +} + +void InstalledWidget::clearResults() { + while (auto* item = m_gridLayout->takeAt(0)) { + if (auto* widget = item->widget()) { + widget->deleteLater(); + } + delete item; + } +} + +void InstalledWidget::filterPackages(const QString& query) { + if (query.isEmpty()) { + m_filteredPackages = m_allPackages; + } else { + m_filteredPackages.clear(); + QString lowerQuery = query.toLower(); + + for (const auto& pkg : m_allPackages) { + if (pkg.name.toLower().contains(lowerQuery) || + pkg.description.toLower().contains(lowerQuery)) { + m_filteredPackages.append(pkg); + } + } + } + + m_countLabel->setText(QString("%1 of %2 packages") + .arg(m_filteredPackages.size()) + .arg(m_allPackages.size())); + + displayPackages(m_filteredPackages); +} + +void InstalledWidget::onFilterTextChanged(const QString& text) { + filterPackages(text); +} + +void InstalledWidget::onPackageClicked(const PackageInfo& info) { + Logger::info(QString("Package clicked: %1").arg(info.name)); + + auto* dialog = new PackageDetailsDialog(info, this); + dialog->exec(); + dialog->deleteLater(); + + // Refresh after dialog closes in case package was uninstalled + refreshPackages(); +} diff --git a/src/gui/installed_widget.h b/src/gui/installed_widget.h new file mode 100644 index 0000000..d4cbe0b --- /dev/null +++ b/src/gui/installed_widget.h @@ -0,0 +1,51 @@ +#ifndef INSTALLED_WIDGET_H +#define INSTALLED_WIDGET_H + +#include +#include +#include +#include +#include +#include +#include "../utils/types.h" + +/** + * @brief Widget displaying installed packages. + * + * Memory Management: + * - All Qt widget members use Qt parent-child ownership (raw pointers are non-owning) + * - Package cards are dynamically created/destroyed in displayPackages/clearResults + */ +class InstalledWidget : public QWidget { + Q_OBJECT + +public: + explicit InstalledWidget(QWidget* parent = nullptr); + ~InstalledWidget() override = default; + + void refreshPackages(); + +private: + void setupUi(); + void loadInstalledPackages(); + void displayPackages(const QVector& packages); + void filterPackages(const QString& query); + void clearResults(); + + // Qt parent-child managed widgets (non-owning pointers) + QLineEdit* m_filterInput = nullptr; + QScrollArea* m_scrollArea = nullptr; + QWidget* m_contentWidget = nullptr; + QGridLayout* m_gridLayout = nullptr; + QLabel* m_statusLabel = nullptr; + QLabel* m_countLabel = nullptr; + + QVector m_allPackages; + QVector m_filteredPackages; + +private slots: + void onPackageClicked(const PackageInfo& info); + void onFilterTextChanged(const QString& text); +}; + +#endif // INSTALLED_WIDGET_H diff --git a/src/gui/mainwindow.cpp b/src/gui/mainwindow.cpp new file mode 100644 index 0000000..dd71d18 --- /dev/null +++ b/src/gui/mainwindow.cpp @@ -0,0 +1,148 @@ +#include "mainwindow.h" +#include "home_widget.h" +#include "search_widget.h" +#include "installed_widget.h" +#include "updates_widget.h" +#include "settings_widget.h" +#include "../utils/logger.h" +#include "../core/alpm_wrapper.h" +#include +#include +#include +#include +#include +#include + +MainWindow::MainWindow(QWidget* parent) + : QMainWindow(parent) + , m_tabWidget(std::make_unique(this)) { + + // Initialize ALPM before creating widgets that might need it + if (!AlpmWrapper::instance().initialize()) { + QMessageBox::critical(this, "Error", + "Failed to initialize package manager. Please check your system configuration."); + Logger::error("Failed to initialize ALPM in MainWindow"); + } + + setupUi(); + loadStyleSheet(); + + Logger::info("MainWindow created successfully"); +} + +MainWindow::~MainWindow() { + AlpmWrapper::instance().release(); + Logger::info("MainWindow destroyed"); +} + +void MainWindow::setupUi() { + setWindowTitle("ALG App Store (Beta)"); + setMinimumSize(1024, 768); + resize(1124, 868); + + // Create widgets + m_homeWidget = new HomeWidget(this); + m_searchWidget = new SearchWidget(this); + m_installedWidget = new InstalledWidget(this); + m_updatesWidget = new UpdatesWidget(this); + m_settingsWidget = new SettingsWidget(this); + + // Add tabs + m_tabWidget->addTab(m_homeWidget, "Home"); + m_tabWidget->addTab(m_searchWidget, "Search"); + m_tabWidget->addTab(m_installedWidget, "Installed"); + m_tabWidget->addTab(m_updatesWidget, "Updates"); + m_tabWidget->addTab(m_settingsWidget, "Settings"); + + // Connect settings signals + connect(m_settingsWidget, &SettingsWidget::multilibStatusChanged, + this, [this]() { + m_searchWidget->updateRepositoryList( + m_settingsWidget->isMultilibEnabled(), + m_settingsWidget->isChaoticAurEnabled() + ); + }); + + connect(m_settingsWidget, &SettingsWidget::chaoticAurStatusChanged, + this, [this]() { + m_searchWidget->updateRepositoryList( + m_settingsWidget->isMultilibEnabled(), + m_settingsWidget->isChaoticAurEnabled() + ); + }); + + // Initialize search widget with current repository states + m_searchWidget->updateRepositoryList( + m_settingsWidget->isMultilibEnabled(), + m_settingsWidget->isChaoticAurEnabled() + ); + + m_tabWidget->setTabPosition(QTabWidget::North); + m_tabWidget->setMovable(false); + + setCentralWidget(m_tabWidget.get()); + createMenuBar(); +} + +void MainWindow::createMenuBar() { + auto* fileMenu = menuBar()->addMenu("&File"); + + auto* refreshAction = new QAction("&Refresh", this); + refreshAction->setShortcut(QKeySequence::Refresh); + connect(refreshAction, &QAction::triggered, [this]() { + int currentIndex = m_tabWidget->currentIndex(); + if (currentIndex == 0) { + // Home widget refresh + } else if (currentIndex == 1) { + // Search widget refresh + } else if (currentIndex == 2) { + m_installedWidget->refreshPackages(); + } else if (currentIndex == 3) { + m_updatesWidget->checkForUpdates(); + } + }); + fileMenu->addAction(refreshAction); + + fileMenu->addSeparator(); + + auto* quitAction = new QAction("&Quit", this); + quitAction->setShortcut(QKeySequence::Quit); + connect(quitAction, &QAction::triggered, this, &QMainWindow::close); + fileMenu->addAction(quitAction); + + auto* helpMenu = menuBar()->addMenu("&Help"); + + auto* aboutAction = new QAction("&About", this); + connect(aboutAction, &QAction::triggered, [this]() { + QMessageBox::about(this, "About ALG App Store", + "ALG App Store (Beta)\n\n" + "A modern package manager for Arch Linux\n" + "Version: 0.2.28\n" + "Built with Qt6 and C++17\n\n" + "© 2025 Arka Linux GUI"); + }); + helpMenu->addAction(aboutAction); +} + +void MainWindow::loadStyleSheet() { + QFile styleFile(":/stylesheet.qss"); + + if (!styleFile.exists()) { + // Try loading from current directory (for development) + styleFile.setFileName("stylesheet.qss"); + } + + if (!styleFile.exists()) { + // Try loading from system installation path + styleFile.setFileName("/usr/share/alg-app-store/stylesheet.qss"); + } + + if (styleFile.open(QFile::ReadOnly)) { + QString styleSheet = QLatin1String(styleFile.readAll()); + qApp->setStyleSheet(styleSheet); + styleFile.close(); + Logger::info(QString("Stylesheet loaded successfully from: %1").arg(styleFile.fileName())); + } else { + Logger::warning("Could not load stylesheet"); + } +} diff --git a/src/gui/mainwindow.h b/src/gui/mainwindow.h new file mode 100644 index 0000000..cf6d531 --- /dev/null +++ b/src/gui/mainwindow.h @@ -0,0 +1,45 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include +#include + +class HomeWidget; +class SearchWidget; +class InstalledWidget; +class UpdatesWidget; +class SettingsWidget; + +/** + * @brief Main application window for ALG App Store. + * + * Memory Management: + * - m_tabWidget: Owned by std::unique_ptr (central widget) + * - Child widgets (m_homeWidget, etc.): Owned by Qt parent-child hierarchy + * through m_tabWidget. Raw pointers are used as non-owning references. + */ +class MainWindow : public QMainWindow { + Q_OBJECT + +public: + explicit MainWindow(QWidget* parent = nullptr); + ~MainWindow() override; + +private: + void setupUi(); + void createMenuBar(); + void loadStyleSheet(); + + std::unique_ptr m_tabWidget; + + // Non-owning pointers - owned by m_tabWidget via Qt parent-child hierarchy + HomeWidget* m_homeWidget = nullptr; + SearchWidget* m_searchWidget = nullptr; + InstalledWidget* m_installedWidget = nullptr; + UpdatesWidget* m_updatesWidget = nullptr; + SettingsWidget* m_settingsWidget = nullptr; +}; + +#endif // MAINWINDOW_H diff --git a/src/gui/package_card.cpp b/src/gui/package_card.cpp new file mode 100644 index 0000000..62cdee8 --- /dev/null +++ b/src/gui/package_card.cpp @@ -0,0 +1,111 @@ +#include "package_card.h" +#include "../core/alpm_wrapper.h" +#include +#include +#include +#include + +PackageCard::PackageCard(const PackageInfo& info, QWidget* parent) + : QWidget(parent) + , m_info(info) + , m_nameLabel(new QLabel(this)) + , m_descriptionLabel(new QLabel(this)) + , m_versionLabel(new QLabel(this)) + , m_repositoryLabel(new QLabel(this)) + , m_statusLabel(new QLabel(this)) { + + setupUi(); + checkInstallStatus(); +} + +void PackageCard::setupUi() { + setMinimumHeight(180); + setMaximumHeight(220); + setMinimumWidth(280); + setCursor(Qt::PointingHandCursor); + + setProperty("class", "package-card"); + + auto* mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(15, 15, 15, 15); + mainLayout->setSpacing(10); + + // Header with name and status + auto* headerLayout = new QHBoxLayout(); + + m_nameLabel->setText(m_info.name); + m_nameLabel->setWordWrap(false); + m_nameLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + auto nameFont = m_nameLabel->font(); + nameFont.setPointSize(14); + nameFont.setBold(true); + m_nameLabel->setFont(nameFont); + headerLayout->addWidget(m_nameLabel, 1); + + m_statusLabel->setProperty("class", "status-badge"); + m_statusLabel->hide(); + headerLayout->addWidget(m_statusLabel, 0); + + mainLayout->addLayout(headerLayout); + + // Description - with word wrap and proper sizing + m_descriptionLabel->setText(m_info.description); + m_descriptionLabel->setWordWrap(true); + m_descriptionLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + m_descriptionLabel->setMinimumHeight(40); + m_descriptionLabel->setMaximumHeight(70); + mainLayout->addWidget(m_descriptionLabel, 1); + + // Version + m_versionLabel->setText(QString("Version: %1").arg(m_info.version)); + auto versionFont = m_versionLabel->font(); + versionFont.setPointSize(9); + m_versionLabel->setFont(versionFont); + m_versionLabel->setStyleSheet("color: #888;"); + mainLayout->addWidget(m_versionLabel, 0); + + // Repository badge + m_repositoryLabel->setText(m_info.repository); + m_repositoryLabel->setProperty("class", "repo-badge"); + m_repositoryLabel->setAlignment(Qt::AlignLeft); + mainLayout->addWidget(m_repositoryLabel, 0); + + setLayout(mainLayout); +} + +void PackageCard::checkInstallStatus() { + m_isInstalled = AlpmWrapper::instance().isPackageInstalled(m_info.name); + updateInstallStatus(m_isInstalled); +} + +void PackageCard::updateInstallStatus(bool installed) { + m_isInstalled = installed; + + if (m_isInstalled) { + m_statusLabel->setText("Installed"); + m_statusLabel->show(); + } else { + m_statusLabel->hide(); + } +} + +void PackageCard::mousePressEvent(QMouseEvent* event) { + if (event->button() == Qt::LeftButton) { + emit clicked(m_info); + } + QWidget::mousePressEvent(event); +} + +void PackageCard::enterEvent(QEnterEvent* event) { + setProperty("hovered", true); + style()->unpolish(this); + style()->polish(this); + QWidget::enterEvent(event); +} + +void PackageCard::leaveEvent(QEvent* event) { + setProperty("hovered", false); + style()->unpolish(this); + style()->polish(this); + QWidget::leaveEvent(event); +} diff --git a/src/gui/package_card.h b/src/gui/package_card.h new file mode 100644 index 0000000..bdb6f86 --- /dev/null +++ b/src/gui/package_card.h @@ -0,0 +1,49 @@ +#ifndef PACKAGE_CARD_H +#define PACKAGE_CARD_H + +#include +#include +#include +#include "../utils/types.h" + +/** + * @brief A clickable card widget displaying package information. + * + * Memory Management: + * - All Qt widget members use Qt parent-child ownership (raw pointers are non-owning) + */ +class PackageCard : public QWidget { + Q_OBJECT + +public: + explicit PackageCard(const PackageInfo& info, QWidget* parent = nullptr); + ~PackageCard() override = default; + + const PackageInfo& packageInfo() const { return m_info; } + void updateInstallStatus(bool installed); + void checkInstallStatus(); + +signals: + void clicked(const PackageInfo& info); + +protected: + void mousePressEvent(QMouseEvent* event) override; + void enterEvent(QEnterEvent* event) override; + void leaveEvent(QEvent* event) override; + +private: + void setupUi(); + + PackageInfo m_info; + + // Qt parent-child managed widgets (non-owning pointers) + QLabel* m_nameLabel = nullptr; + QLabel* m_descriptionLabel = nullptr; + QLabel* m_versionLabel = nullptr; + QLabel* m_repositoryLabel = nullptr; + QLabel* m_statusLabel = nullptr; + + bool m_isInstalled = false; +}; + +#endif // PACKAGE_CARD_H diff --git a/src/gui/package_details_dialog.cpp b/src/gui/package_details_dialog.cpp new file mode 100644 index 0000000..cacebce --- /dev/null +++ b/src/gui/package_details_dialog.cpp @@ -0,0 +1,821 @@ +#include "package_details_dialog.h" +#include "../core/alpm_wrapper.h" +#include "../core/package_manager.h" +#include "../utils/logger.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +PackageDetailsDialog::PackageDetailsDialog(const PackageInfo& info, QWidget* parent) + : QDialog(parent) + , m_info(info) + , m_nameLabel(new QLabel(this)) + , m_versionLabel(new QLabel(this)) + , m_repositoryLabel(new QLabel(this)) + , m_maintainerLabel(new QLabel(this)) + , m_urlLabel(new QLabel(this)) + , m_descriptionText(new QTextEdit(this)) + , m_dependenciesText(new QTextEdit(this)) + , m_lastUpdatedLabel(new QLabel(this)) + , m_installButton(new QPushButton("Install", this)) + , m_uninstallButton(new QPushButton("Uninstall", this)) + , m_launchButton(new QPushButton("Launch", this)) + , m_closeButton(new QPushButton("Close", this)) + , m_statusBadge(new QLabel(this)) + , m_progressBar(new QProgressBar(this)) + , m_progressLabel(new QLabel(this)) + , m_progressWidget(new QWidget(this)) + , m_logViewer(new QTextEdit(this)) + , m_toggleLogButton(new QPushButton("Show Logs", this)) + , m_logWidget(new QWidget(this)) { + + setupUi(); + checkInstallStatus(); + updateButtonStates(); + + // Connect to PackageManager signals + connect(&PackageManager::instance(), &PackageManager::operationStarted, + this, &PackageDetailsDialog::onOperationStarted); + connect(&PackageManager::instance(), &PackageManager::operationOutput, + this, &PackageDetailsDialog::onOperationOutput); + connect(&PackageManager::instance(), &PackageManager::operationCompleted, + this, &PackageDetailsDialog::onOperationCompleted); + connect(&PackageManager::instance(), &PackageManager::operationError, + this, &PackageDetailsDialog::onOperationError); +} + +void PackageDetailsDialog::setupUi() { + setWindowTitle("Package Details"); + setMinimumSize(700, 600); + setModal(true); + + // Remove window icon + setWindowIcon(QIcon()); + + auto* dialogLayout = new QVBoxLayout(this); + dialogLayout->setContentsMargins(0, 0, 0, 0); + dialogLayout->setSpacing(0); + + // Create scroll area for content + auto* scrollArea = new QScrollArea(this); + scrollArea->setWidgetResizable(true); + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea->setFrameShape(QFrame::NoFrame); + + // Content widget + auto* contentWidget = new QWidget(); + auto* mainLayout = new QVBoxLayout(contentWidget); + mainLayout->setContentsMargins(20, 30, 20, 20); + mainLayout->setSpacing(20); + + // Header with name and repository badge + auto* headerLayout = new QHBoxLayout(); + + m_nameLabel->setText(m_info.name); + m_nameLabel->setContentsMargins(0, 0, 0, 0); + auto nameFont = m_nameLabel->font(); + nameFont.setPointSize(20); + nameFont.setBold(true); + m_nameLabel->setFont(nameFont); + headerLayout->addWidget(m_nameLabel); + + headerLayout->addStretch(); + + // Repository badge (member so we can update it later) + m_repositoryLabel->setText(m_info.repository); + m_repositoryLabel->setProperty("class", "repo-badge"); + m_repositoryLabel->setStyleSheet("background-color: #27272a; color: #a1a1aa; " + "border-radius: 4px; padding: 6px 12px; font-size: 12px;"); + headerLayout->addWidget(m_repositoryLabel); + + // Installed status badge (hidden by default) + m_statusBadge->setText("Installed"); + m_statusBadge->setProperty("class", "status-badge"); + m_statusBadge->setStyleSheet("background-color: #16a34a; color: #ffffff; " + "border-radius: 4px; padding: 6px 10px; font-size: 12px;"); + m_statusBadge->hide(); + headerLayout->addWidget(m_statusBadge); + + mainLayout->addLayout(headerLayout); + + // Description right under the name + auto* descLabel = new QLabel(m_info.description, this); + descLabel->setWordWrap(true); + descLabel->setStyleSheet("color: #a1a1aa; font-size: 13px;"); + mainLayout->addWidget(descLabel); + + // Separator line + auto* line1 = new QFrame(this); + line1->setFrameShape(QFrame::HLine); + line1->setStyleSheet("background-color: #27272a;"); + mainLayout->addWidget(line1); + + // Package Details section - 2x2 grid layout + auto* detailsTitle = new QLabel("Package Details", this); + detailsTitle->setStyleSheet("font-size: 16px; font-weight: bold;"); + mainLayout->addWidget(detailsTitle); + + auto* infoWidget = new QWidget(this); + auto* infoGrid = new QGridLayout(infoWidget); + infoGrid->setSpacing(15); + infoGrid->setContentsMargins(0, 10, 0, 10); + infoGrid->setColumnStretch(1, 1); + infoGrid->setColumnStretch(3, 1); + + // Version (top-left) + auto* versionTitle = new QLabel("Version", this); + versionTitle->setStyleSheet("color: #a1a1aa; font-size: 12px;"); + m_versionLabel->setText(m_info.version); + m_versionLabel->setStyleSheet("font-size: 13px;"); + infoGrid->addWidget(versionTitle, 0, 0, Qt::AlignTop); + infoGrid->addWidget(m_versionLabel, 0, 1); + + // Maintainer (top-right) + if (!m_info.maintainer.isEmpty()) { + auto* maintainerTitle = new QLabel("Maintainer", this); + maintainerTitle->setStyleSheet("color: #a1a1aa; font-size: 12px;"); + m_maintainerLabel->setText(m_info.maintainer); + m_maintainerLabel->setStyleSheet("font-size: 13px;"); + infoGrid->addWidget(maintainerTitle, 0, 2, Qt::AlignTop); + infoGrid->addWidget(m_maintainerLabel, 0, 3); + } + + // Upstream URL (bottom-left) + if (!m_info.upstreamUrl.isEmpty()) { + auto* urlTitle = new QLabel("Upstream URL", this); + urlTitle->setStyleSheet("color: #a1a1aa; font-size: 12px;"); + m_urlLabel->setText(QString("%1") + .arg(m_info.upstreamUrl)); + m_urlLabel->setOpenExternalLinks(true); + m_urlLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); + m_urlLabel->setWordWrap(true); + m_urlLabel->setStyleSheet("font-size: 13px;"); + infoGrid->addWidget(urlTitle, 1, 0, Qt::AlignTop); + infoGrid->addWidget(m_urlLabel, 1, 1); + } + + // Last Updated (bottom-right) + if (!m_info.lastUpdated.isNull()) { + auto* updatedTitle = new QLabel("Last Updated", this); + updatedTitle->setStyleSheet("color: #a1a1aa; font-size: 12px;"); + m_lastUpdatedLabel->setText(m_info.lastUpdated.toString("MMM. d, yyyy, h a")); + m_lastUpdatedLabel->setStyleSheet("font-size: 13px;"); + infoGrid->addWidget(updatedTitle, 1, 2, Qt::AlignTop); + infoGrid->addWidget(m_lastUpdatedLabel, 1, 3); + } + + mainLayout->addWidget(infoWidget); + + // Separator line + auto* line2 = new QFrame(this); + line2->setFrameShape(QFrame::HLine); + line2->setStyleSheet("background-color: #27272a;"); + mainLayout->addWidget(line2); + + // Dependencies section + if (!m_info.dependList.isEmpty()) { + auto* depsTitle = new QLabel("Dependencies", this); + depsTitle->setStyleSheet("font-size: 16px; font-weight: bold;"); + mainLayout->addWidget(depsTitle); + + m_dependenciesText->setPlainText(m_info.dependList.join("\n")); + m_dependenciesText->setReadOnly(true); + m_dependenciesText->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + m_dependenciesText->setStyleSheet("background-color: #18181b; border: 1px solid #27272a; " + "border-radius: 6px; padding: 8px; font-size: 12px;"); + + // Adjust height to fit all dependencies without scrolling + QFontMetrics fm(m_dependenciesText->font()); + int lineHeight = fm.lineSpacing(); + int numLines = m_info.dependList.size(); + int contentHeight = (numLines * lineHeight) + 20; // +20 for padding + m_dependenciesText->setMinimumHeight(contentHeight); + m_dependenciesText->setMaximumHeight(contentHeight); + + mainLayout->addWidget(m_dependenciesText); + + // Separator line + auto* line3 = new QFrame(this); + line3->setFrameShape(QFrame::HLine); + line3->setStyleSheet("background-color: #27272a;"); + mainLayout->addWidget(line3); + } + + // Command section + auto* commandTitle = new QLabel("Command", this); + commandTitle->setStyleSheet("font-size: 16px; font-weight: bold;"); + mainLayout->addWidget(commandTitle); + + // Command buttons and text + auto* commandWidget = new QWidget(this); + auto* commandLayout = new QVBoxLayout(commandWidget); + commandLayout->setContentsMargins(0, 0, 0, 0); + commandLayout->setSpacing(10); + + // Determine which package managers to show + bool isAUR = (m_info.repository.toLower() == "aur"); + QString command; + + if (isAUR) { + // Show yay and paru for AUR + auto* buttonLayout = new QHBoxLayout(); + + auto* yayButton = new QPushButton("yay", this); + yayButton->setCheckable(true); + yayButton->setChecked(true); + yayButton->setStyleSheet("QPushButton { background-color: #27272a; color: #fafafa; " + "border: none; border-radius: 4px; padding: 6px 16px; }" + "QPushButton:checked { background-color: #3b82f6; }"); + + auto* paruButton = new QPushButton("paru", this); + paruButton->setCheckable(true); + paruButton->setStyleSheet("QPushButton { background-color: #27272a; color: #fafafa; " + "border: none; border-radius: 4px; padding: 6px 16px; }" + "QPushButton:checked { background-color: #3b82f6; }"); + + buttonLayout->addWidget(yayButton); + buttonLayout->addWidget(paruButton); + buttonLayout->addStretch(); + + commandLayout->addLayout(buttonLayout); + + command = QString("yay -S %1").arg(m_info.name); + auto* commandText = new QLabel(this); + commandText->setText(command); + commandText->setStyleSheet("background-color: #27272a; color: #fafafa; " + "border-radius: 6px; padding: 12px; font-family: monospace; font-size: 13px;"); + commandText->setTextInteractionFlags(Qt::TextSelectableByMouse); + commandLayout->addWidget(commandText); + + // Connect buttons to update command + connect(yayButton, &QPushButton::clicked, [yayButton, paruButton, commandText, this]() { + yayButton->setChecked(true); + paruButton->setChecked(false); + commandText->setText(QString("yay -S %1").arg(m_info.name)); + }); + + connect(paruButton, &QPushButton::clicked, [yayButton, paruButton, commandText, this]() { + paruButton->setChecked(true); + yayButton->setChecked(false); + commandText->setText(QString("paru -S %1").arg(m_info.name)); + }); + + } else { + // Show pacman for official repos + auto* pacmanButton = new QPushButton("pacman", this); + pacmanButton->setCheckable(true); + pacmanButton->setChecked(true); + pacmanButton->setStyleSheet("QPushButton { background-color: #3b82f6; color: #fafafa; " + "border: none; border-radius: 4px; padding: 6px 16px; }"); + commandLayout->addWidget(pacmanButton, 0, Qt::AlignLeft); + + command = QString("sudo pacman -S %1").arg(m_info.name); + auto* commandText = new QLabel(command, this); + commandText->setStyleSheet("background-color: #27272a; color: #fafafa; " + "border-radius: 6px; padding: 12px; font-family: monospace; font-size: 13px;"); + commandText->setTextInteractionFlags(Qt::TextSelectableByMouse); + commandLayout->addWidget(commandText); + } + + mainLayout->addWidget(commandWidget); + + auto* noteLabel = new QLabel("Please ensure your system meets the minimum requirements before installation.", contentWidget); + noteLabel->setStyleSheet("color: #71717a; font-size: 11px; font-style: italic;"); + noteLabel->setWordWrap(true); + mainLayout->addWidget(noteLabel); + + mainLayout->addStretch(); + + // Set content widget to scroll area + contentWidget->setLayout(mainLayout); + scrollArea->setWidget(contentWidget); + dialogLayout->addWidget(scrollArea, 1); + + // Buttons at the bottom (not scrollable) + auto* buttonWidget = new QWidget(this); + auto* buttonLayout = new QHBoxLayout(buttonWidget); + buttonLayout->setContentsMargins(20, 10, 20, 20); + buttonLayout->addStretch(); + + m_installButton->setMinimumWidth(100); + m_installButton->setMinimumHeight(35); + connect(m_installButton, &QPushButton::clicked, this, &PackageDetailsDialog::onInstall); + buttonLayout->addWidget(m_installButton); + + m_uninstallButton->setMinimumWidth(100); + m_uninstallButton->setMinimumHeight(35); + connect(m_uninstallButton, &QPushButton::clicked, this, &PackageDetailsDialog::onUninstall); + buttonLayout->addWidget(m_uninstallButton); + + m_launchButton->setMinimumWidth(100); + m_launchButton->setMinimumHeight(35); + connect(m_launchButton, &QPushButton::clicked, this, &PackageDetailsDialog::launchApplication); + buttonLayout->addWidget(m_launchButton); + + m_closeButton->setMinimumWidth(100); + m_closeButton->setMinimumHeight(35); + connect(m_closeButton, &QPushButton::clicked, this, &QDialog::accept); + buttonLayout->addWidget(m_closeButton); + + dialogLayout->addWidget(buttonWidget, 0); + + // Progress bar section (hidden by default) + auto* progressLayout = new QVBoxLayout(m_progressWidget); + progressLayout->setContentsMargins(20, 0, 20, 20); + progressLayout->setSpacing(8); + + m_progressLabel->setStyleSheet("color: #a1a1aa; font-size: 12px;"); + m_progressLabel->setAlignment(Qt::AlignCenter); + progressLayout->addWidget(m_progressLabel); + + m_progressBar->setMinimumHeight(20); + m_progressBar->setMaximumHeight(20); + m_progressBar->setTextVisible(true); + m_progressBar->setFormat("%p%"); + m_progressBar->setStyleSheet( + "QProgressBar {" + " border: none;" + " border-radius: 4px;" + " background-color: #27272a;" + " color: #fafafa;" + " text-align: center;" + " font-size: 11px;" + "}" + "QProgressBar::chunk {" + " border-radius: 4px;" + " background-color: #3b82f6;" + "}" + ); + progressLayout->addWidget(m_progressBar); + + // Toggle log button + m_toggleLogButton->setStyleSheet( + "QPushButton {" + " background: none;" + " border: none;" + " color: #3b82f6;" + " text-decoration: underline;" + " font-size: 11px;" + " padding: 4px;" + "}" + "QPushButton:hover {" + " color: #60a5fa;" + "}" + ); + connect(m_toggleLogButton, &QPushButton::clicked, this, &PackageDetailsDialog::toggleLogViewer); + progressLayout->addWidget(m_toggleLogButton, 0, Qt::AlignCenter); + + m_progressWidget->hide(); + dialogLayout->addWidget(m_progressWidget, 0); + + // Log viewer section (hidden by default) + auto* logLayout = new QVBoxLayout(m_logWidget); + logLayout->setContentsMargins(20, 0, 20, 20); + logLayout->setSpacing(8); + + m_logViewer->setReadOnly(true); + m_logViewer->setMaximumHeight(200); + m_logViewer->setStyleSheet( + "QTextEdit {" + " background-color: #18181b;" + " border: 1px solid #27272a;" + " border-radius: 6px;" + " padding: 8px;" + " font-family: monospace;" + " font-size: 11px;" + " color: #a1a1aa;" + "}" + ); + logLayout->addWidget(m_logViewer); + + m_logWidget->hide(); + dialogLayout->addWidget(m_logWidget, 0); + + setLayout(dialogLayout); +} + +void PackageDetailsDialog::checkInstallStatus() { + m_isInstalled = AlpmWrapper::instance().isPackageInstalled(m_info.name); + // Update the installed badge in the header + if (m_isInstalled) { + m_statusBadge->show(); + } else { + m_statusBadge->hide(); + } +} + +void PackageDetailsDialog::updateButtonStates() { + m_installButton->setEnabled(!m_isInstalled); + m_uninstallButton->setEnabled(m_isInstalled); + + // Enable launch button only if installed and has a desktop file + QString desktopFile = findDesktopFile(); + m_launchButton->setEnabled(m_isInstalled && !desktopFile.isEmpty()); +} + +void PackageDetailsDialog::onInstall() { + auto reply = QMessageBox::question(this, "Install Package", + QString("Are you sure you want to install %1?").arg(m_info.name), + QMessageBox::Yes | QMessageBox::No); + + if (reply == QMessageBox::Yes) { + Logger::info(QString("Installing package: %1").arg(m_info.name)); + + // Disable buttons during operation + m_installButton->setEnabled(false); + m_uninstallButton->setEnabled(false); + m_launchButton->setEnabled(false); + m_closeButton->setEnabled(false); + + PackageManager::instance().installPackage(m_info.name, m_info.repository); + } +} + +void PackageDetailsDialog::onUninstall() { + auto reply = QMessageBox::question(this, "Uninstall Package", + QString("Are you sure you want to uninstall %1?\n\n" + "This will remove the package and skip dependency checks.") + .arg(m_info.name), + QMessageBox::Yes | QMessageBox::No); + + if (reply == QMessageBox::Yes) { + Logger::info(QString("Uninstalling package: %1").arg(m_info.name)); + + // Disable buttons during operation + m_installButton->setEnabled(false); + m_uninstallButton->setEnabled(false); + m_launchButton->setEnabled(false); + m_closeButton->setEnabled(false); + + PackageManager::instance().uninstallPackage(m_info.name, m_info.repository); + } +} + +void PackageDetailsDialog::showProgress(const QString& message) { + m_progressLabel->setText(message); + m_progressBar->setRange(0, 100); + m_progressBar->setValue(0); + m_progressWidget->show(); + m_progressBar->show(); + m_progressLabel->show(); + m_logViewer->clear(); + m_currentOperation = message; + m_totalPackages = 0; + m_currentPackage = 0; +} + +void PackageDetailsDialog::hideProgress() { + // Hide the progress bar and label, but keep the widget and toggle button visible + m_progressBar->hide(); + m_progressLabel->hide(); + // Don't hide m_progressWidget - keeps the toggle button visible + // Don't hide the log widget or reset log visibility + // This allows users to review logs after operation completes +} + +void PackageDetailsDialog::toggleLogViewer() { + m_logVisible = !m_logVisible; + if (m_logVisible) { + m_logWidget->show(); + m_toggleLogButton->setText("Hide Logs"); + } else { + m_logWidget->hide(); + m_toggleLogButton->setText("Show Logs"); + } +} + +void PackageDetailsDialog::parseProgressOutput(const QString& output) { + // Parse pacman/yay/paru output for progress information + + // Pattern: "downloading..." or "installing..." + if (output.contains("downloading", Qt::CaseInsensitive)) { + m_progressLabel->setText("Downloading packages..."); + } else if (output.contains("installing", Qt::CaseInsensitive)) { + m_progressLabel->setText("Installing packages..."); + } else if (output.contains("building", Qt::CaseInsensitive)) { + m_progressLabel->setText("Building packages..."); + } else if (output.contains("checking", Qt::CaseInsensitive)) { + m_progressLabel->setText("Checking dependencies..."); + } else if (output.contains("resolving", Qt::CaseInsensitive)) { + m_progressLabel->setText("Resolving dependencies..."); + } + + // Pattern: "(1/5)" or "( 1/5)" to track package progress + QRegularExpression packagePattern(R"(\(\s*(\d+)/(\d+)\))"); + auto match = packagePattern.match(output); + if (match.hasMatch()) { + m_currentPackage = match.captured(1).toInt(); + m_totalPackages = match.captured(2).toInt(); + + if (m_totalPackages > 0) { + int percentage = (m_currentPackage * 100) / m_totalPackages; + m_progressBar->setValue(percentage); + } + } + + // Pattern: "[##########] 100%" for download progress + QRegularExpression percentPattern(R"(\s+(\d+)%\s*)"); + auto percentMatch = percentPattern.match(output); + if (percentMatch.hasMatch()) { + int percentage = percentMatch.captured(1).toInt(); + m_progressBar->setValue(percentage); + } +} + +void PackageDetailsDialog::onOperationStarted(const QString& message) { + showProgress(message); +} + +void PackageDetailsDialog::onOperationOutput(const QString& output) { + if (output.trimmed().isEmpty()) { + return; + } + + // Add to log viewer + m_logViewer->append(output.trimmed()); + + // Auto-scroll to bottom + QTextCursor cursor = m_logViewer->textCursor(); + cursor.movePosition(QTextCursor::End); + m_logViewer->setTextCursor(cursor); + + // Parse output for progress information + parseProgressOutput(output); + + // Force UI update + m_progressLabel->repaint(); + m_progressBar->repaint(); + QCoreApplication::processEvents(); +} + +void PackageDetailsDialog::onOperationCompleted(bool success, const QString& message) { + // Refresh ALPM state so subsequent queries reflect the change + AlpmWrapper::instance().release(); + AlpmWrapper::instance().initialize(); + + // Update status and UI + checkInstallStatus(); + updateButtonStates(); + + hideProgress(); + + // Re-enable close button + m_closeButton->setEnabled(true); + + if (success) { + QMessageBox::information(this, "Success", message); + } else { + QMessageBox::warning(this, "Operation Failed", message); + } +} + +void PackageDetailsDialog::onOperationError(const QString& error) { + // Refresh ALPM state (best-effort) + AlpmWrapper::instance().release(); + AlpmWrapper::instance().initialize(); + + // Update status and UI + checkInstallStatus(); + updateButtonStates(); + + hideProgress(); + + // Re-enable close button + m_closeButton->setEnabled(true); + + QMessageBox::critical(this, "Error", error); +} + +QString PackageDetailsDialog::findDesktopFile() const { + if (!m_isInstalled) { + return QString(); + } + + // Common locations for .desktop files + QStringList desktopDirs = { + "/usr/share/applications", + "/usr/local/share/applications", + QDir::homePath() + "/.local/share/applications" + }; + + // Try to find desktop file matching the package name + // Common patterns: package.desktop, package-*.desktop + QStringList patterns = { + m_info.name + ".desktop", + m_info.name + "-*.desktop" + }; + + for (const QString& dir : desktopDirs) { + QDir desktopDir(dir); + if (!desktopDir.exists()) { + continue; + } + + // First try exact patterns + for (const QString& pattern : patterns) { + QStringList matches = desktopDir.entryList(QStringList() << pattern, QDir::Files); + if (!matches.isEmpty()) { + QString desktopFile = desktopDir.absoluteFilePath(matches.first()); + Logger::info(QString("Found desktop file for %1: %2").arg(m_info.name, desktopFile)); + return desktopFile; + } + } + + // If not found, try fuzzy matching with all .desktop files + QStringList allDesktopFiles = desktopDir.entryList(QStringList() << "*.desktop", QDir::Files); + + // Create regex patterns for fuzzy matching + // Handle reverse domain names: com.obsproject.Studio.desktop -> obs-studio + // Handle simple names: code.desktop -> visual-studio-code-bin + QString packageNameLower = m_info.name.toLower(); + QStringList nameVariants; + + // Add the full package name + nameVariants << packageNameLower; + + // Extract keywords from package name (split by dash and underscore) + QStringList parts = packageNameLower.split(QRegularExpression("[-_]")); + QStringList significantParts; + for (const QString& part : parts) { + if (part.length() > 3) { // Skip very short parts to avoid false matches + significantParts << part; + } + } + + // Special handling for common patterns + QStringList specialVariants; + if (packageNameLower.contains("visual-studio-code")) { + specialVariants << "vscode" << "code"; + } else if (packageNameLower == "obs-studio") { + // For obs-studio, look for obsproject specifically + specialVariants << "obsproject"; + } + + // Try special variants first (highest priority) + for (const QString& variant : specialVariants) { + for (const QString& desktopFileName : allDesktopFiles) { + QString fileNameLower = desktopFileName.toLower(); + + if (fileNameLower.contains(variant)) { + QString desktopFile = desktopDir.absoluteFilePath(desktopFileName); + + if (verifyDesktopFile(desktopFile, nameVariants + specialVariants + significantParts)) { + Logger::info(QString("Found desktop file for %1 via special match: %2") + .arg(m_info.name, desktopFile)); + return desktopFile; + } + } + } + } + + // Try full package name match + for (const QString& desktopFileName : allDesktopFiles) { + QString fileNameLower = desktopFileName.toLower(); + + if (fileNameLower.contains(packageNameLower)) { + QString desktopFile = desktopDir.absoluteFilePath(desktopFileName); + + if (verifyDesktopFile(desktopFile, nameVariants + specialVariants + significantParts)) { + Logger::info(QString("Found desktop file for %1 via full name match: %2") + .arg(m_info.name, desktopFile)); + return desktopFile; + } + } + } + + // Finally, try matching individual significant parts (but verify carefully) + for (const QString& part : significantParts) { + for (const QString& desktopFileName : allDesktopFiles) { + QString fileNameLower = desktopFileName.toLower(); + + // Use word boundary-like matching: ensure part is not in the middle of another word + // Check if part appears as a separate component (after . or at start, before . or -) + QRegularExpression wordBoundary(QString("(^|[._-])%1([._-]|$)").arg(QRegularExpression::escape(part))); + + if (wordBoundary.match(fileNameLower).hasMatch()) { + QString desktopFile = desktopDir.absoluteFilePath(desktopFileName); + + if (verifyDesktopFile(desktopFile, nameVariants + specialVariants + significantParts)) { + Logger::info(QString("Found desktop file for %1 via word match: %2") + .arg(m_info.name, desktopFile)); + return desktopFile; + } + } + } + } + } + + Logger::debug(QString("No desktop file found for package: %1").arg(m_info.name)); + return QString(); +} + +bool PackageDetailsDialog::verifyDesktopFile(const QString& desktopFilePath, + const QStringList& nameVariants) const { + QFile file(desktopFilePath); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + return false; + } + + QTextStream in(&file); + QString content = in.readAll(); + file.close(); + + // Check if Exec line contains any of our name variants + QRegularExpression execPattern(R"(^Exec=(.*)$)", QRegularExpression::MultilineOption); + auto match = execPattern.match(content); + + if (match.hasMatch()) { + QString execLine = match.captured(1).toLower(); + + // Check if any variant appears in the Exec line + for (const QString& variant : nameVariants) { + if (execLine.contains(variant)) { + return true; + } + } + } + + // Also check Name field as a fallback + QRegularExpression namePattern(R"(^Name=(.*)$)", QRegularExpression::MultilineOption); + match = namePattern.match(content); + + if (match.hasMatch()) { + QString nameField = match.captured(1).toLower(); + + for (const QString& variant : nameVariants) { + if (nameField.contains(variant)) { + return true; + } + } + } + + return false; +} + +void PackageDetailsDialog::launchApplication() { + QString desktopFile = findDesktopFile(); + + if (desktopFile.isEmpty()) { + QMessageBox::warning(this, "Launch Failed", + QString("Could not find a desktop file for %1.\n" + "This application may not have a graphical interface or " + "may need to be launched from the terminal.").arg(m_info.name)); + return; + } + + // Launch the application using gtk-launch or similar + QProcess* process = new QProcess(this); + + // Try gtk-launch first (works on most desktop environments) + QString baseName = QFileInfo(desktopFile).fileName(); + + connect(process, QOverload::of(&QProcess::finished), + this, [this, process, baseName](int exitCode, QProcess::ExitStatus exitStatus) { + process->deleteLater(); + + if (exitCode != 0 || exitStatus != QProcess::NormalExit) { + Logger::error(QString("Failed to launch application: %1").arg(baseName)); + QMessageBox::warning(this, "Launch Failed", + QString("Failed to launch %1.\n" + "Exit code: %2").arg(m_info.name).arg(exitCode)); + } else { + Logger::info(QString("Successfully launched: %1").arg(baseName)); + } + }); + + // Try gtk-launch first + process->start("gtk-launch", QStringList() << baseName); + + // If gtk-launch doesn't start, try alternative methods + if (!process->waitForStarted(1000)) { + // Try dex (Desktop Entry Execution) + process->start("dex", QStringList() << desktopFile); + + if (!process->waitForStarted(1000)) { + // Try exo-open (XFCE) + process->start("exo-open", QStringList() << desktopFile); + + if (!process->waitForStarted(1000)) { + // Last resort: try to parse and execute the Exec line + process->deleteLater(); + QMessageBox::warning(this, "Launch Failed", + "Could not find a suitable desktop file launcher.\n" + "Please install gtk-launch, dex, or exo-open."); + Logger::error("No desktop file launcher available"); + } + } + } +} diff --git a/src/gui/package_details_dialog.h b/src/gui/package_details_dialog.h new file mode 100644 index 0000000..4a8a324 --- /dev/null +++ b/src/gui/package_details_dialog.h @@ -0,0 +1,81 @@ +#ifndef PACKAGE_DETAILS_DIALOG_H +#define PACKAGE_DETAILS_DIALOG_H + +#include +#include +#include +#include +#include +#include "../utils/types.h" + +/** + * @brief Dialog showing detailed package information and actions. + * + * Memory Management: + * - All Qt widget members use Qt parent-child ownership (raw pointers are non-owning) + */ +class PackageDetailsDialog : public QDialog { + Q_OBJECT + +public: + explicit PackageDetailsDialog(const PackageInfo& info, QWidget* parent = nullptr); + ~PackageDetailsDialog() override = default; + +private: + void setupUi(); + void updateButtonStates(); + void checkInstallStatus(); + void showProgress(const QString& message); + void hideProgress(); + void toggleLogViewer(); + void parseProgressOutput(const QString& output); + QString findDesktopFile() const; + bool verifyDesktopFile(const QString& desktopFilePath, + const QStringList& nameVariants) const; + void launchApplication(); + + PackageInfo m_info; + bool m_isInstalled = false; + + // Qt parent-child managed widgets (non-owning pointers) + QLabel* m_nameLabel = nullptr; + QLabel* m_versionLabel = nullptr; + QLabel* m_repositoryLabel = nullptr; + QLabel* m_maintainerLabel = nullptr; + QLabel* m_urlLabel = nullptr; + QTextEdit* m_descriptionText = nullptr; + QTextEdit* m_dependenciesText = nullptr; + QLabel* m_lastUpdatedLabel = nullptr; + + QPushButton* m_installButton = nullptr; + QPushButton* m_uninstallButton = nullptr; + QPushButton* m_launchButton = nullptr; + QPushButton* m_closeButton = nullptr; + + QLabel* m_statusBadge = nullptr; + + QProgressBar* m_progressBar = nullptr; + QLabel* m_progressLabel = nullptr; + QWidget* m_progressWidget = nullptr; + + // Log viewer (Qt parent-child managed) + QTextEdit* m_logViewer = nullptr; + QPushButton* m_toggleLogButton = nullptr; + QWidget* m_logWidget = nullptr; + bool m_logVisible = false; + + // Progress tracking + QString m_currentOperation; + int m_totalPackages = 0; + int m_currentPackage = 0; + +private slots: + void onInstall(); + void onUninstall(); + void onOperationStarted(const QString& message); + void onOperationOutput(const QString& output); + void onOperationCompleted(bool success, const QString& message); + void onOperationError(const QString& error); +}; + +#endif // PACKAGE_DETAILS_DIALOG_H diff --git a/src/gui/search_widget.cpp b/src/gui/search_widget.cpp new file mode 100644 index 0000000..d4a7e20 --- /dev/null +++ b/src/gui/search_widget.cpp @@ -0,0 +1,293 @@ +#include "search_widget.h" +#include "package_card.h" +#include "package_details_dialog.h" +#include "../core/alpm_wrapper.h" +#include "../core/aur_helper.h" +#include "../utils/logger.h" +#include +#include +#include +#include +#include + +SearchWidget::SearchWidget(QWidget* parent) + : QWidget(parent) + , m_searchInput(new QLineEdit(this)) + , m_searchButton(new QPushButton("Search", this)) + , m_filterCombo(new QComboBox(this)) + , m_scrollArea(new QScrollArea(this)) + , m_contentWidget(new QWidget()) + , m_gridLayout(new QGridLayout(m_contentWidget)) + , m_statusLabel(new QLabel(this)) + , m_aurHelper(std::make_unique(this)) { + + setupUi(); + + connect(m_aurHelper.get(), &AurHelper::searchCompleted, + this, &SearchWidget::onAurSearchCompleted); +} + +void SearchWidget::setupUi() { + auto* mainLayout = new QVBoxLayout(this); + + // Title + auto* titleLabel = new QLabel("Search Packages", this); + auto titleFont = titleLabel->font(); + titleFont.setPointSize(24); + titleFont.setBold(true); + titleLabel->setFont(titleFont); + mainLayout->addWidget(titleLabel); + + // Search bar + auto* searchLayout = new QHBoxLayout(); + + m_searchInput->setPlaceholderText("Search for packages..."); + m_searchInput->setMinimumHeight(35); + m_searchInput->setClearButtonEnabled(true); + connect(m_searchInput, &QLineEdit::returnPressed, this, &SearchWidget::onSearchClicked); + searchLayout->addWidget(m_searchInput, 1); + + // Filter combo + m_filterCombo->addItem("All", "all"); + m_filterCombo->addItem("Core", "core"); + m_filterCombo->addItem("Extra", "extra"); + m_filterCombo->addItem("AUR", "AUR"); + m_filterCombo->setMinimumHeight(35); + connect(m_filterCombo, QOverload::of(&QComboBox::currentIndexChanged), + this, &SearchWidget::onFilterChanged); + searchLayout->addWidget(m_filterCombo); + + m_searchButton->setMinimumHeight(35); + m_searchButton->setMinimumWidth(100); + connect(m_searchButton, &QPushButton::clicked, this, &SearchWidget::onSearchClicked); + searchLayout->addWidget(m_searchButton); + + mainLayout->addLayout(searchLayout); + + // Status label + m_statusLabel->setAlignment(Qt::AlignCenter); + m_statusLabel->hide(); + mainLayout->addWidget(m_statusLabel); + + // Results area + m_scrollArea->setWidget(m_contentWidget); + m_scrollArea->setWidgetResizable(true); + m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + m_gridLayout->setSpacing(15); + m_gridLayout->setContentsMargins(10, 10, 10, 10); + + mainLayout->addWidget(m_scrollArea); + setLayout(mainLayout); +} + +void SearchWidget::onSearchClicked() { + QString query = m_searchInput->text().trimmed(); + + if (query.isEmpty()) { + m_statusLabel->setText("Please enter a search term"); + m_statusLabel->show(); + return; + } + + m_searchButton->setEnabled(false); + m_searchButton->setText("Searching..."); + m_statusLabel->setText("Searching..."); + m_statusLabel->show(); + + clearResults(); + performSearch(); +} + +void SearchWidget::performSearch() { + QString query = m_searchInput->text().trimmed().toLower().replace(' ', '-'); + + // Search in official repos using ALPM + (void)QtConcurrent::run([this, query]() { + auto results = AlpmWrapper::instance().searchPackages(query); + + QMetaObject::invokeMethod(this, [this, results]() { + m_allResults = results; + + // Also search AUR + m_aurHelper->searchPackages(m_searchInput->text().trimmed()); + }, Qt::QueuedConnection); + }); +} + +void SearchWidget::onAurSearchCompleted(const QVector& results) { + // Combine with official repo results + m_allResults.append(results); + + m_searchButton->setEnabled(true); + m_searchButton->setText("Search"); + + if (m_allResults.isEmpty()) { + m_statusLabel->setText("No results found"); + return; + } + + m_statusLabel->hide(); + + // Apply filter + onFilterChanged(m_filterCombo->currentIndex()); + + Logger::info(QString("Search completed: %1 results").arg(m_allResults.size())); +} + +void SearchWidget::onFilterChanged(int index) { + QString filter = m_filterCombo->itemData(index).toString(); + + if (filter == "all") { + displayResults(m_allResults); + } else { + QVector filtered; + for (const auto& pkg : m_allResults) { + if (pkg.repository == filter) { + filtered.append(pkg); + } + } + displayResults(filtered); + } +} + +void SearchWidget::displayResults(const QVector& results) { + clearResults(); + m_currentResults = results; + + if (results.isEmpty()) { + m_statusLabel->setText("No results found for selected filter"); + m_statusLabel->show(); + return; + } + + int row = 0; + int col = 0; + const int columns = 3; + + for (const auto& pkg : results) { + auto* card = new PackageCard(pkg, m_contentWidget); + connect(card, &PackageCard::clicked, this, &SearchWidget::onPackageClicked); + + m_gridLayout->addWidget(card, row, col); + + col++; + if (col >= columns) { + col = 0; + row++; + } + } + + m_gridLayout->setRowStretch(row + 1, 1); +} + +void SearchWidget::clearResults() { + while (auto* item = m_gridLayout->takeAt(0)) { + if (auto* widget = item->widget()) { + widget->deleteLater(); + } + delete item; + } +} + +void SearchWidget::onPackageClicked(const PackageInfo& info) { + Logger::info(QString("Package clicked: %1").arg(info.name)); + + PackageInfo fullInfo = info; + + // For AUR packages, fetch complete details including maintainer, URL, dependencies + if (info.repository.toLower() == "aur") { + auto* aurHelper = new AurHelper(this); + QEventLoop loop; + + connect(aurHelper, &AurHelper::packageInfoReceived, [&](const PackageInfo& detailedInfo) { + fullInfo = detailedInfo; + loop.quit(); + }); + + connect(aurHelper, &AurHelper::error, [&](const QString& errorMsg) { + Logger::warning(QString("Failed to fetch AUR details for %1: %2").arg(info.name, errorMsg)); + loop.quit(); + }); + + aurHelper->getPackageInfo(info.name); + loop.exec(); + + aurHelper->deleteLater(); + } + + auto* dialog = new PackageDetailsDialog(fullInfo, this); + dialog->exec(); + dialog->deleteLater(); +} + +void SearchWidget::updateRepositoryList(bool multilibEnabled, bool chaoticAurEnabled) { + // Save the current selection + int currentIndex = m_filterCombo->currentIndex(); + QString currentFilter = m_filterCombo->itemData(currentIndex).toString(); + + // Check if multilib already exists in the list + bool multilibExists = false; + for (int i = 0; i < m_filterCombo->count(); ++i) { + if (m_filterCombo->itemData(i).toString() == "multilib") { + multilibExists = true; + break; + } + } + + // Check if chaotic-aur already exists in the list + bool chaoticAurExists = false; + for (int i = 0; i < m_filterCombo->count(); ++i) { + if (m_filterCombo->itemData(i).toString() == "chaotic-aur") { + chaoticAurExists = true; + break; + } + } + + // Handle multilib + if (multilibEnabled && !multilibExists) { + // Add multilib to the dropdown (insert before AUR) + int aurIndex = m_filterCombo->findData("AUR"); + if (aurIndex != -1) { + m_filterCombo->insertItem(aurIndex, "Multilib", "multilib"); + } else { + m_filterCombo->addItem("Multilib", "multilib"); + } + Logger::info("Added multilib repository to search filter"); + } else if (!multilibEnabled && multilibExists) { + // Remove multilib from the dropdown + int multilibIndex = m_filterCombo->findData("multilib"); + if (multilibIndex != -1) { + m_filterCombo->removeItem(multilibIndex); + Logger::info("Removed multilib repository from search filter"); + } + } + + // Handle chaotic-aur + if (chaoticAurEnabled && !chaoticAurExists) { + // Add chaotic-aur to the dropdown (insert before AUR) + int aurIndex = m_filterCombo->findData("AUR"); + if (aurIndex != -1) { + m_filterCombo->insertItem(aurIndex, "Chaotic-AUR", "chaotic-aur"); + } else { + m_filterCombo->addItem("Chaotic-AUR", "chaotic-aur"); + } + Logger::info("Added chaotic-aur repository to search filter"); + } else if (!chaoticAurEnabled && chaoticAurExists) { + // Remove chaotic-aur from the dropdown + int chaoticAurIndex = m_filterCombo->findData("chaotic-aur"); + if (chaoticAurIndex != -1) { + m_filterCombo->removeItem(chaoticAurIndex); + Logger::info("Removed chaotic-aur repository from search filter"); + } + } + + // Restore previous selection if it still exists + int newIndex = m_filterCombo->findData(currentFilter); + if (newIndex != -1) { + m_filterCombo->setCurrentIndex(newIndex); + } else { + // If previous selection was removed, select "All" + m_filterCombo->setCurrentIndex(0); + } +} diff --git a/src/gui/search_widget.h b/src/gui/search_widget.h new file mode 100644 index 0000000..5767dcf --- /dev/null +++ b/src/gui/search_widget.h @@ -0,0 +1,62 @@ +#ifndef SEARCH_WIDGET_H +#define SEARCH_WIDGET_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../utils/types.h" +#include "../core/aur_helper.h" + +/** + * @brief Widget for searching packages across repositories. + * + * Memory Management: + * - m_aurHelper: Owned by std::unique_ptr for explicit lifetime management + * - All Qt widget members use Qt parent-child ownership (raw pointers are non-owning) + * - Package cards are dynamically created/destroyed in displayResults/clearResults + */ +class SearchWidget : public QWidget { + Q_OBJECT + +public: + explicit SearchWidget(QWidget* parent = nullptr); + ~SearchWidget() override = default; + +public slots: + void updateRepositoryList(bool multilibEnabled, bool chaoticAurEnabled = false); + +private: + void setupUi(); + void performSearch(); + void displayResults(const QVector& results); + void clearResults(); + + // Qt parent-child managed widgets (non-owning pointers) + QLineEdit* m_searchInput = nullptr; + QPushButton* m_searchButton = nullptr; + QComboBox* m_filterCombo = nullptr; + QScrollArea* m_scrollArea = nullptr; + QWidget* m_contentWidget = nullptr; + QGridLayout* m_gridLayout = nullptr; + QLabel* m_statusLabel = nullptr; + + // Owned resources + std::unique_ptr m_aurHelper; + + QVector m_currentResults; + QVector m_allResults; + +private slots: + void onSearchClicked(); + void onAurSearchCompleted(const QVector& results); + void onFilterChanged(int index); + void onPackageClicked(const PackageInfo& info); +}; + +#endif // SEARCH_WIDGET_H diff --git a/src/gui/settings_widget.cpp b/src/gui/settings_widget.cpp new file mode 100644 index 0000000..6d31e71 --- /dev/null +++ b/src/gui/settings_widget.cpp @@ -0,0 +1,1174 @@ +#include "settings_widget.h" +#include "../utils/logger.h" +#include "../core/alpm_wrapper.h" +#include "../core/package_manager.h" +#include +#include +#include +#include +#include +#include +#include +#include + +SettingsWidget::SettingsWidget(QWidget* parent) + : QWidget(parent) { + + setupUi(); + loadCurrentSettings(); + + Logger::info("SettingsWidget created successfully"); +} + +void SettingsWidget::setupUi() { + // Create main layout for the widget + auto* outerLayout = new QVBoxLayout(this); + outerLayout->setContentsMargins(0, 0, 0, 0); + + // Create scroll area + auto* scrollArea = new QScrollArea(this); + scrollArea->setWidgetResizable(true); + scrollArea->setFrameShape(QFrame::NoFrame); + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + + // Create content widget that will be scrollable + auto* contentWidget = new QWidget(); + auto* mainLayout = new QVBoxLayout(contentWidget); + mainLayout->setSpacing(20); + mainLayout->setContentsMargins(10, 10, 10, 10); + + // Title + auto* titleLabel = new QLabel("Settings", contentWidget); + auto titleFont = titleLabel->font(); + titleFont.setPointSize(24); + titleFont.setBold(true); + titleLabel->setFont(titleFont); + mainLayout->addWidget(titleLabel); + + // Repository Settings + createRepositorySettings(); + mainLayout->addWidget(m_repositoryGroup); + + // Chaotic-AUR Setup + createChaoticAurSettings(); + mainLayout->addWidget(m_chaoticAurGroup); + + // Maintenance Settings + createMaintenanceSettings(); + mainLayout->addWidget(m_maintenanceGroup); + + // Status label + m_statusLabel = new QLabel(contentWidget); + m_statusLabel->setAlignment(Qt::AlignCenter); + m_statusLabel->setStyleSheet("QLabel { color: #0066cc; padding: 10px; }"); + m_statusLabel->hide(); + mainLayout->addWidget(m_statusLabel); + + // Add stretch at the bottom + mainLayout->addStretch(); + + // Set the content widget to the scroll area + scrollArea->setWidget(contentWidget); + + // Add scroll area to the outer layout + outerLayout->addWidget(scrollArea); + + setLayout(outerLayout); +} + +void SettingsWidget::createRepositorySettings() { + m_repositoryGroup = new QGroupBox("Package Repositories", this); + auto* repoLayout = new QVBoxLayout(m_repositoryGroup); + + // Description + auto* descLabel = new QLabel( + "Select which package repositories to use for searching and installing packages.\n" + "Core and Extra repositories are required and cannot be disabled.", + this); + descLabel->setWordWrap(true); + descLabel->setStyleSheet("QLabel { color: #666; margin-bottom: 10px; }"); + repoLayout->addWidget(descLabel); + + // Core repository (always enabled, cannot be disabled) + m_coreRepoCheckbox = new QCheckBox("Core - Essential system packages", this); + m_coreRepoCheckbox->setChecked(true); + m_coreRepoCheckbox->setEnabled(false); + m_coreRepoCheckbox->setToolTip("Core repository is required and cannot be disabled"); + repoLayout->addWidget(m_coreRepoCheckbox); + + // Extra repository (always enabled, cannot be disabled) + m_extraRepoCheckbox = new QCheckBox("Extra - Additional official packages", this); + m_extraRepoCheckbox->setChecked(true); + m_extraRepoCheckbox->setEnabled(false); + m_extraRepoCheckbox->setToolTip("Extra repository is required and cannot be disabled"); + repoLayout->addWidget(m_extraRepoCheckbox); + + // Multilib repository (optional, can be enabled/disabled) + m_multilibRepoCheckbox = new QCheckBox("Multilib - 32-bit packages on x86_64", this); + m_multilibRepoCheckbox->setToolTip( + "Enable multilib repository for 32-bit applications on 64-bit systems.\n" + "This modifies /etc/pacman.conf and requires administrator privileges."); + connect(m_multilibRepoCheckbox, &QCheckBox::checkStateChanged, + this, &SettingsWidget::onSettingsChanged); + repoLayout->addWidget(m_multilibRepoCheckbox); + + // Chaotic-AUR repository (optional, can be enabled/disabled) + m_chaoticAurCheckbox = new QCheckBox("Chaotic-AUR - Pre-built AUR packages", this); + m_chaoticAurCheckbox->setToolTip( + "Enable chaotic-aur repository for pre-built AUR packages.\n" + "This modifies /etc/pacman.conf and requires administrator privileges.\n" + "Note: chaotic-keyring and chaotic-mirrorlist must be installed first."); + connect(m_chaoticAurCheckbox, &QCheckBox::checkStateChanged, + this, &SettingsWidget::onSettingsChanged); + repoLayout->addWidget(m_chaoticAurCheckbox); + + // Info label + auto* infoLabel = new QLabel( + "Note: Changes to repositories require modifying system configuration files " + "and may require administrator privileges.", + this); + infoLabel->setWordWrap(true); + infoLabel->setStyleSheet("QLabel { color: #888; font-style: italic; margin-top: 10px; }"); + repoLayout->addWidget(infoLabel); + + // Apply button + auto* applyLayout = new QHBoxLayout(); + applyLayout->addStretch(); + + m_applyButton = new QPushButton("Apply Changes", this); + m_applyButton->setMinimumWidth(150); + m_applyButton->setEnabled(false); + connect(m_applyButton, &QPushButton::clicked, this, &SettingsWidget::onApplyClicked); + applyLayout->addWidget(m_applyButton); + + repoLayout->addLayout(applyLayout); + + m_repositoryGroup->setLayout(repoLayout); +} + +void SettingsWidget::createChaoticAurSettings() { + m_chaoticAurGroup = new QGroupBox("Chaotic-AUR Setup", this); + auto* chaoticLayout = new QVBoxLayout(m_chaoticAurGroup); + + // Description + auto* descLabel = new QLabel( + "Chaotic-AUR provides pre-built AUR packages, making installation faster and easier.\n" + "Setup requires installing the keyring and mirrorlist packages.", + this); + descLabel->setWordWrap(true); + descLabel->setStyleSheet("QLabel { color: #666; margin-bottom: 10px; }"); + chaoticLayout->addWidget(descLabel); + + // Setup button section + auto* setupLayout = new QHBoxLayout(); + + auto* setupLabel = new QLabel("Install Chaotic-AUR:", this); + setupLabel->setStyleSheet("QLabel { font-weight: bold; }"); + setupLayout->addWidget(setupLabel); + + setupLayout->addStretch(); + + m_setupChaoticButton = new QPushButton("Setup Chaotic-AUR", this); + m_setupChaoticButton->setMinimumWidth(150); + m_setupChaoticButton->setToolTip( + "Install chaotic-keyring and chaotic-mirrorlist packages.\n" + "This will enable access to pre-built AUR packages."); + connect(m_setupChaoticButton, &QPushButton::clicked, this, &SettingsWidget::onSetupChaoticClicked); + setupLayout->addWidget(m_setupChaoticButton); + + chaoticLayout->addLayout(setupLayout); + + // Setup info + auto* setupInfoLabel = new QLabel( + "This will install chaotic-keyring and chaotic-mirrorlist from the official Chaotic-AUR repository.", + this); + setupInfoLabel->setWordWrap(true); + setupInfoLabel->setStyleSheet("QLabel { color: #888; font-size: 11px; margin-top: 5px; margin-left: 10px; }"); + chaoticLayout->addWidget(setupInfoLabel); + + // Spacer + chaoticLayout->addSpacing(10); + + // Remove button section + auto* removeLayout = new QHBoxLayout(); + + auto* removeLabel = new QLabel("Remove Chaotic-AUR:", this); + removeLabel->setStyleSheet("QLabel { font-weight: bold; }"); + removeLayout->addWidget(removeLabel); + + removeLayout->addStretch(); + + m_removeChaoticButton = new QPushButton("Remove Chaotic-AUR", this); + m_removeChaoticButton->setMinimumWidth(150); + m_removeChaoticButton->setToolTip( + "Remove chaotic-keyring and chaotic-mirrorlist packages.\n" + "You may need to manually remove the repository from /etc/pacman.conf"); + connect(m_removeChaoticButton, &QPushButton::clicked, this, &SettingsWidget::onRemoveChaoticClicked); + removeLayout->addWidget(m_removeChaoticButton); + + chaoticLayout->addLayout(removeLayout); + + // Remove info + auto* removeInfoLabel = new QLabel( + "This will remove the Chaotic-AUR packages. You need to uncheck the Chaotic-AUR option above or manually edit /etc/pacman.conf to remove the repository configuration.", + this); + removeInfoLabel->setWordWrap(true); + removeInfoLabel->setStyleSheet("QLabel { color: #888; font-size: 11px; margin-top: 5px; margin-left: 10px; }"); + chaoticLayout->addWidget(removeInfoLabel); + + m_chaoticAurGroup->setLayout(chaoticLayout); +} + +void SettingsWidget::createMaintenanceSettings() { + m_maintenanceGroup = new QGroupBox("Maintenance", this); + auto* maintenanceLayout = new QVBoxLayout(m_maintenanceGroup); + + // Description + auto* descLabel = new QLabel( + "System maintenance and troubleshooting tools.", + this); + descLabel->setWordWrap(true); + descLabel->setStyleSheet("QLabel { color: #666; margin-bottom: 10px; }"); + maintenanceLayout->addWidget(descLabel); + + // Lock file section + auto* lockFileLayout = new QHBoxLayout(); + + auto* lockFileLabel = new QLabel( + "Pacman Database Lock:", + this); + lockFileLabel->setStyleSheet("QLabel { font-weight: bold; }"); + lockFileLayout->addWidget(lockFileLabel); + + lockFileLayout->addStretch(); + + m_removeLockButton = new QPushButton("Remove Lock File", this); + m_removeLockButton->setMinimumWidth(150); + m_removeLockButton->setToolTip( + "Remove /var/lib/pacman/db.lck if pacman is stuck.\n" + "Only use this if you're sure no other pacman process is running."); + connect(m_removeLockButton, &QPushButton::clicked, this, &SettingsWidget::onRemoveLockClicked); + lockFileLayout->addWidget(m_removeLockButton); + + maintenanceLayout->addLayout(lockFileLayout); + + // Lock file info + auto* lockInfoLabel = new QLabel( + "If pacman was interrupted, it may leave a lock file that prevents other operations.\n" + "Remove it only if you're certain no package manager is currently running.", + this); + lockInfoLabel->setWordWrap(true); + lockInfoLabel->setStyleSheet("QLabel { color: #888; font-size: 11px; margin-top: 5px; margin-left: 10px; }"); + maintenanceLayout->addWidget(lockInfoLabel); + + // Spacer + maintenanceLayout->addSpacing(15); + + // Sync repositories section + auto* syncReposLayout = new QHBoxLayout(); + + auto* syncReposLabel = new QLabel( + "Synchronize Repositories:", + this); + syncReposLabel->setStyleSheet("QLabel { font-weight: bold; }"); + syncReposLayout->addWidget(syncReposLabel); + + syncReposLayout->addStretch(); + + m_syncReposButton = new QPushButton("Sync Repositories", this); + m_syncReposButton->setMinimumWidth(150); + m_syncReposButton->setToolTip( + "Manually synchronize package databases (pacman -Sy).\n" + "This updates the list of available packages from all enabled repositories."); + connect(m_syncReposButton, &QPushButton::clicked, this, &SettingsWidget::onSyncReposClicked); + syncReposLayout->addWidget(m_syncReposButton); + + maintenanceLayout->addLayout(syncReposLayout); + + // Sync info + auto* syncInfoLabel = new QLabel( + "Use this to manually update your package database. This is useful after enabling/disabling repositories\n" + "or when you want to ensure you have the latest package information.", + this); + syncInfoLabel->setWordWrap(true); + syncInfoLabel->setStyleSheet("QLabel { color: #888; font-size: 11px; margin-top: 5px; margin-left: 10px; }"); + maintenanceLayout->addWidget(syncInfoLabel); + + // Spacer + maintenanceLayout->addSpacing(15); + + // Cancel running process section + auto* cancelProcessLayout = new QHBoxLayout(); + + auto* cancelProcessLabel = new QLabel( + "Cancel Running Process:", + this); + cancelProcessLabel->setStyleSheet("QLabel { font-weight: bold; }"); + cancelProcessLayout->addWidget(cancelProcessLabel); + + cancelProcessLayout->addStretch(); + + m_cancelProcessButton = new QPushButton("Cancel Process", this); + m_cancelProcessButton->setMinimumWidth(150); + m_cancelProcessButton->setToolTip( + "Cancel any running package operation (install, uninstall, update).\n" + "Use this if an operation is stuck or taking too long.\n" + "This is different from removing the lock file - it actually stops the running process."); + connect(m_cancelProcessButton, &QPushButton::clicked, this, &SettingsWidget::onCancelProcessClicked); + cancelProcessLayout->addWidget(m_cancelProcessButton); + + maintenanceLayout->addLayout(cancelProcessLayout); + + // Cancel process info + auto* cancelInfoLabel = new QLabel( + "Use this to cancel a stuck installation, uninstallation, or update process.\n" + "This is useful when you see 'Another operation is already in progress' and want to stop it.", + this); + cancelInfoLabel->setWordWrap(true); + cancelInfoLabel->setStyleSheet("QLabel { color: #888; font-size: 11px; margin-top: 5px; margin-left: 10px; }"); + maintenanceLayout->addWidget(cancelInfoLabel); + + m_maintenanceGroup->setLayout(maintenanceLayout); +} + +void SettingsWidget::loadCurrentSettings() { + // Check if multilib is currently enabled + bool multilibEnabled = isMultilibEnabledInPacmanConf(); + m_multilibRepoCheckbox->setChecked(multilibEnabled); + m_originalMultilibState = multilibEnabled; + + // Check if chaotic-aur is enabled + bool chaoticAurEnabled = isChaoticAurEnabledInPacmanConf(); + m_chaoticAurCheckbox->setChecked(chaoticAurEnabled); + m_originalChaoticAurState = chaoticAurEnabled; + + Logger::info(QString("Loaded settings: multilib=%1, chaotic-aur=%2") + .arg(multilibEnabled ? "enabled" : "disabled") + .arg(chaoticAurEnabled ? "enabled" : "disabled")); +} + +bool SettingsWidget::isMultilibEnabledInPacmanConf() const { + QFile file("/etc/pacman.conf"); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + Logger::error("Failed to open /etc/pacman.conf for reading"); + return false; + } + + QTextStream in(&file); + bool inMultilibSection = false; + + while (!in.atEnd()) { + QString line = in.readLine().trimmed(); + + // Check for [multilib] section header + if (line == "[multilib]") { + inMultilibSection = true; + continue; + } + + // If we found [multilib] section, check if it's not commented + if (inMultilibSection && !line.isEmpty() && !line.startsWith("#")) { + // If we find Include directive, multilib is enabled + if (line.startsWith("Include")) { + file.close(); + return true; + } + } + + // If we hit another section, stop + if (inMultilibSection && line.startsWith("[") && line != "[multilib]") { + break; + } + } + + file.close(); + return false; +} + +bool SettingsWidget::isChaoticAurEnabledInPacmanConf() const { + QFile file("/etc/pacman.conf"); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + Logger::error("Failed to open /etc/pacman.conf for reading"); + return false; + } + + QTextStream in(&file); + bool inChaoticAurSection = false; + + while (!in.atEnd()) { + QString line = in.readLine().trimmed(); + + // Check for [chaotic-aur] section header + if (line == "[chaotic-aur]") { + inChaoticAurSection = true; + continue; + } + + // If we found [chaotic-aur] section, check if it's not commented + if (inChaoticAurSection && !line.isEmpty() && !line.startsWith("#")) { + // If we find Include or Server directive, chaotic-aur is enabled + if (line.startsWith("Include") || line.startsWith("Server")) { + file.close(); + return true; + } + } + + // If we hit another section, stop + if (inChaoticAurSection && line.startsWith("[") && line != "[chaotic-aur]") { + break; + } + } + + file.close(); + return false; +} + +bool SettingsWidget::enableMultilibInPacmanConf() { + QFile file("/etc/pacman.conf"); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + Logger::error("Failed to open /etc/pacman.conf for reading"); + return false; + } + + QStringList lines; + QTextStream in(&file); + bool multilibSectionFound = false; + + while (!in.atEnd()) { + QString line = in.readLine(); + + // Check if this is a commented [multilib] section + if (line.trimmed() == "#[multilib]") { + lines.append("[multilib]"); + multilibSectionFound = true; + } + // Check if the Include line in multilib section is commented + else if (multilibSectionFound && line.trimmed().startsWith("#Include") && + line.contains("mirrorlist")) { + lines.append(line.mid(1)); // Remove the # comment character + multilibSectionFound = false; // Reset flag after processing + } + else { + lines.append(line); + } + } + file.close(); + + // Write back to file using pkexec for elevated privileges + QString tempFile = "/tmp/pacman.conf.tmp"; + QFile temp(tempFile); + if (!temp.open(QIODevice::WriteOnly | QIODevice::Text)) { + Logger::error("Failed to create temporary file"); + return false; + } + + QTextStream out(&temp); + for (const QString& line : lines) { + out << line << "\n"; + } + temp.close(); + + // Use pkexec to copy the file with elevated privileges + QProcess process; + process.start("pkexec", QStringList() << "cp" << tempFile << "/etc/pacman.conf"); + process.waitForFinished(30000); // 30 second timeout + + if (process.exitCode() != 0) { + Logger::error("Failed to update pacman.conf with elevated privileges"); + QFile::remove(tempFile); + return false; + } + + QFile::remove(tempFile); + Logger::info("Successfully enabled multilib repository"); + return true; +} + +bool SettingsWidget::disableMultilibInPacmanConf() { + QFile file("/etc/pacman.conf"); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + Logger::error("Failed to open /etc/pacman.conf for reading"); + return false; + } + + QStringList lines; + QTextStream in(&file); + bool inMultilibSection = false; + + while (!in.atEnd()) { + QString line = in.readLine(); + QString trimmedLine = line.trimmed(); + + // Check if this is [multilib] section + if (trimmedLine == "[multilib]") { + lines.append("#[multilib]"); + inMultilibSection = true; + } + // Check if we're in multilib section and this is the Include line + else if (inMultilibSection && trimmedLine.startsWith("Include") && + trimmedLine.contains("mirrorlist")) { + lines.append("#" + line); + inMultilibSection = false; + } + // Check if we hit another section + else if (trimmedLine.startsWith("[") && trimmedLine != "[multilib]") { + lines.append(line); + inMultilibSection = false; + } + else { + lines.append(line); + } + } + file.close(); + + // Write back to file using pkexec for elevated privileges + QString tempFile = "/tmp/pacman.conf.tmp"; + QFile temp(tempFile); + if (!temp.open(QIODevice::WriteOnly | QIODevice::Text)) { + Logger::error("Failed to create temporary file"); + return false; + } + + QTextStream out(&temp); + for (const QString& line : lines) { + out << line << "\n"; + } + temp.close(); + + // Use pkexec to copy the file with elevated privileges + QProcess process; + process.start("pkexec", QStringList() << "cp" << tempFile << "/etc/pacman.conf"); + process.waitForFinished(30000); // 30 second timeout + + if (process.exitCode() != 0) { + Logger::error("Failed to update pacman.conf with elevated privileges"); + QFile::remove(tempFile); + return false; + } + + QFile::remove(tempFile); + Logger::info("Successfully disabled multilib repository"); + return true; +} + +bool SettingsWidget::enableChaoticAurInPacmanConf() { + QFile file("/etc/pacman.conf"); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + Logger::error("Failed to open /etc/pacman.conf for reading"); + return false; + } + + QStringList lines; + QTextStream in(&file); + bool inCommentedChaoticAurSection = false; + bool chaoticAurSectionExists = false; + + while (!in.atEnd()) { + QString line = in.readLine(); + QString trimmedLine = line.trimmed(); + + // Check if chaotic-aur section already exists (uncommented or commented) + if (trimmedLine == "[chaotic-aur]" || trimmedLine == "#[chaotic-aur]") { + chaoticAurSectionExists = true; + + // If it's commented, uncomment it + if (trimmedLine == "#[chaotic-aur]") { + lines.append("[chaotic-aur]"); + inCommentedChaoticAurSection = true; + } else { + // Already uncommented, keep as is + lines.append(line); + } + } + // Check if the Include/Server line in chaotic-aur section is commented + else if (inCommentedChaoticAurSection && trimmedLine.startsWith("#") && + (trimmedLine.contains("Include") || trimmedLine.contains("Server"))) { + // Remove the # comment character + lines.append(line.mid(line.indexOf('#') + 1)); + inCommentedChaoticAurSection = false; + } + // Check if we hit another section, reset flag + else if (trimmedLine.startsWith("[") && trimmedLine != "[chaotic-aur]" && trimmedLine != "#[chaotic-aur]") { + lines.append(line); + inCommentedChaoticAurSection = false; + } + else { + lines.append(line); + } + } + file.close(); + + // If chaotic-aur section doesn't exist at all, add it + if (!chaoticAurSectionExists) { + lines.append(""); + lines.append("[chaotic-aur]"); + lines.append("Include = /etc/pacman.d/chaotic-mirrorlist"); + } + + // Write back to file using pkexec for elevated privileges + QString tempFile = "/tmp/pacman.conf.tmp"; + QFile temp(tempFile); + if (!temp.open(QIODevice::WriteOnly | QIODevice::Text)) { + Logger::error("Failed to create temporary file"); + return false; + } + + QTextStream out(&temp); + for (const QString& line : lines) { + out << line << "\n"; + } + temp.close(); + + // Use pkexec to copy the file with elevated privileges + QProcess process; + process.start("pkexec", QStringList() << "cp" << tempFile << "/etc/pacman.conf"); + process.waitForFinished(30000); // 30 second timeout + + if (process.exitCode() != 0) { + Logger::error("Failed to update pacman.conf with elevated privileges"); + QFile::remove(tempFile); + return false; + } + + QFile::remove(tempFile); + Logger::info("Successfully enabled chaotic-aur repository"); + return true; +} + +bool SettingsWidget::disableChaoticAurInPacmanConf() { + QFile file("/etc/pacman.conf"); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + Logger::error("Failed to open /etc/pacman.conf for reading"); + return false; + } + + QStringList lines; + QTextStream in(&file); + bool inChaoticAurSection = false; + + while (!in.atEnd()) { + QString line = in.readLine(); + QString trimmedLine = line.trimmed(); + + // Check if this is [chaotic-aur] section (uncommented or already commented) + if (trimmedLine == "[chaotic-aur]") { + lines.append("#[chaotic-aur]"); + inChaoticAurSection = true; + } + else if (trimmedLine == "#[chaotic-aur]") { + // Already commented, keep as is + lines.append(line); + inChaoticAurSection = false; + } + // Check if we're in chaotic-aur section and this is the Include/Server line (not already commented) + else if (inChaoticAurSection && !trimmedLine.startsWith("#") && + (trimmedLine.startsWith("Include") || trimmedLine.startsWith("Server"))) { + lines.append("#" + line); + inChaoticAurSection = false; + } + // Check if we hit another section + else if (trimmedLine.startsWith("[") && trimmedLine != "[chaotic-aur]" && trimmedLine != "#[chaotic-aur]") { + lines.append(line); + inChaoticAurSection = false; + } + else { + lines.append(line); + } + } + file.close(); + + // Write back to file using pkexec for elevated privileges + QString tempFile = "/tmp/pacman.conf.tmp"; + QFile temp(tempFile); + if (!temp.open(QIODevice::WriteOnly | QIODevice::Text)) { + Logger::error("Failed to create temporary file"); + return false; + } + + QTextStream out(&temp); + for (const QString& line : lines) { + out << line << "\n"; + } + temp.close(); + + // Use pkexec to copy the file with elevated privileges + QProcess process; + process.start("pkexec", QStringList() << "cp" << tempFile << "/etc/pacman.conf"); + process.waitForFinished(30000); // 30 second timeout + + if (process.exitCode() != 0) { + Logger::error("Failed to update pacman.conf with elevated privileges"); + QFile::remove(tempFile); + return false; + } + + QFile::remove(tempFile); + Logger::info("Successfully disabled chaotic-aur repository"); + return true; +} + +void SettingsWidget::onSettingsChanged() { + // Enable apply button when settings change + bool hasChanges = (m_multilibRepoCheckbox->isChecked() != m_originalMultilibState) || + (m_chaoticAurCheckbox->isChecked() != m_originalChaoticAurState); + m_applyButton->setEnabled(hasChanges); + m_statusLabel->hide(); +} + +void SettingsWidget::onApplyClicked() { + bool currentMultilibState = m_multilibRepoCheckbox->isChecked(); + bool currentChaoticAurState = m_chaoticAurCheckbox->isChecked(); + bool success = true; + bool changesApplied = false; + + // Handle multilib changes + if (currentMultilibState != m_originalMultilibState) { + // Show confirmation dialog + QString message; + if (currentMultilibState) { + message = "This will enable the multilib repository by modifying /etc/pacman.conf.\n" + "You will be prompted for administrator privileges.\n\n" + "After enabling, you should run 'sudo pacman -Sy' to sync the databases.\n\n" + "Do you want to continue?"; + } else { + message = "This will disable the multilib repository by modifying /etc/pacman.conf.\n" + "You will be prompted for administrator privileges.\n\n" + "Do you want to continue?"; + } + + auto reply = QMessageBox::question(this, "Confirm Repository Change", + message, + QMessageBox::Yes | QMessageBox::No); + + if (reply != QMessageBox::Yes) { + return; + } + + // Apply the change + if (currentMultilibState) { + success = enableMultilibInPacmanConf(); + } else { + success = disableMultilibInPacmanConf(); + } + + if (success) { + m_originalMultilibState = currentMultilibState; + changesApplied = true; + + // Emit signal to notify other widgets + emit multilibStatusChanged(currentMultilibState); + } else { + success = false; + } + } + + // Handle chaotic-aur changes + if (currentChaoticAurState != m_originalChaoticAurState) { + // Show confirmation dialog + QString message; + if (currentChaoticAurState) { + message = "This will enable the chaotic-aur repository by modifying /etc/pacman.conf.\n" + "You will be prompted for administrator privileges.\n\n" + "Note: Make sure chaotic-keyring and chaotic-mirrorlist are installed first.\n\n" + "After enabling, you should run 'sudo pacman -Sy' to sync the databases.\n\n" + "Do you want to continue?"; + } else { + message = "This will disable the chaotic-aur repository by modifying /etc/pacman.conf.\n" + "You will be prompted for administrator privileges.\n\n" + "Do you want to continue?"; + } + + auto reply = QMessageBox::question(this, "Confirm Repository Change", + message, + QMessageBox::Yes | QMessageBox::No); + + if (reply != QMessageBox::Yes) { + return; + } + + // Apply the change + bool chaoticSuccess = false; + if (currentChaoticAurState) { + chaoticSuccess = enableChaoticAurInPacmanConf(); + } else { + chaoticSuccess = disableChaoticAurInPacmanConf(); + } + + if (chaoticSuccess) { + m_originalChaoticAurState = currentChaoticAurState; + changesApplied = true; + + // Emit signal to notify other widgets + emit chaoticAurStatusChanged(currentChaoticAurState); + } else { + success = false; + } + } + + // Show results and offer database sync if changes were applied + if (changesApplied && success) { + m_statusLabel->setText("Settings applied successfully! Please sync package databases."); + m_statusLabel->setStyleSheet("QLabel { color: #00aa00; padding: 10px; font-weight: bold; }"); + m_statusLabel->show(); + + m_applyButton->setEnabled(false); + + // Suggest database sync + auto reply = QMessageBox::question(this, "Sync Package Database", + "Would you like to sync the package database now?\n" + "(This will run 'pkexec pacman -Sy')", + QMessageBox::Yes | QMessageBox::No); + + if (reply == QMessageBox::Yes) { + QProcess process; + m_statusLabel->setText("Syncing package databases..."); + process.start("pkexec", QStringList() << "pacman" << "-Sy"); + process.waitForFinished(60000); // 60 second timeout + + if (process.exitCode() == 0) { + m_statusLabel->setText("Package databases synced successfully!"); + Logger::info("Package databases synced after repository change"); + + // Refresh ALPM databases to pick up the new repository + AlpmWrapper::instance().refreshDatabases(); + } else { + m_statusLabel->setText("Failed to sync package databases. Please run 'sudo pacman -Sy' manually."); + m_statusLabel->setStyleSheet("QLabel { color: #aa0000; padding: 10px; }"); + } + } else { + // Even if they don't sync now, refresh ALPM to detect the new repo configuration + AlpmWrapper::instance().refreshDatabases(); + } + } else if (!success) { + m_statusLabel->setText("Failed to apply settings. Please check permissions."); + m_statusLabel->setStyleSheet("QLabel { color: #aa0000; padding: 10px; }"); + m_statusLabel->show(); + } +} + +bool SettingsWidget::isMultilibEnabled() const { + return m_multilibRepoCheckbox->isChecked() && + (m_multilibRepoCheckbox->isChecked() == m_originalMultilibState); +} + +bool SettingsWidget::isChaoticAurEnabled() const { + return m_chaoticAurCheckbox->isChecked() && + (m_chaoticAurCheckbox->isChecked() == m_originalChaoticAurState); +} + +void SettingsWidget::applySettings() { + onApplyClicked(); +} + +void SettingsWidget::onRemoveLockClicked() { + QString lockFilePath = "/var/lib/pacman/db.lck"; + + // Check if lock file exists + QFile lockFile(lockFilePath); + if (!lockFile.exists()) { + QMessageBox::information(this, "Lock File Not Found", + "The pacman lock file does not exist.\n" + "No action needed."); + return; + } + + // Show warning dialog with checkbox + QMessageBox msgBox(this); + msgBox.setIcon(QMessageBox::Warning); + msgBox.setWindowTitle("Remove Pacman Lock File"); + msgBox.setText("Are you sure you want to remove the pacman lock file?"); + msgBox.setInformativeText( + "This will remove: /var/lib/pacman/db.lck\n\n" + "WARNING: Only do this if you are certain that no other package manager " + "(pacman, yay, paru, etc.) is currently running.\n\n" + "Removing the lock file while a package operation is in progress can " + "corrupt your package database!"); + + QCheckBox* confirmCheckbox = new QCheckBox("I understand the risks and confirm no package manager is running"); + msgBox.setCheckBox(confirmCheckbox); + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + msgBox.setDefaultButton(QMessageBox::No); + + int ret = msgBox.exec(); + + if (ret == QMessageBox::Yes && confirmCheckbox->isChecked()) { + // Use pkexec to remove the lock file with elevated privileges + QProcess process; + process.start("pkexec", QStringList() << "rm" << "-f" << lockFilePath); + process.waitForFinished(30000); // 30 second timeout + + if (process.exitCode() == 0) { + m_statusLabel->setText("Lock file removed successfully!"); + m_statusLabel->setStyleSheet("QLabel { color: #00aa00; padding: 10px; font-weight: bold; }"); + m_statusLabel->show(); + Logger::info("Pacman lock file removed successfully"); + + QMessageBox::information(this, "Success", + "The pacman lock file has been removed successfully.\n" + "You can now run package operations."); + } else { + m_statusLabel->setText("Failed to remove lock file. Check permissions."); + m_statusLabel->setStyleSheet("QLabel { color: #aa0000; padding: 10px; }"); + m_statusLabel->show(); + Logger::error("Failed to remove pacman lock file"); + + QMessageBox::critical(this, "Error", + "Failed to remove the lock file.\n" + "You may need to run: sudo rm /var/lib/pacman/db.lck"); + } + } else if (ret == QMessageBox::Yes && !confirmCheckbox->isChecked()) { + QMessageBox::warning(this, "Confirmation Required", + "You must check the confirmation box to proceed."); + } +} + +void SettingsWidget::onSetupChaoticClicked() { + QMessageBox msgBox(this); + msgBox.setIcon(QMessageBox::Question); + msgBox.setWindowTitle("Setup Chaotic-AUR"); + msgBox.setText("Install Chaotic-AUR repository?"); + msgBox.setInformativeText( + "This will:\n" + "1. Download chaotic-keyring and chaotic-mirrorlist packages\n" + "2. Install them using pacman\n" + "3. Add the repository to /etc/pacman.conf\n\n" + "This requires internet connection and administrator privileges."); + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + msgBox.setDefaultButton(QMessageBox::Yes); + + if (msgBox.exec() != QMessageBox::Yes) { + return; + } + + m_statusLabel->setText("Setting up Chaotic-AUR repository..."); + m_statusLabel->setStyleSheet("QLabel { color: #0066cc; padding: 10px; }"); + m_statusLabel->show(); + m_setupChaoticButton->setEnabled(false); + + // Use a shell script to download and install chaotic-aur packages + // This follows the official installation guide from aur.chaotic.cx + QString script = + "cd /tmp && " + "rm -f chaotic-keyring.pkg.tar.zst chaotic-mirrorlist.pkg.tar.zst && " + "curl -L -O https://cdn-mirror.chaotic.cx/chaotic-aur/chaotic-keyring.pkg.tar.zst && " + "curl -L -O https://cdn-mirror.chaotic.cx/chaotic-aur/chaotic-mirrorlist.pkg.tar.zst && " + "pacman -U --noconfirm chaotic-keyring.pkg.tar.zst chaotic-mirrorlist.pkg.tar.zst"; + + QProcess* process = new QProcess(this); + + // Capture both stdout and stderr for debugging + process->setProcessChannelMode(QProcess::MergedChannels); + + connect(process, QOverload::of(&QProcess::finished), + this, [this, process](int exitCode, QProcess::ExitStatus exitStatus) { + QString output = process->readAll(); + + Logger::info(QString("Chaotic-AUR setup exit code: %1, status: %2") + .arg(exitCode) + .arg(exitStatus == QProcess::NormalExit ? "Normal" : "Crashed")); + + if (!output.isEmpty()) { + Logger::debug(QString("Chaotic-AUR setup output:\n%1").arg(output)); + } + + process->deleteLater(); + m_setupChaoticButton->setEnabled(true); + + if (exitCode == 0 && exitStatus == QProcess::NormalExit) { + m_statusLabel->setText("Chaotic-AUR packages installed successfully!"); + m_statusLabel->setStyleSheet("QLabel { color: #00aa00; padding: 10px; font-weight: bold; }"); + m_statusLabel->show(); + Logger::info("Chaotic-AUR packages installed successfully"); + + // Refresh the chaotic-aur checkbox status + loadCurrentSettings(); + + QMessageBox::information(this, "Success", + "Chaotic-AUR packages installed successfully!\n\n" + "You can now enable the Chaotic-AUR repository using the checkbox above.\n" + "After enabling, remember to sync the package databases."); + } else { + m_statusLabel->setText("Failed to install Chaotic-AUR packages."); + m_statusLabel->setStyleSheet("QLabel { color: #aa0000; padding: 10px; }"); + m_statusLabel->show(); + Logger::error(QString("Failed to install Chaotic-AUR packages. Exit code: %1").arg(exitCode)); + + // Show output in error message if available + QString errorDetails = "Possible reasons:\n" + "• No internet connection\n" + "• Download failed\n" + "• Installation cancelled\n" + "• User denied authentication\n\n"; + + if (!output.isEmpty() && output.length() < 500) { + errorDetails += "Error output:\n" + output; + } + + QMessageBox::critical(this, "Error", + "Failed to install Chaotic-AUR packages.\n\n" + errorDetails); + } + }); + + process->start("pkexec", QStringList() << "bash" << "-c" << script); +} + +void SettingsWidget::onRemoveChaoticClicked() { + QMessageBox msgBox(this); + msgBox.setIcon(QMessageBox::Warning); + msgBox.setWindowTitle("Remove Chaotic-AUR"); + msgBox.setText("Remove Chaotic-AUR repository?"); + msgBox.setInformativeText( + "This will remove:\n" + "• chaotic-keyring\n" + "• chaotic-mirrorlist\n\n" + "Note: You may need to manually remove the [chaotic-aur] section " + "from /etc/pacman.conf to fully disable the repository."); + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + msgBox.setDefaultButton(QMessageBox::No); + + if (msgBox.exec() != QMessageBox::Yes) { + return; + } + + m_statusLabel->setText("Removing Chaotic-AUR packages..."); + m_statusLabel->setStyleSheet("QLabel { color: #0066cc; padding: 10px; }"); + m_statusLabel->show(); + m_removeChaoticButton->setEnabled(false); + + // Remove chaotic-keyring and chaotic-mirrorlist + QProcess* process = new QProcess(this); + connect(process, QOverload::of(&QProcess::finished), + this, [this, process](int exitCode, QProcess::ExitStatus exitStatus) { + process->deleteLater(); + m_removeChaoticButton->setEnabled(true); + + if (exitCode == 0 && exitStatus == QProcess::NormalExit) { + m_statusLabel->setText("Chaotic-AUR packages removed successfully!"); + m_statusLabel->setStyleSheet("QLabel { color: #00aa00; padding: 10px; font-weight: bold; }"); + m_statusLabel->show(); + Logger::info("Chaotic-AUR packages removed successfully"); + + // Refresh the chaotic-aur checkbox status + loadCurrentSettings(); + + QMessageBox::information(this, "Success", + "Chaotic-AUR packages removed successfully!\n\n" + "To fully disable the repository, you may need to remove or comment out " + "the [chaotic-aur] section in /etc/pacman.conf"); + } else { + m_statusLabel->setText("Failed to remove Chaotic-AUR packages."); + m_statusLabel->setStyleSheet("QLabel { color: #aa0000; padding: 10px; }"); + m_statusLabel->show(); + Logger::error("Failed to remove Chaotic-AUR packages"); + + QMessageBox::critical(this, "Error", + "Failed to remove Chaotic-AUR packages.\n" + "Please check the logs for details."); + } + }); + + process->start("pkexec", QStringList() << "pacman" << "-Rns" << "--noconfirm" + << "chaotic-keyring" << "chaotic-mirrorlist"); +} + +void SettingsWidget::onSyncReposClicked() { + QMessageBox msgBox(this); + msgBox.setIcon(QMessageBox::Question); + msgBox.setWindowTitle("Sync Repositories"); + msgBox.setText("Synchronize package databases?"); + msgBox.setInformativeText( + "This will run: pacman -Sy\n\n" + "This updates the list of available packages from all enabled repositories.\n" + "This is useful after enabling/disabling repositories or when you want to " + "ensure you have the latest package information."); + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + msgBox.setDefaultButton(QMessageBox::Yes); + + if (msgBox.exec() != QMessageBox::Yes) { + return; + } + + m_statusLabel->setText("Synchronizing repositories..."); + m_statusLabel->setStyleSheet("QLabel { color: #0066cc; padding: 10px; }"); + m_statusLabel->show(); + m_syncReposButton->setEnabled(false); + + // Run pacman -Sy with pkexec + QProcess* process = new QProcess(this); + connect(process, QOverload::of(&QProcess::finished), + this, [this, process](int exitCode, QProcess::ExitStatus exitStatus) { + process->deleteLater(); + m_syncReposButton->setEnabled(true); + + if (exitCode == 0 && exitStatus == QProcess::NormalExit) { + m_statusLabel->setText("Repositories synchronized successfully!"); + m_statusLabel->setStyleSheet("QLabel { color: #00aa00; padding: 10px; font-weight: bold; }"); + m_statusLabel->show(); + Logger::info("Repositories synchronized successfully"); + + // Refresh ALPM databases + AlpmWrapper::instance().refreshDatabases(); + + QMessageBox::information(this, "Success", + "Package databases synchronized successfully!\n\n" + "The package list has been updated with the latest available packages."); + } else { + m_statusLabel->setText("Failed to synchronize repositories."); + m_statusLabel->setStyleSheet("QLabel { color: #aa0000; padding: 10px; }"); + m_statusLabel->show(); + Logger::error("Failed to synchronize repositories"); + + QMessageBox::critical(this, "Error", + "Failed to synchronize package databases.\n" + "Please check your internet connection and try again."); + } + }); + + process->start("pkexec", QStringList() << "pacman" << "-Sy"); +} + +void SettingsWidget::onCancelProcessClicked() { + // Check if there's actually a process running + if (!PackageManager::instance().isOperationRunning()) { + QMessageBox::information(this, "No Process Running", + "There is no package operation currently running.\n" + "Nothing to cancel."); + return; + } + + // Show confirmation dialog + QMessageBox msgBox(this); + msgBox.setIcon(QMessageBox::Warning); + msgBox.setWindowTitle("Cancel Running Process"); + msgBox.setText("Are you sure you want to cancel the running package operation?"); + msgBox.setInformativeText( + "This will stop the current installation, uninstallation, or update process.\n\n" + "WARNING: Cancelling a package operation may leave your system in an inconsistent state.\n" + "You may need to run the operation again to complete it properly.\n\n" + "It's recommended to only cancel if the process is truly stuck or unresponsive."); + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + msgBox.setDefaultButton(QMessageBox::No); + + if (msgBox.exec() != QMessageBox::Yes) { + return; + } + + m_statusLabel->setText("Cancelling running process..."); + m_statusLabel->setStyleSheet("QLabel { color: #0066cc; padding: 10px; }"); + m_statusLabel->show(); + + // Cancel the operation + PackageManager::instance().cancelRunningOperation(); + + m_statusLabel->setText("Process cancelled successfully!"); + m_statusLabel->setStyleSheet("QLabel { color: #00aa00; padding: 10px; font-weight: bold; }"); + m_statusLabel->show(); + + Logger::info("User cancelled running package operation from settings"); + + QMessageBox::information(this, "Process Cancelled", + "The running package operation has been cancelled.\n\n" + "If you were in the middle of installing or updating a package, " + "you may need to run the operation again to complete it."); +} diff --git a/src/gui/settings_widget.h b/src/gui/settings_widget.h new file mode 100644 index 0000000..064f2f8 --- /dev/null +++ b/src/gui/settings_widget.h @@ -0,0 +1,83 @@ +#ifndef SETTINGS_WIDGET_H +#define SETTINGS_WIDGET_H + +#include +#include +#include +#include +#include +#include + +/** + * @brief Widget for application and repository settings. + * + * Memory Management: + * - All Qt widget members use Qt parent-child ownership (raw pointers are non-owning) + */ +class SettingsWidget : public QWidget { + Q_OBJECT + +public: + explicit SettingsWidget(QWidget* parent = nullptr); + ~SettingsWidget() override = default; + + bool isMultilibEnabled() const; + bool isChaoticAurEnabled() const; + +signals: + void multilibStatusChanged(bool enabled); + void chaoticAurStatusChanged(bool enabled); + +private: + void setupUi(); + void loadCurrentSettings(); + void createRepositorySettings(); + void createChaoticAurSettings(); + void createMaintenanceSettings(); + bool isMultilibEnabledInPacmanConf() const; + bool isChaoticAurEnabledInPacmanConf() const; + bool enableMultilibInPacmanConf(); + bool disableMultilibInPacmanConf(); + bool enableChaoticAurInPacmanConf(); + bool disableChaoticAurInPacmanConf(); + void applySettings(); + + // Repository settings (Qt parent-child managed, non-owning pointers) + QGroupBox* m_repositoryGroup = nullptr; + QCheckBox* m_coreRepoCheckbox = nullptr; + QCheckBox* m_extraRepoCheckbox = nullptr; + QCheckBox* m_multilibRepoCheckbox = nullptr; + QCheckBox* m_chaoticAurCheckbox = nullptr; + + // Chaotic-AUR setup (Qt parent-child managed) + QGroupBox* m_chaoticAurGroup = nullptr; + QPushButton* m_setupChaoticButton = nullptr; + QPushButton* m_removeChaoticButton = nullptr; + + // Maintenance settings (Qt parent-child managed) + QGroupBox* m_maintenanceGroup = nullptr; + QPushButton* m_removeLockButton = nullptr; + QPushButton* m_syncReposButton = nullptr; + QPushButton* m_cancelProcessButton = nullptr; + + // Control buttons (Qt parent-child managed) + QPushButton* m_applyButton = nullptr; + + // Status (Qt parent-child managed) + QLabel* m_statusLabel = nullptr; + + // Track original state + bool m_originalMultilibState = false; + bool m_originalChaoticAurState = false; + +private slots: + void onApplyClicked(); + void onSettingsChanged(); + void onSetupChaoticClicked(); + void onRemoveChaoticClicked(); + void onRemoveLockClicked(); + void onSyncReposClicked(); + void onCancelProcessClicked(); +}; + +#endif // SETTINGS_WIDGET_H diff --git a/src/gui/updates_widget.cpp b/src/gui/updates_widget.cpp new file mode 100644 index 0000000..f191a46 --- /dev/null +++ b/src/gui/updates_widget.cpp @@ -0,0 +1,491 @@ +#include "updates_widget.h" +#include "../core/alpm_wrapper.h" +#include "../core/aur_helper.h" +#include "../core/package_manager.h" +#include "../utils/logger.h" +#include +#include +#include +#include +#include + +class UpdateItem : public QWidget { + Q_OBJECT + +public: + UpdateItem(const UpdateInfo& info, QWidget* parent = nullptr) + : QWidget(parent), m_info(info) { + + auto* layout = new QHBoxLayout(this); + layout->setContentsMargins(10, 10, 10, 10); + + auto* infoLayout = new QVBoxLayout(); + + auto* nameLabel = new QLabel(m_info.name, this); + auto nameFont = nameLabel->font(); + nameFont.setBold(true); + nameFont.setPointSize(12); + nameLabel->setFont(nameFont); + infoLayout->addWidget(nameLabel); + + auto* versionLabel = new QLabel( + QString("%1 → %2").arg(m_info.oldVersion, m_info.newVersion), this); + versionLabel->setStyleSheet("color: #888;"); + infoLayout->addWidget(versionLabel); + + auto* repoLabel = new QLabel(m_info.repository, this); + repoLabel->setStyleSheet("color: #666; font-size: 10px;"); + infoLayout->addWidget(repoLabel); + + layout->addLayout(infoLayout); + layout->addStretch(); + + if (m_info.downloadSize > 0) { + auto* sizeLabel = new QLabel(formatSize(m_info.downloadSize), this); + sizeLabel->setStyleSheet("color: #888;"); + layout->addWidget(sizeLabel); + } + + auto* updateButton = new QPushButton("Update", this); + updateButton->setMinimumWidth(100); + connect(updateButton, &QPushButton::clicked, [this]() { + emit updateRequested(m_info.name); + }); + layout->addWidget(updateButton); + + setLayout(layout); + setProperty("class", "update-item"); + } + +signals: + void updateRequested(const QString& packageName); + +private: + UpdateInfo m_info; + + QString formatSize(qint64 bytes) { + const qint64 KB = 1024; + const qint64 MB = KB * 1024; + const qint64 GB = MB * 1024; + + if (bytes >= GB) { + return QString("%1 GB").arg(bytes / static_cast(GB), 0, 'f', 2); + } else if (bytes >= MB) { + return QString("%1 MB").arg(bytes / static_cast(MB), 0, 'f', 1); + } else if (bytes >= KB) { + return QString("%1 KB").arg(bytes / static_cast(KB), 0, 'f', 0); + } + return QString("%1 B").arg(bytes); + } +}; + +UpdatesWidget::UpdatesWidget(QWidget* parent) + : QWidget(parent) + , m_searchInput(new QLineEdit(this)) + , m_scrollArea(new QScrollArea(this)) + , m_contentWidget(new QWidget()) + , m_contentLayout(new QVBoxLayout(m_contentWidget)) + , m_statusLabel(new QLabel(this)) + , m_countLabel(new QLabel(this)) + , m_updateAllButton(new QPushButton("Update All", this)) + , m_checkButton(new QPushButton("Check for Updates", this)) + , m_progressWidget(new QWidget(this)) + , m_progressBar(new QProgressBar(this)) + , m_progressLabel(new QLabel(this)) + , m_toggleLogButton(new QPushButton("Show Logs", this)) + , m_logWidget(new QWidget(this)) + , m_logViewer(new QTextEdit(this)) { + + setupUi(); + + // Connect to PackageManager signals + connect(&PackageManager::instance(), &PackageManager::operationStarted, + this, &UpdatesWidget::onOperationStarted); + connect(&PackageManager::instance(), &PackageManager::operationOutput, + this, &UpdatesWidget::onOperationOutput); + connect(&PackageManager::instance(), &PackageManager::operationCompleted, + this, &UpdatesWidget::onOperationCompleted); + connect(&PackageManager::instance(), &PackageManager::operationError, + this, &UpdatesWidget::onOperationError); +} + +void UpdatesWidget::setupUi() { + auto* mainLayout = new QVBoxLayout(this); + + // Header + auto* headerLayout = new QHBoxLayout(); + + auto* titleLabel = new QLabel("Available Updates", this); + auto titleFont = titleLabel->font(); + titleFont.setPointSize(24); + titleFont.setBold(true); + titleLabel->setFont(titleFont); + headerLayout->addWidget(titleLabel); + + headerLayout->addStretch(); + + m_countLabel->setStyleSheet("font-size: 14px; color: #888;"); + headerLayout->addWidget(m_countLabel); + + m_checkButton->setMinimumHeight(35); + connect(m_checkButton, &QPushButton::clicked, this, &UpdatesWidget::checkForUpdates); + headerLayout->addWidget(m_checkButton); + + m_updateAllButton->setMinimumHeight(35); + m_updateAllButton->setMinimumWidth(120); + m_updateAllButton->setEnabled(false); + connect(m_updateAllButton, &QPushButton::clicked, this, &UpdatesWidget::onUpdateAll); + headerLayout->addWidget(m_updateAllButton); + + mainLayout->addLayout(headerLayout); + + // Search bar + m_searchInput->setPlaceholderText("Search updates..."); + m_searchInput->setMinimumHeight(35); + m_searchInput->setClearButtonEnabled(true); + m_searchInput->setEnabled(false); // Disabled until updates are loaded + connect(m_searchInput, &QLineEdit::textChanged, this, &UpdatesWidget::onSearchTextChanged); + mainLayout->addWidget(m_searchInput); + + // Status label + m_statusLabel->setAlignment(Qt::AlignCenter); + m_statusLabel->setText("Click 'Check for Updates' to scan for available updates"); + mainLayout->addWidget(m_statusLabel); + + // Updates area + m_scrollArea->setWidget(m_contentWidget); + m_scrollArea->setWidgetResizable(true); + m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + m_contentLayout->setSpacing(5); + m_contentLayout->setContentsMargins(10, 10, 10, 10); + m_contentLayout->addStretch(); + + mainLayout->addWidget(m_scrollArea); + + // Progress bar section (hidden by default) + auto* progressLayout = new QVBoxLayout(m_progressWidget); + progressLayout->setContentsMargins(20, 0, 20, 20); + progressLayout->setSpacing(8); + + m_progressLabel->setStyleSheet("color: #a1a1aa; font-size: 12px;"); + m_progressLabel->setAlignment(Qt::AlignCenter); + progressLayout->addWidget(m_progressLabel); + + m_progressBar->setMinimumHeight(20); + m_progressBar->setMaximumHeight(20); + m_progressBar->setTextVisible(true); + m_progressBar->setFormat("%p%"); + m_progressBar->setStyleSheet( + "QProgressBar {" + " border: none;" + " border-radius: 4px;" + " background-color: #27272a;" + " color: #fafafa;" + " text-align: center;" + " font-size: 11px;" + "}" + "QProgressBar::chunk {" + " border-radius: 4px;" + " background-color: #3b82f6;" + "}" + ); + progressLayout->addWidget(m_progressBar); + + // Toggle log button + m_toggleLogButton->setStyleSheet( + "QPushButton {" + " background: none;" + " border: none;" + " color: #3b82f6;" + " text-decoration: underline;" + " font-size: 11px;" + " padding: 4px;" + "}" + "QPushButton:hover {" + " color: #60a5fa;" + "}" + ); + connect(m_toggleLogButton, &QPushButton::clicked, this, &UpdatesWidget::toggleLogViewer); + progressLayout->addWidget(m_toggleLogButton, 0, Qt::AlignCenter); + + m_progressWidget->hide(); + mainLayout->addWidget(m_progressWidget, 0); + + // Log viewer section (hidden by default) + auto* logLayout = new QVBoxLayout(m_logWidget); + logLayout->setContentsMargins(20, 0, 20, 20); + logLayout->setSpacing(8); + + m_logViewer->setReadOnly(true); + m_logViewer->setMaximumHeight(200); + m_logViewer->setStyleSheet( + "QTextEdit {" + " background-color: #18181b;" + " border: 1px solid #27272a;" + " border-radius: 6px;" + " color: #d4d4d8;" + " font-family: 'Consolas', 'Monaco', monospace;" + " font-size: 11px;" + " padding: 8px;" + "}" + ); + logLayout->addWidget(m_logViewer); + + m_logWidget->hide(); + mainLayout->addWidget(m_logWidget, 0); + + setLayout(mainLayout); +} + +void UpdatesWidget::checkForUpdates() { + m_statusLabel->setText("Checking for updates..."); + m_statusLabel->show(); + m_checkButton->setEnabled(false); + m_checkButton->setText("Checking..."); + m_updateAllButton->setEnabled(false); + + clearUpdates(); + + (void)QtConcurrent::run([this]() { + auto updates = AlpmWrapper::instance().getAvailableUpdates(); + + // Also check AUR updates + AurHelper aurHelper; + auto aurUpdates = aurHelper.checkAurUpdates(); + updates.append(aurUpdates); + + QMetaObject::invokeMethod(this, [this, updates]() { + m_updates = updates; + m_filteredUpdates = updates; + + m_checkButton->setEnabled(true); + m_checkButton->setText("Check for Updates"); + + if (updates.isEmpty()) { + m_statusLabel->setText("Your system is up to date!"); + m_countLabel->clear(); + m_searchInput->setEnabled(false); + } else { + m_statusLabel->hide(); + m_countLabel->setText(QString("%1 updates available") + .arg(updates.size())); + m_updateAllButton->setEnabled(true); + m_searchInput->setEnabled(true); + + // Apply any existing search filter + QString searchText = m_searchInput->text(); + if (!searchText.isEmpty()) { + filterUpdates(searchText); + } else { + displayUpdates(updates); + } + } + + Logger::info(QString("Found %1 updates").arg(updates.size())); + }, Qt::QueuedConnection); + }); +} + +void UpdatesWidget::displayUpdates(const QVector& updates) { + clearUpdates(); + + for (const auto& update : updates) { + auto* item = new UpdateItem(update, m_contentWidget); + connect(item, &UpdateItem::updateRequested, + this, &UpdatesWidget::onUpdateSingle); + m_contentLayout->insertWidget(m_contentLayout->count() - 1, item); + } +} + +void UpdatesWidget::clearUpdates() { + while (m_contentLayout->count() > 1) { + auto* item = m_contentLayout->takeAt(0); + if (auto* widget = item->widget()) { + widget->deleteLater(); + } + delete item; + } +} + +void UpdatesWidget::filterUpdates(const QString& searchText) { + if (searchText.isEmpty()) { + m_filteredUpdates = m_updates; + displayUpdates(m_filteredUpdates); + m_countLabel->setText(QString("%1 updates available").arg(m_updates.size())); + return; + } + + QString lowerSearch = searchText.toLower(); + m_filteredUpdates.clear(); + + for (const auto& update : m_updates) { + if (update.name.toLower().contains(lowerSearch)) { + m_filteredUpdates.append(update); + } + } + + displayUpdates(m_filteredUpdates); + + // Update count label to show filtered count + if (m_filteredUpdates.size() == m_updates.size()) { + m_countLabel->setText(QString("%1 updates available").arg(m_updates.size())); + } else { + m_countLabel->setText(QString("%1 of %2 updates").arg(m_filteredUpdates.size()).arg(m_updates.size())); + } +} + +void UpdatesWidget::onSearchTextChanged(const QString& text) { + filterUpdates(text); +} + +void UpdatesWidget::onUpdateAll() { + auto reply = QMessageBox::question(this, "Update All", + QString("Are you sure you want to update all %1 packages?") + .arg(m_updates.size()), + QMessageBox::Yes | QMessageBox::No); + + if (reply == QMessageBox::Yes) { + PackageManager::instance().updateAllPackages(); + } +} + +void UpdatesWidget::onUpdateSingle(const QString& packageName) { + auto reply = QMessageBox::question(this, "Update Package", + QString("Are you sure you want to update %1?").arg(packageName), + QMessageBox::Yes | QMessageBox::No); + + if (reply == QMessageBox::Yes) { + PackageManager::instance().updatePackage(packageName); + } +} + +QString UpdatesWidget::formatSize(qint64 bytes) { + const qint64 KB = 1024; + const qint64 MB = KB * 1024; + const qint64 GB = MB * 1024; + + if (bytes >= GB) { + return QString("%1 GB").arg(bytes / static_cast(GB), 0, 'f', 2); + } else if (bytes >= MB) { + return QString("%1 MB").arg(bytes / static_cast(MB), 0, 'f', 1); + } else if (bytes >= KB) { + return QString("%1 KB").arg(bytes / static_cast(KB), 0, 'f', 0); + } + return QString("%1 B").arg(bytes); +} + +void UpdatesWidget::showProgress(const QString& message) { + m_progressLabel->setText(message); + m_progressBar->setRange(0, 100); + m_progressBar->setValue(0); + m_progressWidget->show(); + m_progressBar->show(); + m_progressLabel->show(); + m_logViewer->clear(); +} + +void UpdatesWidget::hideProgress() { + // Hide the progress bar and label, but keep the widget and toggle button visible + m_progressBar->hide(); + m_progressLabel->hide(); + // Don't hide m_progressWidget - keeps the toggle button visible + // Don't hide the log widget or reset log visibility + // This allows users to review logs after operation completes +} + +void UpdatesWidget::toggleLogViewer() { + m_logVisible = !m_logVisible; + if (m_logVisible) { + m_logWidget->show(); + m_toggleLogButton->setText("Hide Logs"); + } else { + m_logWidget->hide(); + m_toggleLogButton->setText("Show Logs"); + } +} + +void UpdatesWidget::onOperationStarted(const QString& message) { + showProgress(message); + m_updateAllButton->setEnabled(false); + m_checkButton->setEnabled(false); + + // Disable all individual update buttons + for (int i = 0; i < m_contentLayout->count() - 1; ++i) { + if (auto* item = m_contentLayout->itemAt(i)) { + if (auto* widget = item->widget()) { + widget->setEnabled(false); + } + } + } +} + +void UpdatesWidget::onOperationOutput(const QString& output) { + m_logViewer->append(output); + + // Auto-scroll to bottom + auto cursor = m_logViewer->textCursor(); + cursor.movePosition(QTextCursor::End); + m_logViewer->setTextCursor(cursor); + + // Try to parse progress information from output + // This is a simple implementation - could be enhanced + if (output.contains("downloading", Qt::CaseInsensitive)) { + m_progressLabel->setText("Downloading packages..."); + m_progressBar->setRange(0, 0); // Indeterminate + } else if (output.contains("installing", Qt::CaseInsensitive)) { + m_progressLabel->setText("Installing packages..."); + m_progressBar->setRange(0, 0); // Indeterminate + } +} + +void UpdatesWidget::onOperationCompleted(bool success, const QString& message) { + // Refresh ALPM state so subsequent queries reflect the changes + AlpmWrapper::instance().release(); + AlpmWrapper::instance().initialize(); + + hideProgress(); + + m_updateAllButton->setEnabled(!m_updates.isEmpty()); + m_checkButton->setEnabled(true); + + // Re-enable all individual update buttons + for (int i = 0; i < m_contentLayout->count() - 1; ++i) { + if (auto* item = m_contentLayout->itemAt(i)) { + if (auto* widget = item->widget()) { + widget->setEnabled(true); + } + } + } + + if (success) { + QMessageBox::information(this, "Success", message); + checkForUpdates(); + } else { + QMessageBox::warning(this, "Operation Failed", message); + } +} + +void UpdatesWidget::onOperationError(const QString& error) { + // Refresh ALPM state (best-effort) + AlpmWrapper::instance().release(); + AlpmWrapper::instance().initialize(); + + hideProgress(); + + m_updateAllButton->setEnabled(!m_updates.isEmpty()); + m_checkButton->setEnabled(true); + + // Re-enable all individual update buttons + for (int i = 0; i < m_contentLayout->count() - 1; ++i) { + if (auto* item = m_contentLayout->itemAt(i)) { + if (auto* widget = item->widget()) { + widget->setEnabled(true); + } + } + } + + QMessageBox::critical(this, "Error", error); +} + +#include "updates_widget.moc" diff --git a/src/gui/updates_widget.h b/src/gui/updates_widget.h new file mode 100644 index 0000000..4742f2e --- /dev/null +++ b/src/gui/updates_widget.h @@ -0,0 +1,75 @@ +#ifndef UPDATES_WIDGET_H +#define UPDATES_WIDGET_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../utils/types.h" + +class UpdateItem; + +/** + * @brief Widget for displaying and managing package updates. + * + * Memory Management: + * - All Qt widget members use Qt parent-child ownership (raw pointers are non-owning) + * - UpdateItem widgets are dynamically created/destroyed in displayUpdates/clearUpdates + */ +class UpdatesWidget : public QWidget { + Q_OBJECT + +public: + explicit UpdatesWidget(QWidget* parent = nullptr); + ~UpdatesWidget() override = default; + + void checkForUpdates(); + +private: + void setupUi(); + void displayUpdates(const QVector& updates); + void clearUpdates(); + void filterUpdates(const QString& searchText); + QString formatSize(qint64 bytes); + void showProgress(const QString& message); + void hideProgress(); + void toggleLogViewer(); + + // Qt parent-child managed widgets (non-owning pointers) + QLineEdit* m_searchInput = nullptr; + QScrollArea* m_scrollArea = nullptr; + QWidget* m_contentWidget = nullptr; + QVBoxLayout* m_contentLayout = nullptr; + QLabel* m_statusLabel = nullptr; + QLabel* m_countLabel = nullptr; + QPushButton* m_updateAllButton = nullptr; + QPushButton* m_checkButton = nullptr; + + // Progress bar and log viewer (Qt parent-child managed) + QWidget* m_progressWidget = nullptr; + QProgressBar* m_progressBar = nullptr; + QLabel* m_progressLabel = nullptr; + QPushButton* m_toggleLogButton = nullptr; + QWidget* m_logWidget = nullptr; + QTextEdit* m_logViewer = nullptr; + bool m_logVisible = false; + + QVector m_updates; + QVector m_filteredUpdates; + +private slots: + void onUpdateAll(); + void onUpdateSingle(const QString& packageName); + void onSearchTextChanged(const QString& text); + void onOperationStarted(const QString& message); + void onOperationOutput(const QString& output); + void onOperationCompleted(bool success, const QString& message); + void onOperationError(const QString& error); +}; + +#endif // UPDATES_WIDGET_H diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..a3f35bd --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,29 @@ +#include "gui/mainwindow.h" +#include "utils/logger.h" +#include +#include + +int main(int argc, char *argv[]) { + QApplication app(argc, argv); + + // Set application metadata + app.setApplicationName("ALG App Store"); + app.setApplicationVersion("2.0.0"); + app.setOrganizationName("Arch Linux GUI"); + + // Set application style + app.setStyle(QStyleFactory::create("Fusion")); + + Logger::info("Starting ALG App Store"); + Logger::info(QString("Qt version: %1").arg(qVersion())); + + MainWindow window; + window.show(); + + Logger::info("Application window shown"); + + int result = app.exec(); + + Logger::info("Application exiting"); + return result; +} diff --git a/src/utils/logger.h b/src/utils/logger.h new file mode 100644 index 0000000..a8be860 --- /dev/null +++ b/src/utils/logger.h @@ -0,0 +1,25 @@ +#ifndef LOGGER_H +#define LOGGER_H + +#include +#include + +namespace Logger { + inline void info(const QString &message) { + qDebug() << "[INFO]" << message; + } + + inline void warning(const QString &message) { + qWarning() << "[WARNING]" << message; + } + + inline void error(const QString &message) { + qCritical() << "[ERROR]" << message; + } + + inline void debug(const QString &message) { + qDebug() << "[DEBUG]" << message; + } +} + +#endif // LOGGER_H diff --git a/src/utils/types.h b/src/utils/types.h new file mode 100644 index 0000000..d4907f8 --- /dev/null +++ b/src/utils/types.h @@ -0,0 +1,43 @@ +#ifndef TYPES_H +#define TYPES_H + +#include +#include +#include +#include + +struct PackageInfo { + QString name; + QString version; + QString description; + QString repository; + QString maintainer; + QString upstreamUrl; + QStringList dependList; + QDateTime lastUpdated; + + PackageInfo() = default; + + PackageInfo(const QString& name, const QString& version, + const QString& description, const QString& repository) + : name(name), version(version), description(description), + repository(repository) {} +}; + +struct UpdateInfo { + QString name; + QString oldVersion; + QString newVersion; + QString repository; + qint64 downloadSize; + + UpdateInfo() : downloadSize(0) {} + + UpdateInfo(const QString& name, const QString& oldVersion, + const QString& newVersion, const QString& repository, + qint64 downloadSize = 0) + : name(name), oldVersion(oldVersion), newVersion(newVersion), + repository(repository), downloadSize(downloadSize) {} +}; + +#endif // TYPES_H diff --git a/stylesheet.qss b/stylesheet.qss new file mode 100644 index 0000000..2ec70f9 --- /dev/null +++ b/stylesheet.qss @@ -0,0 +1,338 @@ +/* ALG App Store - Qt Stylesheet */ +/* Based on the original Tailwind CSS dark theme */ + +* { + font-family: "Geist Sans", "Segoe UI", Roboto, Arial, sans-serif; +} + +QMainWindow { + background-color: #09090b; + color: #fafafa; +} + +QWidget { + background-color: #09090b; + color: #fafafa; +} + +/* Tab Widget */ +QTabWidget::pane { + border: 1px solid #27272a; + background-color: #09090b; +} + +QTabBar::tab { + background-color: #18181b; + color: #a1a1aa; + padding: 10px 20px; + margin-right: 2px; + border: 1px solid #27272a; + border-bottom: none; + min-width: 100px; +} + +QTabBar::tab:selected { + background-color: #27272a; + color: #fafafa; +} + +QTabBar::tab:hover { + background-color: #27272a; +} + +/* Buttons */ +QPushButton { + background-color: #fafafa; + color: #18181b; + border: none; + border-radius: 6px; + padding: 8px 16px; + font-weight: 500; +} + +QPushButton:hover { + background-color: #e4e4e7; +} + +QPushButton:pressed { + background-color: #d4d4d8; +} + +QPushButton:disabled { + background-color: #3f3f46; + color: #71717a; +} + +/* Line Edit / Input */ +QLineEdit { + background-color: #18181b; + color: #fafafa; + border: 1px solid #27272a; + border-radius: 6px; + padding: 8px 12px; + selection-background-color: #3f3f46; +} + +QLineEdit:focus { + border: 1px solid #52525b; +} + +QLineEdit::placeholder { + color: #71717a; +} + +/* Combo Box */ +QComboBox { + background-color: #18181b; + color: #fafafa; + border: 1px solid #27272a; + border-radius: 6px; + padding: 8px 12px; + min-width: 120px; +} + +QComboBox:hover { + border: 1px solid #3f3f46; +} + +QComboBox::drop-down { + border: none; + width: 20px; +} + +QComboBox::down-arrow { + width: 10px; + height: 10px; +} + +QComboBox QAbstractItemView { + background-color: #18181b; + color: #fafafa; + border: 1px solid #27272a; + selection-background-color: #27272a; +} + +/* Scroll Area */ +QScrollArea { + border: none; + background-color: transparent; +} + +/* Scroll Bar */ +QScrollBar:vertical { + background-color: #18181b; + width: 12px; + margin: 0px; +} + +QScrollBar::handle:vertical { + background-color: #3f3f46; + border-radius: 6px; + min-height: 30px; +} + +QScrollBar::handle:vertical:hover { + background-color: #52525b; +} + +QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { + height: 0px; +} + +QScrollBar:horizontal { + background-color: #18181b; + height: 12px; + margin: 0px; +} + +QScrollBar::handle:horizontal { + background-color: #3f3f46; + border-radius: 6px; + min-width: 30px; +} + +QScrollBar::handle:horizontal:hover { + background-color: #52525b; +} + +QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal { + width: 0px; +} + +/* Package Card */ +QWidget[class="package-card"] { + background-color: #18181b; + border: 1px solid #27272a; + border-radius: 8px; +} + +QWidget[class="package-card"][hovered="true"] { + background-color: #27272a; + border: 1px solid #3f3f46; +} + +/* Labels / Badges */ +QLabel[class="status-badge"] { + background-color: #27272a; + color: #fafafa; + border-radius: 4px; + padding: 4px 8px; + font-size: 11px; +} + +QLabel[class="repo-badge"] { + background-color: #27272a; + color: #a1a1aa; + border-radius: 4px; + padding: 4px 8px; + font-size: 11px; +} + +/* Update Item */ +QWidget[class="update-item"] { + background-color: #18181b; + border: 1px solid #27272a; + border-radius: 6px; + margin: 2px 0px; +} + +/* Group Box */ +QGroupBox { + background-color: #18181b; + border: 1px solid #27272a; + border-radius: 8px; + margin-top: 10px; + padding-top: 10px; + font-weight: bold; +} + +QGroupBox::title { + subcontrol-origin: margin; + subcontrol-position: top left; + padding: 0px 10px; + color: #fafafa; +} + +/* Text Edit */ +QTextEdit { + background-color: #18181b; + color: #fafafa; + border: 1px solid #27272a; + border-radius: 6px; + padding: 8px; + selection-background-color: #3f3f46; +} + +QTextEdit:focus { + border: 1px solid #52525b; +} + +/* Dialog */ +QDialog { + background-color: #09090b; + color: #fafafa; +} + +/* Menu Bar */ +QMenuBar { + background-color: #18181b; + color: #fafafa; + border-bottom: 1px solid #27272a; +} + +QMenuBar::item { + padding: 5px 10px; + background-color: transparent; +} + +QMenuBar::item:selected { + background-color: #27272a; +} + +QMenuBar::item:pressed { + background-color: #3f3f46; +} + +/* Menu */ +QMenu { + background-color: #18181b; + color: #fafafa; + border: 1px solid #27272a; + border-radius: 6px; + padding: 5px; +} + +QMenu::item { + padding: 8px 25px; + border-radius: 4px; +} + +QMenu::item:selected { + background-color: #27272a; +} + +QMenu::separator { + height: 1px; + background-color: #27272a; + margin: 5px 0px; +} + +/* Message Box */ +QMessageBox { + background-color: #18181b; + color: #fafafa; +} + +QMessageBox QLabel { + color: #fafafa; +} + +QMessageBox QPushButton { + min-width: 80px; + background-color: #3b82f6; + color: #fafafa; + border: none; + border-radius: 6px; + padding: 8px 16px; + font-weight: 500; +} + +QMessageBox QPushButton:hover { + background-color: #2563eb; +} + +QMessageBox QPushButton:pressed { + background-color: #1d4ed8; +} + +QMessageBox QPushButton:default { + background-color: #3b82f6; + color: #fafafa; +} + +QMessageBox QPushButton:default:hover { + background-color: #2563eb; +} + +QMessageBox QPushButton:!default { + background-color: #27272a; + color: #fafafa; +} + +QMessageBox QPushButton:!default:hover { + background-color: #3f3f46; +} + +/* Progress Bar (if needed in future) */ +QProgressBar { + background-color: #27272a; + border: 1px solid #3f3f46; + border-radius: 4px; + text-align: center; + color: #fafafa; +} + +QProgressBar::chunk { + background-color: #3b82f6; + border-radius: 4px; +} diff --git a/wails.json b/wails.json deleted file mode 100644 index 9b86be0..0000000 --- a/wails.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "https://wails.io/schemas/config.v2.json", - "name": "alg-app-store", - "outputfilename": "alg-app-store", - "frontend:install": "npm install", - "frontend:build": "npm run build", - "frontend:dev:watcher": "npm run dev", - "frontend:dev:serverUrl": "auto", - "author": { - "name": "harshau007", - "email": "amanupadhyay2004@gmail.com" - } -}