Compare commits
11 Commits
0.0.0.69
...
66e7ca4fc7
| Author | SHA1 | Date | |
|---|---|---|---|
|
66e7ca4fc7
|
|||
|
a015908d44
|
|||
|
1c9598c00a
|
|||
|
4fb77ef0de
|
|||
|
df6c970112
|
|||
|
411ff15ea5
|
|||
|
269950cf4a
|
|||
|
f1cd7eadb9
|
|||
|
49ba0a4602
|
|||
|
958fe26cad
|
|||
|
44fe6245b9
|
@@ -63,7 +63,27 @@ jobs:
|
||||
SERVER_URL: ${{ github.server_url }}
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$uploadUrl = "$env:SERVER_URL/api/v1/repos/$env:REPO/releases/$env:RELEASE_ID/assets?name=ProgressionLoader.exe"
|
||||
# Gitea API endpoint for uploading release assets
|
||||
$uploadUrl = "$env:SERVER_URL/api/v1/repos/$env:REPO/releases/$env:RELEASE_ID/assets"
|
||||
Write-Host "Uploading ProgressionLoader.exe to $uploadUrl"
|
||||
Invoke-RestMethod -Method Post -Uri $uploadUrl -Headers @{ Authorization = "token $env:TOKEN" } -ContentType "application/octet-stream" -InFile "ProgressionLoader.exe"
|
||||
|
||||
# Use multipart form data for Gitea
|
||||
$boundary = [System.Guid]::NewGuid().ToString()
|
||||
$LF = "`r`n"
|
||||
|
||||
$fileBytes = [System.IO.File]::ReadAllBytes("ProgressionLoader.exe")
|
||||
$fileEnc = [System.Text.Encoding]::GetEncoding('iso-8859-1').GetString($fileBytes)
|
||||
|
||||
$bodyLines = (
|
||||
"--$boundary",
|
||||
"Content-Disposition: form-data; name=`"attachment`"; filename=`"ProgressionLoader.exe`"",
|
||||
"Content-Type: application/octet-stream$LF",
|
||||
$fileEnc,
|
||||
"--$boundary--$LF"
|
||||
) -join $LF
|
||||
|
||||
Invoke-RestMethod -Method Post -Uri $uploadUrl -Headers @{
|
||||
Authorization = "token $env:TOKEN"
|
||||
"Content-Type" = "multipart/form-data; boundary=$boundary"
|
||||
} -Body $bodyLines
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
inclusion: always
|
||||
---
|
||||
<!------------------------------------------------------------------------------------
|
||||
Add rules to this file or a short description and have Kiro refine them for you.
|
||||
|
||||
Learn about inclusion modes: https://kiro.dev/docs/steering/#inclusion-modes
|
||||
------------------------------------------------------------------------------------->
|
||||
|
||||
This project has a GUI and a TUI when fixing or implmenting features access and make changes to both.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 358 KiB |
@@ -1,9 +1,18 @@
|
||||
# Progression: Loader
|
||||
|
||||
This project pulls from the three steam workshop collections of Progression, then it searches through your installed mods, in the workshop folder and matches the steam id to a package id, and Formal name, then it takes those names, package ids and steam ids and creates the modloader modslist, thats a mouth full, and save it to your modslist fodler so all you have to do is launch the game. The merge button will take your currently ENABLED mods and merge them with the latest progression pack to give you a new modslist called progresisonhomebrew which you can sort and load.
|
||||
This project pulls from the three steam workshop collections of Progression, then it searches through your installed mods, in the workshop folder and matches the steam id to a package id, and Formal name, then it takes those names, package ids and steam ids and creates the modloader modslist, thats a mouth full, and save it to your modslist folder so all you have to do is launch the game. The merge button will take your currently ENABLED mods and merge them with the latest progression pack to give you a new modslist called progresisonhomebrew which you can sort and load.
|
||||
|
||||
|
||||
DLC detection is based on NotOwned_x.png if the user doesnt the same package NAME as the not owned png it doesnt proceed.
|
||||
.envs populate pre loaded data, only change if their is an update to collection url
|
||||
Doesnt sort so the current guidence to autosort from base game mod manager will still apply.
|
||||
Art by ferny
|
||||
Art by ferny
|
||||
|
||||
to bump the version
|
||||
|
||||
python version_manager.py 1.0.1
|
||||
|
||||
|
||||
build
|
||||
|
||||
pyinstaller --clean --onefile --windowed --icon=art/Progression.ico --add-data "art;art" --name ProgressionLoader steam_workshop_gui.py
|
||||
@@ -0,0 +1,179 @@
|
||||
# Steam Collections Page Design Update Summary
|
||||
|
||||
## Overview
|
||||
Updated all UI screens (except mod manager) to match Steam Collections page design with modern dark blue theme, improved typography, and Steam-inspired visual elements.
|
||||
|
||||
## Color Scheme Changes
|
||||
|
||||
### New Steam Collections Inspired Colors
|
||||
```python
|
||||
COLORS = {
|
||||
'bg_primary': '#1b2838', # Steam dark blue background
|
||||
'bg_secondary': '#2a475e', # Steam medium blue background
|
||||
'bg_tertiary': '#1e2328', # Steam darker background for cards
|
||||
'bg_card': '#16202d', # Steam card background
|
||||
'bg_hover': '#2a475e', # Steam hover state
|
||||
'text_primary': '#c7d5e0', # Steam primary text (light blue-gray)
|
||||
'text_highlight': '#66c0f4', # Steam blue highlight color
|
||||
'text_body': '#8f98a0', # Steam body text (muted blue-gray)
|
||||
'text_secondary': '#acb2b8', # Steam secondary text
|
||||
'text_muted': '#67707b', # Steam muted text
|
||||
'accent_green': '#5ba32b', # Steam success green
|
||||
'accent_red': '#cd5c5c', # Steam error red
|
||||
'accent_yellow': '#ffa500', # Steam warning orange
|
||||
'accent_blue': '#66c0f4', # Steam signature blue
|
||||
'border_light': '#3c4043', # Steam light border
|
||||
'border_dark': '#0e141b', # Steam dark border
|
||||
}
|
||||
```
|
||||
|
||||
### Previous Colors (Replaced)
|
||||
- Old yellow-green highlights (#f5f5b5) → Steam blue (#66c0f4)
|
||||
- Old gray backgrounds (#424041) → Steam dark blue (#1b2838)
|
||||
- Old bright colors → Muted Steam palette
|
||||
|
||||
## GUI Changes (steam_workshop_gui.py)
|
||||
|
||||
### 1. Main Application Window
|
||||
- **Background**: Updated to Steam dark blue (#1b2838)
|
||||
- **Card System**: Implemented Steam-style cards with rounded corners (12px radius)
|
||||
- **Typography**: Enhanced with Steam blue highlights for titles
|
||||
- **Padding**: Increased from 10px to 15px for better spacing
|
||||
|
||||
### 2. Input Section Redesign
|
||||
- **Card-based Layout**: Each section now uses Steam-style cards
|
||||
- RimWorld Installation card
|
||||
- Workshop Content card
|
||||
- Advanced Configuration card
|
||||
- Steam Workshop Collections card
|
||||
- **Input Fields**:
|
||||
- Dark backgrounds (#1e2328)
|
||||
- Steam blue focus borders (#66c0f4)
|
||||
- Improved padding and spacing
|
||||
- **Buttons**:
|
||||
- Primary button: Steam blue (#66c0f4) with dark text
|
||||
- Secondary button: Steam green (#5ba32b)
|
||||
- Disabled state: Muted colors
|
||||
|
||||
### 3. Output Section (Logs)
|
||||
- **Card Container**: Logs now in Steam-style card
|
||||
- **Color-coded Messages**:
|
||||
- Success: Steam green
|
||||
- Error: Steam red
|
||||
- Warning: Steam orange
|
||||
- Info: Steam blue
|
||||
- **Timestamps**: Added with muted color
|
||||
- **Background**: Dark Steam theme (#1e2328)
|
||||
|
||||
### 4. Loading Screen Updates
|
||||
- **Card Backgrounds**: Updated to Steam styling with subtle borders
|
||||
- **Corner Radius**: Reduced from 20px to 12px for modern look
|
||||
- **Colors**: All text and UI elements use Steam palette
|
||||
- **Error Cards**: Steam red accents for error states
|
||||
|
||||
### 5. Button Styling
|
||||
- **Enable State**: Steam blue primary, Steam green secondary
|
||||
- **Disable State**: Muted Steam colors
|
||||
- **Hover Effects**: Lighter Steam blue on hover
|
||||
- **Cursor**: Hand cursor for better UX
|
||||
|
||||
## TUI Changes (progression_tui.py)
|
||||
|
||||
### 1. Color Scheme
|
||||
- Updated all TUI_COLORS to match GUI Steam palette
|
||||
- Consistent blue theme throughout terminal interface
|
||||
|
||||
### 2. Header Styling
|
||||
- **Border**: Steam blue double border
|
||||
- **Title**: Added "PROGRESSION LOADER" title to panel
|
||||
- **ASCII Art**: Highlighted with Steam blue
|
||||
|
||||
### 3. Menu System
|
||||
- **Table Styling**: Rounded borders with Steam blue accents
|
||||
- **Status Indicators**:
|
||||
- Valid: ✓ with Steam green
|
||||
- Invalid: ⚠ with Steam yellow
|
||||
- Ready: ✓ Ready with Steam green
|
||||
- Disabled: ⚠ Disabled with muted colors
|
||||
|
||||
### 4. Configuration Display
|
||||
- **Path Display**: Added icons and Steam blue labels
|
||||
- **Panel Styling**: Rounded borders with Steam theme
|
||||
- **Status Colors**: Consistent with GUI
|
||||
|
||||
## Font and Styling (font_and_colors_update.py)
|
||||
|
||||
### 1. New Utility Functions
|
||||
- `apply_steam_styling()`: Returns Steam-inspired style dictionaries
|
||||
- `get_steam_button_colors()`: Provides button color schemes by type
|
||||
- Updated color constants to match Steam theme
|
||||
|
||||
### 2. Button Color Schemes
|
||||
- **Default**: Steam secondary background
|
||||
- **Primary**: Steam blue with dark text
|
||||
- **Success**: Steam green
|
||||
- **Warning**: Steam orange
|
||||
- **Danger**: Steam red
|
||||
|
||||
## Visual Improvements
|
||||
|
||||
### 1. Card System
|
||||
- **Rounded Corners**: 12px radius for modern look
|
||||
- **Subtle Borders**: Light Steam borders (#3c4043)
|
||||
- **Inner Highlights**: Very subtle Steam blue inner glow
|
||||
- **Proper Spacing**: Consistent 10-15px padding
|
||||
|
||||
### 2. Typography Hierarchy
|
||||
- **Titles**: Steam blue (#66c0f4) with bold weight
|
||||
- **Labels**: Steam secondary text (#acb2b8)
|
||||
- **Body Text**: Steam body color (#8f98a0)
|
||||
- **Muted Text**: Steam muted (#67707b)
|
||||
|
||||
### 3. Interactive Elements
|
||||
- **Focus States**: Steam blue borders on input fields
|
||||
- **Hover Effects**: Lighter Steam blue on buttons
|
||||
- **Status Indicators**: Color-coded with Steam palette
|
||||
- **Blinking Animation**: Steam blue/muted for invalid states
|
||||
|
||||
## Consistency Improvements
|
||||
|
||||
### 1. Cross-Platform
|
||||
- GUI and TUI now use matching color schemes
|
||||
- Consistent terminology and styling
|
||||
- Same visual hierarchy in both interfaces
|
||||
|
||||
### 2. Error Handling
|
||||
- Consistent error colors (Steam red)
|
||||
- Warning colors (Steam orange)
|
||||
- Success colors (Steam green)
|
||||
- Info colors (Steam blue)
|
||||
|
||||
### 3. Accessibility
|
||||
- Better contrast ratios with Steam's tested palette
|
||||
- Consistent focus indicators
|
||||
- Clear visual hierarchy
|
||||
|
||||
## Files Modified
|
||||
|
||||
1. **steam_workshop_gui.py** - Main GUI application
|
||||
2. **progression_tui.py** - Terminal user interface
|
||||
3. **font_and_colors_update.py** - Color and styling utilities
|
||||
4. **update_checker.py** - Update dialog styling
|
||||
|
||||
## Testing
|
||||
|
||||
- ✅ Syntax validation passed for all Python files
|
||||
- ✅ Color scheme consistency verified
|
||||
- ✅ Both GUI and TUI updated per requirements
|
||||
- ✅ Steam Collections design patterns implemented
|
||||
|
||||
## Result
|
||||
|
||||
The application now features a cohesive Steam Collections inspired design with:
|
||||
- Modern dark blue theme matching Steam's visual identity
|
||||
- Improved card-based layouts for better organization
|
||||
- Consistent typography and color usage
|
||||
- Enhanced user experience with better visual hierarchy
|
||||
- Professional appearance matching Steam's design standards
|
||||
|
||||
All screens except the mod manager have been updated to match the Steam Collections page design as requested.
|
||||
@@ -0,0 +1,116 @@
|
||||
@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@ @@@@@@@@ @@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@ @@@@@@@@@@ @@@@@@@@@ @ @@@@@@@@@@
|
||||
@....................@@@ @....................%@@@ @@@...........*@@@ @@@+................@@ @....................@@@ @@......................@@ @@@...................@@ @@@..................@@@ @@.......@@ @@@@...........@@@ @..@@ @@.......@@
|
||||
@@.......................@@ @*......................@@@ @@..................@@@ @@@....................@@@@@@.......................@@ @@......................@@@@ @@%......................@@@ @@.......................@@@ @@.......@@@@ @@..................@@@ @....@@ @@.......@@@@
|
||||
@@.........................@@@ @@#........................@@@ @@......................@@@ @@.......................@@@@@@.........................@@@ @@@......................@@@@@@@.........................@@@@ @.........................%@@@@@@.......@@@@@ @@......................@@@ @=.....@@ @@.......@@@@@
|
||||
@@..........................@@@@@@*.........................@@@@ @@..........................@@@ @@........................@@@@@@..........................@@@@@@@......................@@@@@@..........................@@@@@@@..........................@@@@@@.......@@@@@@@..........................@@@@@@=......@@@ @@@.......@@@@@
|
||||
@@.......@@@@@@@@@@@........@@@@@@*.......@@@@@@@@@@@........@@@@@@.........#@@@@@@@@..........@@@ @@..........@@@@@@@@@@@@@@@@@@@@@.......@@@@@@@@@@@........@@@@@@@.......@@@@@@@@@@@@@@@@@@@@@.......@@@@@@@@@@@@@@@@@@@@@@@@@........@@@@@@@@@@@@@@@@@@@@@@@@@.......@@@@@@..........@@@@@@@@@.........@@@@@=........@@@@@@.......@@@@@
|
||||
@@.......@@@@@@@@@@@@.......@@@@@@#.......@@@@@@@@@@@........@@@@@........@@@@@@@@@@@@@.........@@@@@.........@@@@@@@@@@@@@@@@@@@@@@@.......@@@@@@@@@@@@.......@@@@@@@.......@@@@@@@@@@@@@@@@@@@@@.......@@@@@@@@@@@@@@@@@@@@@@@@@........@@@@@@@@@@@@@@@@@@@@@@@@@.......@@@@@@........@@@@@@@@@@@@@........@@@@=..........@@@@.......@@@@@
|
||||
@@.......@@@@@@@@@@@@.......@@@@@@#.......@@@@@@@@@@@........@@@@@.......@@@@@@@@@@@@@@@@.......@@@@@.......@@@@@@@@@@@@@@@@@@@@@@@@@.......@@@@@@@@@@@@.......@@@@@@@................@@@@@@@@@@@@..................@@@@@@@@@@@@@@#..................@@@@@@@@@@@@@@.......@@@@@........@@@@@@@@@@@@@@@........@@@+............@@.......@@@@@
|
||||
@@.......@.......:..........@@@@@@#.........................@@@@@........@@@@@@@@@@@@@@@@.......*@@@........@@@@@@@@@@@@@@@@@@.@@@@@@..........................@@@@@@@................@@@@@@@@@@@@@.....................@@@@@@@@@@@......................@@@@@@@@@@.......@@@@@.......@@@@@@@@@@@@@@@@@.......@@@+.....................@@@@@
|
||||
@@.........................@@@@@@@#........................:@@@@@........@@@@@@@ @@@@@........@@@........@@@@@@@@ @@@.....@@@@@@.........................@@@@@@@@................@@@@@ @@@@......................@@@@ @@@@@......................@@@ @@@.......@@@@@.......@@@@@@@@ @@@@@.......@@@+.....................@@@@@
|
||||
@@........................@@@@@@@@#.......................@@@@@@@@.......@@@@@@ @@@.......@@@@*.......@@@@@@ @........@@@@@@........................@@@@@@@@@................@@@@@ @@@@@@@....................@@@@ @@@@@@@.....................@@@@@@@.......@@@@@.......@@@@@@ @@@........@@@+.......@.............@@@@@
|
||||
@@......................@@@@@@@@@@#.....................@@@@@@@@@@........@@@@ @@........@@@@@........@@@@ @........@@@@@@.....................@@@@@@@@@@@@.......@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@........@@@@@@@@@@@@@@@@@@@@@@@@........@@@@@@@.......@@@@@........@@@@@ @@.......@@@@*.......@@............@@@@@
|
||||
@@.......@*********@@@@@@@@@@@@@@@#.......@****@........@@@@@@@@@@@........@@@@ @@@........@@@@@@.........@@@@ @@........@@@@@@.......@****%.........@@@@@@@@@@@.......@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@.......@@@@@ @@@@@@@@@@@@@@@@@@@........@@@@@@.......@@@@@@........@@@@ @@.........@@@@*.......@@@@..........@@@@@
|
||||
@@.......@@@@@@@@@@@@@@@@@@@@@@@@@%.......@@@@@@@........@@@@@@@@@@...........@@@@@@..........@@@@@@@@............@@@@@........@@@@@@.......@@@@@@@.........@@@@@@@@@@.......@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@........@@@@@@@@@@@@@@@@@@@@@@@@........@@@@@@@.......@@@@@@@..........@@@@@@...........@@@@@*.......@@@@@@........@@@@@
|
||||
@@.......@@@@@@@@@@@@@@@@@@@@ @@#.......@@@@@@@@........@@@@@@@@@@@........................@@@@@@@@@@@.......................@@@@@@.......@@@@@@@@=........@@@@ @@@......................@@@ @@.........................@@@@@@@..........................@@@@@@@.......@@@@@@@@........................@@@@@@@*.......@@@@@@@@......@@@@@
|
||||
@@.......@@@@@@@@@@@@@@@@@ @@@.......@@@@@@@@@........@@@@@@@@@@@@....................@@@@@@@@@@@@@@-.....................@@@@@@.......@@@@@@@@@@........@@@@ @@@......................@@@@@@........................@@@@@@@@.........................@@@@@@@@.......@@@@@@@@@@.....................@@@@@@@@@.......@@@@@@@@@@....@@@@@
|
||||
@@@......@@@@@@@@@@@@ @@@*......@@@@@@@@@@.........@@@@@@@@@@@@...............-@@@@@@@@@@@@@@@@@@@...................@@@@@@@......@@@@@@@@@@@........@@@@@@@@.....................@@@@@@@.....................@@@@@@@@@@@:.....................@@@@@@@@@@@......@@@@@@@@@@@@@...............@@@@@@@@@@@@@......@@@@@@@@@@@@..@@@@@
|
||||
@@@@@@@.+@@@@@@ @@@@@@@-.@@@@@@@@@@@@@@@@@@@#@@@@@@@@@@@@@@@@@@#..@@@@@@@@@@@@@@@@ @@@@@@@@@@@@-...........-@@@@@@@@@@@.+@@@@@@@@@@@@@@@@@@@*@@@@@@@@@@#................*@@@@@@@@@@+............@@@@@@@@@@@@@@@@@@@@............%@@@@@@@@@@@@@@@@@@@*.@@@@@@@@@@@@@@@@@@@@:.*@@@@@@@@@@@@@@@@@@@@@@#.@@@@@@@@@@@@@@@@@@@@
|
||||
@@@@@@@@@@@@@ @@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@ @@@@@@@@@@@
|
||||
@@@@@@@@@@@@@ @@@@@@@@@@@@@ @@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@ @@@@@@@@@
|
||||
@@@@@@@@@@ @@@@@@@@@ @@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@ @@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@ @@@@@@@@@@@@@@@@@@ @@@@@@@@@ @@@@@
|
||||
|
||||
@@@@@@@@%@@@@@@@@
|
||||
@@@....@@@@.....@@@@@@
|
||||
%@@.....@@@@..........@@@@
|
||||
%@@......@@@@.............@@@%
|
||||
:@@.......@@@@...............@@@-
|
||||
@@@.......@@@@.................@@@
|
||||
@@@........@@@@..................@@@
|
||||
@@@.....@@@@@@@@@@@.................@@+
|
||||
@@@......@@-. .+@@@@:..............@@-
|
||||
@@@.......@@ @@@@.............@@
|
||||
+@@........@@@@@@@@@@@ @@@............@@%
|
||||
-@@.........@@-......@@@@ =@@............@@
|
||||
@@:.........@@.........=@@. @@@...........@@@
|
||||
@@@..........@@..........:@@ .@@...........#@@
|
||||
@@@...........@@...........@@ @@............@@
|
||||
@@@............@@...........@@ @@...........=@@
|
||||
#@@.............@@..........@@@ :@@...........@@@
|
||||
*@@..............@@.........@@@ @@:...........@@=
|
||||
:@@...............@@@@....@@@@: @@@............@@
|
||||
@@:...............@@#@@@@@@+ #@@%............@@*
|
||||
@@@................@@ .@@@@.............@@%
|
||||
@@@.............@@@@@@@@@@@@@@@@@@..............@@@
|
||||
@@@..............@@ @@.-@@@@...................@@@
|
||||
@@@...............@@ @@........................@@@
|
||||
=@@................@@ @@......................@@@.
|
||||
@@.................@@ @@....................@@@#
|
||||
@@@.................@@ @@..................@@@%
|
||||
@@-..................@@ @@..............@@@@@
|
||||
-@@@@@@@@@@@@@@@@@@@@@@@ @@.......*@@@@@@@*
|
||||
+@@@@@@@@@@@@@@@@@@@@@ %@@@@@@@@@@%=
|
||||
|
||||
█████████████████
|
||||
███░░░░████░░░░░██████
|
||||
███░░░░░████░░░░░░░░░░████
|
||||
███░░░░░░████░░░░░░░░░░░░░████
|
||||
▒██░░░░░░░████░░░░░░░░░░░░░░░███▒
|
||||
███░░░░░░░████░░░░░░░░░░░░░░░░░███
|
||||
███░░░░░░░░████░░░░░░░░░░░░░░░░░░███
|
||||
███░░░░░███████████░░░░░░░░░░░░░░░░░██▓
|
||||
███░░░░░░██▒░ ░▓████▒░░░░░░░░░░░░░░██▒
|
||||
███░░░░░░░██ ████░░░░░░░░░░░░░██
|
||||
▓██░░░░░░░░███████████ ███░░░░░░░░░░░░███
|
||||
▒██░░░░░░░░░██▒░░░░░░████ ▓██░░░░░░░░░░░░██
|
||||
██▒░░░░░░░░░██░░░░░░░░░▒██░ ███░░░░░░░░░░░███
|
||||
███░░░░░░░░░░██░░░░░░░░░░▒██ ░██░░░░░░░░░░░▓██
|
||||
███░░░░░░░░░░░██░░░░░░░░░░░██ ██░░░░░░░░░░░░██
|
||||
███░░░░░░░░░░░░██░░░░░░░░░░░██ ██░░░░░░░░░░░▓██
|
||||
███░░░░░░░░░░░░░██░░░░░░░░░░███ ▒██░░░░░░░░░░░███
|
||||
▓██░░░░░░░░░░░░░░██░░░░░░░░░███ ██▒░░░░░░░░░░░██▓
|
||||
▒██░░░░░░░░░░░░░░░████░░░░████▒ ███░░░░░░░░░░░░██
|
||||
██▒░░░░░░░░░░░░░░░█████████▓ ████░░░░░░░░░░░░██▓
|
||||
███░░░░░░░░░░░░░░░░██ ░████░░░░░░░░░░░░░███
|
||||
███░░░░░░░░░░░░░██████████████████░░░░░░░░░░░░░░███
|
||||
███░░░░░░░░░░░░░░██ ██░▒████░░░░░░░░░░░░░░░░░░░███
|
||||
███░░░░░░░░░░░░░░░██ ██░░░░░░░░░░░░░░░░░░░░░░░░███
|
||||
▒██░░░░░░░░░░░░░░░░██ ██░░░░░░░░░░░░░░░░░░░░░░███░
|
||||
██░░░░░░░░░░░░░░░░░██ ██░░░░░░░░░░░░░░░░░░░░████
|
||||
███░░░░░░░░░░░░░░░░░██ ██░░░░░░░░░░░░░░░░░░████
|
||||
██▒░░░░░░░░░░░░░░░░░░██ ██░░░░░░░░░░░░░░█████
|
||||
▒███████████████████████ ██░░░░░░░▓███████▓
|
||||
▓█████████████████████ ████████████▒
|
||||
|
||||
``````` ````````````````````````````````````````´
|
||||
``````` ``````````````````````````````````````````
|
||||
``````´ ´``````````````````````````````````````````
|
||||
``````` ``````````````````````````````````````````´
|
||||
``````` ````````
|
||||
``````` ``````
|
||||
``````` ´`````´
|
||||
``````` ´``````
|
||||
``````` ``````´
|
||||
``````` ´```````
|
||||
``````´````````````````````````` ```````````````````````````````
|
||||
`````````````````````````````````` ´```````````````````````````
|
||||
```````````````````````````````````` ´``````````````````````´
|
||||
`````````````````````````````````````` ```````````````````
|
||||
``````` `````` `````````´
|
||||
``````` `````` ``````````
|
||||
``````` `````` `````````
|
||||
``````` `````` ``````````
|
||||
``````` `````` ``````````
|
||||
``````` `````` ``````````
|
||||
``````` `````` ``````````
|
||||
``````` `````` `````````´
|
||||
``````` `````` `````````
|
||||
``````` `````` ´`````````
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 591 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 186 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
@@ -0,0 +1,187 @@
|
||||
# Updated font loading and color scheme for Progression Loader
|
||||
# Steam Collections page inspired design:
|
||||
# - Dark blue theme matching Steam's signature colors
|
||||
# - Steam blue (#66c0f4) for highlights and accents
|
||||
# - Dark blue backgrounds (#1b2838, #2a475e) for depth
|
||||
# - Light blue-gray text (#c7d5e0) for readability
|
||||
# - Card-based layouts with rounded corners
|
||||
# - Subtle shadows and glows for depth
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
def get_resource_path(relative_path):
|
||||
"""Get absolute path to resource, works for dev and for PyInstaller"""
|
||||
try:
|
||||
# PyInstaller creates a temp folder and stores path in _MEIPASS
|
||||
base_path = sys._MEIPASS
|
||||
except Exception:
|
||||
base_path = os.path.abspath(".")
|
||||
|
||||
return os.path.join(base_path, relative_path)
|
||||
|
||||
# Steam Collections inspired color constants
|
||||
COLORS = {
|
||||
'bg_primary': '#1b2838', # Steam dark blue background
|
||||
'bg_secondary': '#2a475e', # Steam medium blue background
|
||||
'bg_tertiary': '#1e2328', # Steam darker background for cards
|
||||
'bg_card': '#16202d', # Steam card background
|
||||
'bg_hover': '#2a475e', # Steam hover state
|
||||
'text_primary': '#c7d5e0', # Steam primary text (light blue-gray)
|
||||
'text_highlight': '#66c0f4', # Steam blue highlight color
|
||||
'text_body': '#8f98a0', # Steam body text (muted blue-gray)
|
||||
'text_secondary': '#acb2b8', # Steam secondary text
|
||||
'text_muted': '#67707b', # Steam muted text
|
||||
'accent_green': '#5ba32b', # Steam success green
|
||||
'accent_red': '#cd5c5c', # Steam error red
|
||||
'accent_yellow': '#ffa500', # Steam warning orange
|
||||
'accent_blue': '#66c0f4', # Steam signature blue
|
||||
'border_light': '#3c4043', # Steam light border
|
||||
'border_dark': '#0e141b', # Steam dark border
|
||||
}
|
||||
|
||||
def load_all_georgia_fonts():
|
||||
"""Load all Georgia font variants using Windows AddFontResourceEx with private flag"""
|
||||
custom_font_available = False
|
||||
custom_font_family = None
|
||||
|
||||
# List of Georgia font files to load
|
||||
georgia_fonts = [
|
||||
"georgia.ttf", # Regular
|
||||
"georgiab.ttf", # Bold
|
||||
"georgiai.ttf", # Italic
|
||||
"georgiaz.ttf" # Bold Italic
|
||||
]
|
||||
|
||||
fonts_loaded = 0
|
||||
|
||||
try:
|
||||
for font_file in georgia_fonts:
|
||||
font_path = get_resource_path(os.path.join("art", font_file))
|
||||
if os.path.exists(font_path):
|
||||
abs_font_path = os.path.abspath(font_path)
|
||||
|
||||
# Use the Stack Overflow method with AddFontResourceEx
|
||||
success = _load_font_private(abs_font_path)
|
||||
|
||||
if success:
|
||||
fonts_loaded += 1
|
||||
print(f"Successfully loaded font: {font_file}")
|
||||
else:
|
||||
print(f"Failed to load font: {font_file}")
|
||||
else:
|
||||
print(f"Font file not found: {font_path}")
|
||||
|
||||
if fonts_loaded > 0:
|
||||
custom_font_available = True
|
||||
custom_font_family = "Georgia"
|
||||
print(f"Successfully loaded {fonts_loaded} Georgia font variants")
|
||||
else:
|
||||
print("No Georgia fonts could be loaded, using system fallback")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error loading fonts: {e}")
|
||||
custom_font_available = False
|
||||
custom_font_family = None
|
||||
|
||||
return custom_font_available, custom_font_family
|
||||
|
||||
def _load_font_private(fontpath):
|
||||
"""
|
||||
Load font privately using AddFontResourceEx
|
||||
Based on Stack Overflow solution by Felipe
|
||||
"""
|
||||
try:
|
||||
from ctypes import windll, byref, create_unicode_buffer
|
||||
|
||||
# Constants for AddFontResourceEx
|
||||
FR_PRIVATE = 0x10 # Font is private to this process
|
||||
FR_NOT_ENUM = 0x20 # Font won't appear in font enumeration
|
||||
|
||||
# Create unicode buffer for the font path
|
||||
pathbuf = create_unicode_buffer(fontpath)
|
||||
|
||||
# Use AddFontResourceExW for Unicode strings
|
||||
AddFontResourceEx = windll.gdi32.AddFontResourceExW
|
||||
|
||||
# Set flags: private (unloaded when process dies) and not enumerable
|
||||
flags = FR_PRIVATE | FR_NOT_ENUM
|
||||
|
||||
# Add the font resource
|
||||
numFontsAdded = AddFontResourceEx(byref(pathbuf), flags, 0)
|
||||
|
||||
return numFontsAdded > 0
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error in _load_font_private: {e}")
|
||||
return False
|
||||
|
||||
def apply_steam_styling():
|
||||
"""Apply Steam Collections page inspired styling to UI components"""
|
||||
return {
|
||||
'button_style': {
|
||||
'relief': 'flat',
|
||||
'borderwidth': 0,
|
||||
'highlightthickness': 0,
|
||||
'font': ('Georgia', 10, 'bold'),
|
||||
'cursor': 'hand2'
|
||||
},
|
||||
'entry_style': {
|
||||
'relief': 'flat',
|
||||
'borderwidth': 2,
|
||||
'highlightthickness': 0,
|
||||
'font': ('Georgia', 9),
|
||||
'insertbackground': COLORS['text_primary']
|
||||
},
|
||||
'label_style': {
|
||||
'font': ('Georgia', 9),
|
||||
'anchor': 'w'
|
||||
},
|
||||
'title_style': {
|
||||
'font': ('Georgia', 14, 'bold'),
|
||||
'anchor': 'center'
|
||||
},
|
||||
'card_style': {
|
||||
'relief': 'flat',
|
||||
'borderwidth': 1,
|
||||
'highlightthickness': 0
|
||||
}
|
||||
}
|
||||
|
||||
def get_steam_button_colors(button_type='default'):
|
||||
"""Get Steam-inspired button color schemes"""
|
||||
button_colors = {
|
||||
'default': {
|
||||
'bg': COLORS['bg_secondary'],
|
||||
'fg': COLORS['text_primary'],
|
||||
'activebackground': COLORS['bg_hover'],
|
||||
'activeforeground': COLORS['text_highlight']
|
||||
},
|
||||
'primary': {
|
||||
'bg': COLORS['accent_blue'],
|
||||
'fg': COLORS['bg_primary'],
|
||||
'activebackground': COLORS['text_highlight'],
|
||||
'activeforeground': COLORS['bg_primary']
|
||||
},
|
||||
'success': {
|
||||
'bg': COLORS['accent_green'],
|
||||
'fg': COLORS['text_primary'],
|
||||
'activebackground': '#6bb33f',
|
||||
'activeforeground': COLORS['text_primary']
|
||||
},
|
||||
'warning': {
|
||||
'bg': COLORS['accent_yellow'],
|
||||
'fg': COLORS['bg_primary'],
|
||||
'activebackground': '#ffb733',
|
||||
'activeforeground': COLORS['bg_primary']
|
||||
},
|
||||
'danger': {
|
||||
'bg': COLORS['accent_red'],
|
||||
'fg': COLORS['text_primary'],
|
||||
'activebackground': '#d66f6f',
|
||||
'activeforeground': COLORS['text_primary']
|
||||
}
|
||||
}
|
||||
|
||||
return button_colors.get(button_type, button_colors['default'])
|
||||
+1160
File diff suppressed because it is too large
Load Diff
+4
-1
@@ -1,4 +1,7 @@
|
||||
requests>=2.25.1
|
||||
python-dotenv>=0.19.0
|
||||
pyglet>=1.5.0
|
||||
Pillow>=8.0.0
|
||||
Pillow>=8.0.0
|
||||
packaging>=21.0
|
||||
customtkinter>=5.0.0
|
||||
rich>=13.0.0
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 962 KiB |
+2632
-749
File diff suppressed because it is too large
Load Diff
+250
@@ -0,0 +1,250 @@
|
||||
from PIL import Image, ImageColor, ImageDraw
|
||||
|
||||
|
||||
FONT_FAMILY = "Georgia"
|
||||
|
||||
COLORS = {
|
||||
"bg_primary": "#151515",
|
||||
"bg_secondary": "#1d1d1d",
|
||||
"bg_tertiary": "#212121",
|
||||
"bg_card": "#2b2b2b",
|
||||
"bg_hover": "#36312c",
|
||||
"bg_error": "#2f2926",
|
||||
"text_primary": "#f5f2ec",
|
||||
"text_highlight": "#f0a621",
|
||||
"text_body": "#c8c8c8",
|
||||
"text_secondary": "#ffffff",
|
||||
"text_muted": "#979797",
|
||||
"accent_green": "#3a8e35",
|
||||
"accent_red": "#ff4a46",
|
||||
"accent_yellow": "#f0a621",
|
||||
"accent_blue": "#d7b56a",
|
||||
"border_light": "#4d453b",
|
||||
"border_dark": "#0f0f0f",
|
||||
"stripe": "#1d1d1d",
|
||||
"stripe_soft": "#232323",
|
||||
}
|
||||
|
||||
|
||||
def _rgba(color, alpha=255):
|
||||
red, green, blue = ImageColor.getrgb(color)
|
||||
return red, green, blue, alpha
|
||||
|
||||
|
||||
def create_striped_texture(
|
||||
width,
|
||||
height,
|
||||
*,
|
||||
base_color=None,
|
||||
stripe_color=None,
|
||||
stripe_width=16,
|
||||
stripe_gap=18,
|
||||
stripe_alpha=110,
|
||||
corner_radius=0,
|
||||
border_color=None,
|
||||
border_width=0,
|
||||
border_alpha=255,
|
||||
):
|
||||
"""Create a dark diagonal striped texture matching the pack UI style."""
|
||||
width = max(1, int(width))
|
||||
height = max(1, int(height))
|
||||
|
||||
base = Image.new(
|
||||
"RGBA",
|
||||
(width, height),
|
||||
_rgba(base_color or COLORS["bg_card"]),
|
||||
)
|
||||
|
||||
stripes = Image.new("RGBA", (width, height), (0, 0, 0, 0))
|
||||
stripe_draw = ImageDraw.Draw(stripes)
|
||||
span = width + height
|
||||
step = max(1, stripe_width + stripe_gap)
|
||||
|
||||
for offset in range(-height, span + height, step):
|
||||
stripe_draw.line(
|
||||
(offset, 0, offset + height, height),
|
||||
fill=_rgba(stripe_color or COLORS["stripe"], stripe_alpha),
|
||||
width=max(1, stripe_width),
|
||||
)
|
||||
|
||||
image = Image.alpha_composite(base, stripes)
|
||||
|
||||
if border_color and border_width > 0:
|
||||
border_draw = ImageDraw.Draw(image)
|
||||
inset = max(1, border_width // 2)
|
||||
bounds = (inset, inset, width - inset - 1, height - inset - 1)
|
||||
if corner_radius > 0:
|
||||
border_draw.rounded_rectangle(
|
||||
bounds,
|
||||
radius=max(1, corner_radius - inset),
|
||||
outline=_rgba(border_color, border_alpha),
|
||||
width=border_width,
|
||||
)
|
||||
else:
|
||||
border_draw.rectangle(
|
||||
bounds,
|
||||
outline=_rgba(border_color, border_alpha),
|
||||
width=border_width,
|
||||
)
|
||||
|
||||
if corner_radius > 0:
|
||||
mask = Image.new("L", (width, height), 0)
|
||||
mask_draw = ImageDraw.Draw(mask)
|
||||
mask_draw.rounded_rectangle(
|
||||
(0, 0, width - 1, height - 1),
|
||||
radius=corner_radius,
|
||||
fill=255,
|
||||
)
|
||||
clipped = Image.new("RGBA", (width, height), (0, 0, 0, 0))
|
||||
clipped.paste(image, (0, 0), mask)
|
||||
image = clipped
|
||||
|
||||
return image
|
||||
|
||||
|
||||
def create_striped_panel(
|
||||
width,
|
||||
height,
|
||||
*,
|
||||
panel_color=None,
|
||||
stripe_color=None,
|
||||
stripe_width=18,
|
||||
stripe_gap=18,
|
||||
stripe_alpha=110,
|
||||
halo_padding=22,
|
||||
halo_alpha=135,
|
||||
panel_alpha=240,
|
||||
corner_radius=18,
|
||||
border_color=None,
|
||||
border_width=1,
|
||||
border_alpha=255,
|
||||
):
|
||||
"""Create a mostly solid panel with stripes that extend just beyond its edges."""
|
||||
width = max(1, int(width))
|
||||
height = max(1, int(height))
|
||||
halo_padding = max(0, int(halo_padding))
|
||||
total_width = width + (halo_padding * 2)
|
||||
total_height = height + (halo_padding * 2)
|
||||
|
||||
image = Image.new("RGBA", (total_width, total_height), (0, 0, 0, 0))
|
||||
|
||||
stripes = Image.new("RGBA", (total_width, total_height), (0, 0, 0, 0))
|
||||
stripe_draw = ImageDraw.Draw(stripes)
|
||||
span = total_width + total_height
|
||||
step = max(1, stripe_width + stripe_gap)
|
||||
|
||||
for offset in range(-total_height, span + total_height, step):
|
||||
stripe_draw.line(
|
||||
(offset, 0, offset + total_height, total_height),
|
||||
fill=_rgba(stripe_color or COLORS["stripe"], halo_alpha),
|
||||
width=max(1, stripe_width),
|
||||
)
|
||||
|
||||
halo_mask = Image.new("L", (total_width, total_height), 0)
|
||||
halo_draw = ImageDraw.Draw(halo_mask)
|
||||
halo_draw.rounded_rectangle(
|
||||
(0, 0, total_width - 1, total_height - 1),
|
||||
radius=max(1, corner_radius + halo_padding),
|
||||
fill=255,
|
||||
)
|
||||
halo_image = Image.new("RGBA", (total_width, total_height), (0, 0, 0, 0))
|
||||
halo_image.paste(stripes, (0, 0), halo_mask)
|
||||
image = Image.alpha_composite(image, halo_image)
|
||||
|
||||
panel_left = halo_padding
|
||||
panel_top = halo_padding
|
||||
panel_right = panel_left + width - 1
|
||||
panel_bottom = panel_top + height - 1
|
||||
|
||||
panel = Image.new("RGBA", (total_width, total_height), (0, 0, 0, 0))
|
||||
panel_draw = ImageDraw.Draw(panel)
|
||||
panel_draw.rounded_rectangle(
|
||||
(panel_left, panel_top, panel_right, panel_bottom),
|
||||
radius=corner_radius,
|
||||
fill=_rgba(panel_color or COLORS["bg_card"], panel_alpha),
|
||||
)
|
||||
|
||||
if border_color and border_width > 0:
|
||||
inset = max(1, border_width // 2)
|
||||
panel_draw.rounded_rectangle(
|
||||
(
|
||||
panel_left + inset,
|
||||
panel_top + inset,
|
||||
panel_right - inset,
|
||||
panel_bottom - inset,
|
||||
),
|
||||
radius=max(1, corner_radius - inset),
|
||||
outline=_rgba(border_color, border_alpha),
|
||||
width=border_width,
|
||||
)
|
||||
|
||||
image = Image.alpha_composite(image, panel)
|
||||
return image
|
||||
|
||||
|
||||
def load_cover_background(image_path, width, height, *, overlay_color=None, overlay_alpha=125):
|
||||
"""Load an image, scale it to cover, and apply a dark overlay for readability."""
|
||||
width = max(1, int(width))
|
||||
height = max(1, int(height))
|
||||
|
||||
image = Image.open(image_path).convert("RGBA")
|
||||
source_ratio = image.width / image.height
|
||||
target_ratio = width / height
|
||||
|
||||
if source_ratio > target_ratio:
|
||||
new_height = height
|
||||
new_width = int(height * source_ratio)
|
||||
else:
|
||||
new_width = width
|
||||
new_height = int(width / source_ratio)
|
||||
|
||||
image = image.resize((new_width, new_height), Image.Resampling.LANCZOS)
|
||||
left = (new_width - width) // 2
|
||||
top = (new_height - height) // 2
|
||||
image = image.crop((left, top, left + width, top + height))
|
||||
|
||||
overlay = Image.new(
|
||||
"RGBA",
|
||||
(width, height),
|
||||
_rgba(overlay_color or COLORS["bg_primary"], overlay_alpha),
|
||||
)
|
||||
return Image.alpha_composite(image, overlay)
|
||||
|
||||
|
||||
def style_text_button(
|
||||
button,
|
||||
foreground,
|
||||
background,
|
||||
*,
|
||||
hover_foreground=None,
|
||||
disabled_foreground=None,
|
||||
):
|
||||
"""Style a tkinter Button to look like a color-coded text action."""
|
||||
button.configure(
|
||||
bg=background,
|
||||
fg=foreground,
|
||||
activebackground=background,
|
||||
activeforeground=hover_foreground or COLORS["text_primary"],
|
||||
relief="flat",
|
||||
bd=0,
|
||||
borderwidth=0,
|
||||
highlightthickness=0,
|
||||
disabledforeground=disabled_foreground or COLORS["text_muted"],
|
||||
cursor="hand2",
|
||||
)
|
||||
|
||||
|
||||
def center_window(window, width, height, parent=None):
|
||||
"""Center a window either on its parent or the current screen."""
|
||||
width = int(width)
|
||||
height = int(height)
|
||||
window.update_idletasks()
|
||||
|
||||
if parent is not None and parent.winfo_exists():
|
||||
x = parent.winfo_rootx() + (parent.winfo_width() // 2) - (width // 2)
|
||||
y = parent.winfo_rooty() + (parent.winfo_height() // 2) - (height // 2)
|
||||
else:
|
||||
x = (window.winfo_screenwidth() // 2) - (width // 2)
|
||||
y = (window.winfo_screenheight() // 2) - (height // 2)
|
||||
|
||||
window.geometry(f"{width}x{height}+{max(0, x)}+{max(0, y)}")
|
||||
@@ -0,0 +1,577 @@
|
||||
"""
|
||||
Update Checker Module for Progression Loader
|
||||
Checks for new releases from https://git.hudsonriggs.systems/HRiggs/ProgressionMods/releases
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
import re
|
||||
from packaging import version
|
||||
import tkinter as tk
|
||||
from tkinter import messagebox
|
||||
import webbrowser
|
||||
import threading
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
import os
|
||||
from pathlib import Path
|
||||
from update_config import get_update_config
|
||||
from ui_theme import COLORS, FONT_FAMILY, style_text_button, center_window
|
||||
|
||||
class UpdateChecker:
|
||||
def __init__(self, current_version=None):
|
||||
# Load configuration
|
||||
self.config = get_update_config()
|
||||
|
||||
# Set version
|
||||
self.current_version = current_version or self.config["current_version"]
|
||||
|
||||
# API configuration
|
||||
self.api_base_url = self.config["api_base_url"]
|
||||
self.repo_owner = self.config["repo_owner"]
|
||||
self.repo_name = self.config["repo_name"]
|
||||
self.releases_url = f"{self.api_base_url}/repos/{self.repo_owner}/{self.repo_name}/releases"
|
||||
|
||||
# Check configuration
|
||||
self.last_check_file = Path(self.config["last_check_file"]) if self.config["last_check_file"] else None
|
||||
self.check_interval_hours = self.config["check_interval_hours"]
|
||||
self.include_prereleases = self.config["include_prereleases"]
|
||||
|
||||
# Request configuration
|
||||
self.user_agent = self.config["user_agent"]
|
||||
self.request_timeout = self.config["request_timeout"]
|
||||
|
||||
def get_current_version(self):
|
||||
"""Get the current version of the application"""
|
||||
return self.current_version
|
||||
|
||||
def set_current_version(self, version_string):
|
||||
"""Set the current version of the application"""
|
||||
self.current_version = version_string
|
||||
|
||||
def should_check_for_updates(self):
|
||||
"""Check if enough time has passed since last update check"""
|
||||
# If no persistent file or check interval is 0, always check
|
||||
if not self.last_check_file or self.check_interval_hours == 0:
|
||||
return True
|
||||
|
||||
if not self.last_check_file.exists():
|
||||
return True
|
||||
|
||||
try:
|
||||
with open(self.last_check_file, 'r') as f:
|
||||
data = json.load(f)
|
||||
last_check = datetime.fromisoformat(data.get('last_check', '2000-01-01'))
|
||||
return datetime.now() - last_check > timedelta(hours=self.check_interval_hours)
|
||||
except (json.JSONDecodeError, ValueError, KeyError):
|
||||
return True
|
||||
|
||||
def save_last_check_time(self):
|
||||
"""Save the current time as the last check time"""
|
||||
# Skip saving if no persistent file is configured
|
||||
if not self.last_check_file:
|
||||
return
|
||||
|
||||
try:
|
||||
data = {'last_check': datetime.now().isoformat()}
|
||||
with open(self.last_check_file, 'w') as f:
|
||||
json.dump(data, f)
|
||||
except Exception as e:
|
||||
print(f"Could not save last check time: {e}")
|
||||
|
||||
def fetch_latest_release(self):
|
||||
"""Fetch the latest release information from the API"""
|
||||
try:
|
||||
headers = {
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': self.user_agent
|
||||
}
|
||||
|
||||
response = requests.get(self.releases_url, headers=headers, timeout=self.request_timeout)
|
||||
response.raise_for_status()
|
||||
|
||||
releases = response.json()
|
||||
|
||||
if not releases:
|
||||
return None
|
||||
|
||||
# Filter releases based on prerelease setting
|
||||
if not self.include_prereleases:
|
||||
releases = [r for r in releases if not r.get('prerelease', False)]
|
||||
|
||||
# Filter out draft releases
|
||||
releases = [r for r in releases if not r.get('draft', False)]
|
||||
|
||||
if not releases:
|
||||
return None
|
||||
|
||||
# Get the latest release (first in the filtered list)
|
||||
latest_release = releases[0]
|
||||
|
||||
return {
|
||||
'version': latest_release.get('tag_name', '').lstrip('v'),
|
||||
'name': latest_release.get('name', ''),
|
||||
'body': latest_release.get('body', ''),
|
||||
'html_url': latest_release.get('html_url', ''),
|
||||
'published_at': latest_release.get('published_at', ''),
|
||||
'assets': latest_release.get('assets', []),
|
||||
'prerelease': latest_release.get('prerelease', False),
|
||||
'draft': latest_release.get('draft', False)
|
||||
}
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Network error checking for updates: {e}")
|
||||
return None
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"Error parsing release data: {e}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"Unexpected error checking for updates: {e}")
|
||||
return None
|
||||
|
||||
def is_newer_version(self, latest_version):
|
||||
"""Compare versions to see if the latest is newer than current"""
|
||||
try:
|
||||
current = version.parse(self.current_version)
|
||||
latest = version.parse(latest_version)
|
||||
return latest > current
|
||||
except Exception as e:
|
||||
print(f"Error comparing versions: {e}")
|
||||
# Fallback to string comparison
|
||||
return latest_version != self.current_version
|
||||
|
||||
def check_for_updates_async(self, callback=None):
|
||||
"""Check for updates in a background thread"""
|
||||
def check_thread():
|
||||
try:
|
||||
if not self.should_check_for_updates():
|
||||
if callback:
|
||||
callback(None, "No check needed")
|
||||
return
|
||||
|
||||
latest_release = self.fetch_latest_release()
|
||||
self.save_last_check_time()
|
||||
|
||||
if callback:
|
||||
callback(latest_release, None)
|
||||
|
||||
except Exception as e:
|
||||
if callback:
|
||||
callback(None, str(e))
|
||||
|
||||
thread = threading.Thread(target=check_thread, daemon=True)
|
||||
thread.start()
|
||||
|
||||
def check_for_updates_sync(self):
|
||||
"""Check for updates synchronously"""
|
||||
if not self.should_check_for_updates():
|
||||
return None, "No check needed"
|
||||
|
||||
latest_release = self.fetch_latest_release()
|
||||
self.save_last_check_time()
|
||||
|
||||
return latest_release, None
|
||||
|
||||
def _show_notice_dialog(self, parent_window, title, message, accent_color):
|
||||
"""Show a small themed notice dialog."""
|
||||
dialog = tk.Toplevel(parent_window)
|
||||
dialog.title(title)
|
||||
dialog.configure(bg=COLORS['bg_primary'])
|
||||
dialog.resizable(False, False)
|
||||
dialog.attributes('-topmost', True)
|
||||
|
||||
panel = tk.Frame(
|
||||
dialog,
|
||||
bg=COLORS['bg_card'],
|
||||
highlightbackground=COLORS['border_light'],
|
||||
highlightcolor=COLORS['border_light'],
|
||||
highlightthickness=1,
|
||||
)
|
||||
panel.pack(fill='both', expand=True, padx=16, pady=16)
|
||||
|
||||
tk.Label(
|
||||
panel,
|
||||
text=title,
|
||||
fg=accent_color,
|
||||
bg=COLORS['bg_card'],
|
||||
font=(FONT_FAMILY, 18, 'bold italic'),
|
||||
).pack(pady=(18, 10))
|
||||
|
||||
tk.Label(
|
||||
panel,
|
||||
text=message,
|
||||
fg=COLORS['text_body'],
|
||||
bg=COLORS['bg_card'],
|
||||
font=(FONT_FAMILY, 11),
|
||||
justify='center',
|
||||
wraplength=340,
|
||||
).pack(padx=24, pady=(0, 18))
|
||||
|
||||
close_btn = tk.Button(
|
||||
panel,
|
||||
text="Close",
|
||||
command=dialog.destroy,
|
||||
bg=COLORS['bg_card'],
|
||||
font=(FONT_FAMILY, 12, 'bold italic'),
|
||||
padx=0,
|
||||
pady=6,
|
||||
)
|
||||
style_text_button(close_btn, COLORS['accent_green'], COLORS['bg_card'])
|
||||
close_btn.pack(pady=(0, 18))
|
||||
|
||||
center_window(dialog, 420, 220, parent_window)
|
||||
dialog.protocol("WM_DELETE_WINDOW", dialog.destroy)
|
||||
return dialog
|
||||
|
||||
def show_update_dialog(self, parent_window, release_info):
|
||||
"""Show an update notification dialog"""
|
||||
if not release_info:
|
||||
return
|
||||
|
||||
latest_version = release_info['version']
|
||||
|
||||
if not self.is_newer_version(latest_version):
|
||||
return # No update needed
|
||||
|
||||
# Create update dialog
|
||||
dialog = tk.Toplevel(parent_window)
|
||||
dialog.title("Update Available - Progression Loader")
|
||||
dialog.configure(bg=COLORS['bg_primary'])
|
||||
dialog.resizable(False, False)
|
||||
dialog.attributes('-topmost', True)
|
||||
|
||||
# Set window icon if available
|
||||
try:
|
||||
if hasattr(parent_window, 'iconbitmap'):
|
||||
dialog.iconbitmap(parent_window.iconbitmap())
|
||||
except:
|
||||
pass
|
||||
|
||||
dialog_width = 500
|
||||
dialog_height = 430
|
||||
center_window(dialog, dialog_width, dialog_height, parent_window)
|
||||
|
||||
panel = tk.Frame(
|
||||
dialog,
|
||||
bg=COLORS['bg_card'],
|
||||
highlightbackground=COLORS['border_light'],
|
||||
highlightcolor=COLORS['border_light'],
|
||||
highlightthickness=1,
|
||||
)
|
||||
panel.pack(fill='both', expand=True, padx=16, pady=16)
|
||||
|
||||
title_label = tk.Label(
|
||||
panel,
|
||||
text="Update Available",
|
||||
fg=COLORS['text_highlight'],
|
||||
bg=COLORS['bg_card'],
|
||||
font=(FONT_FAMILY, 18, 'bold italic'),
|
||||
)
|
||||
title_label.pack(pady=20)
|
||||
|
||||
# Version info
|
||||
version_frame = tk.Frame(panel, bg=COLORS['bg_card'])
|
||||
version_frame.pack(pady=10)
|
||||
|
||||
current_label = tk.Label(version_frame,
|
||||
text=f"Current Version: {self.current_version}",
|
||||
fg=COLORS['text_body'], bg=COLORS['bg_card'],
|
||||
font=(FONT_FAMILY, 12))
|
||||
current_label.pack()
|
||||
|
||||
latest_label = tk.Label(version_frame,
|
||||
text=f"Latest Version: {latest_version}",
|
||||
fg=COLORS['accent_green'], bg=COLORS['bg_card'],
|
||||
font=(FONT_FAMILY, 12, 'bold'))
|
||||
latest_label.pack()
|
||||
|
||||
# Release notes
|
||||
if release_info.get('body'):
|
||||
notes_label = tk.Label(panel,
|
||||
text="Release Notes:",
|
||||
fg=COLORS['text_primary'], bg=COLORS['bg_card'],
|
||||
font=(FONT_FAMILY, 12, 'bold italic'))
|
||||
notes_label.pack(pady=(20, 5))
|
||||
|
||||
# Create scrollable text widget for release notes
|
||||
notes_frame = tk.Frame(panel, bg=COLORS['bg_card'])
|
||||
notes_frame.pack(fill='both', expand=True, padx=20, pady=(0, 20))
|
||||
|
||||
notes_text = tk.Text(notes_frame,
|
||||
height=8,
|
||||
bg=COLORS['bg_tertiary'], fg=COLORS['text_body'],
|
||||
font=(FONT_FAMILY, 10),
|
||||
wrap=tk.WORD,
|
||||
state='disabled',
|
||||
relief='flat',
|
||||
bd=0,
|
||||
insertbackground=COLORS['text_primary'],
|
||||
highlightthickness=1,
|
||||
highlightbackground=COLORS['border_light'])
|
||||
|
||||
scrollbar = tk.Scrollbar(
|
||||
notes_frame,
|
||||
bg=COLORS['bg_tertiary'],
|
||||
troughcolor=COLORS['bg_secondary'],
|
||||
activebackground=COLORS['bg_hover'],
|
||||
)
|
||||
scrollbar.pack(side='right', fill='y')
|
||||
|
||||
notes_text.pack(side='left', fill='both', expand=True)
|
||||
notes_text.config(yscrollcommand=scrollbar.set)
|
||||
scrollbar.config(command=notes_text.yview)
|
||||
|
||||
# Insert release notes
|
||||
notes_text.config(state='normal')
|
||||
notes_text.insert('1.0', release_info['body'])
|
||||
notes_text.config(state='disabled')
|
||||
|
||||
# Buttons
|
||||
button_frame = tk.Frame(panel, bg=COLORS['bg_card'])
|
||||
button_frame.pack(pady=20)
|
||||
|
||||
def download_update():
|
||||
webbrowser.open(release_info['html_url'])
|
||||
dialog.destroy()
|
||||
|
||||
def remind_later():
|
||||
# If no persistent file, just close the dialog
|
||||
if not self.last_check_file:
|
||||
dialog.destroy()
|
||||
return
|
||||
|
||||
# Reset last check time to check again sooner
|
||||
try:
|
||||
data = {'last_check': (datetime.now() - timedelta(hours=self.check_interval_hours - 4)).isoformat()}
|
||||
with open(self.last_check_file, 'w') as f:
|
||||
json.dump(data, f)
|
||||
except:
|
||||
pass
|
||||
dialog.destroy()
|
||||
|
||||
def skip_version():
|
||||
# If no persistent file, just close the dialog
|
||||
if not self.last_check_file:
|
||||
dialog.destroy()
|
||||
return
|
||||
|
||||
# Save this version as skipped
|
||||
try:
|
||||
data = {
|
||||
'last_check': datetime.now().isoformat(),
|
||||
'skipped_version': latest_version
|
||||
}
|
||||
with open(self.last_check_file, 'w') as f:
|
||||
json.dump(data, f)
|
||||
except:
|
||||
pass
|
||||
dialog.destroy()
|
||||
|
||||
download_btn = tk.Button(button_frame,
|
||||
text="Download Update",
|
||||
command=download_update,
|
||||
bg=COLORS['bg_card'],
|
||||
font=(FONT_FAMILY, 12, 'bold italic'),
|
||||
padx=0, pady=6)
|
||||
style_text_button(download_btn, COLORS['accent_green'], COLORS['bg_card'])
|
||||
download_btn.pack(side='left', padx=5)
|
||||
|
||||
# Only show remind/skip buttons if persistence is enabled
|
||||
if self.last_check_file:
|
||||
later_btn = tk.Button(button_frame,
|
||||
text="Remind Later",
|
||||
command=remind_later,
|
||||
bg=COLORS['bg_card'],
|
||||
font=(FONT_FAMILY, 12, 'bold italic'),
|
||||
padx=0, pady=6)
|
||||
style_text_button(later_btn, COLORS['accent_yellow'], COLORS['bg_card'])
|
||||
later_btn.pack(side='left', padx=5)
|
||||
|
||||
skip_btn = tk.Button(button_frame,
|
||||
text="Skip Version",
|
||||
command=skip_version,
|
||||
bg=COLORS['bg_card'],
|
||||
font=(FONT_FAMILY, 12, 'bold italic'),
|
||||
padx=0, pady=6)
|
||||
style_text_button(skip_btn, COLORS['accent_red'], COLORS['bg_card'])
|
||||
skip_btn.pack(side='left', padx=5)
|
||||
else:
|
||||
close_btn = tk.Button(button_frame,
|
||||
text="Close",
|
||||
command=dialog.destroy,
|
||||
bg=COLORS['bg_card'],
|
||||
font=(FONT_FAMILY, 12, 'bold italic'),
|
||||
padx=0, pady=6)
|
||||
style_text_button(close_btn, COLORS['accent_red'], COLORS['bg_card'])
|
||||
close_btn.pack(side='left', padx=5)
|
||||
|
||||
# Handle window close
|
||||
dialog.protocol("WM_DELETE_WINDOW", dialog.destroy)
|
||||
|
||||
return dialog
|
||||
|
||||
def should_skip_version(self, version_string):
|
||||
"""Check if this version should be skipped"""
|
||||
# Skip version checking if no persistent file is configured
|
||||
if not self.last_check_file:
|
||||
return False
|
||||
|
||||
try:
|
||||
with open(self.last_check_file, 'r') as f:
|
||||
data = json.load(f)
|
||||
return data.get('skipped_version') == version_string
|
||||
except:
|
||||
return False
|
||||
|
||||
def manual_check_for_updates(self, parent_window):
|
||||
"""Manually check for updates and show result"""
|
||||
def check_complete(release_info, error):
|
||||
if error:
|
||||
self._show_notice_dialog(
|
||||
parent_window,
|
||||
"Update Check Failed",
|
||||
f"Could not check for updates:\n{error}",
|
||||
COLORS['accent_red'],
|
||||
)
|
||||
return
|
||||
|
||||
if not release_info:
|
||||
self._show_notice_dialog(
|
||||
parent_window,
|
||||
"No Updates",
|
||||
"Could not retrieve release information.",
|
||||
COLORS['accent_yellow'],
|
||||
)
|
||||
return
|
||||
|
||||
latest_version = release_info['version']
|
||||
|
||||
if self.is_newer_version(latest_version) and not self.should_skip_version(latest_version):
|
||||
self.show_update_dialog(parent_window, release_info)
|
||||
else:
|
||||
self._show_notice_dialog(
|
||||
parent_window,
|
||||
"Up To Date",
|
||||
f"You are running the latest version ({self.current_version}).",
|
||||
COLORS['accent_green'],
|
||||
)
|
||||
|
||||
# Show checking message
|
||||
checking_dialog = tk.Toplevel(parent_window)
|
||||
checking_dialog.title("Checking for Updates")
|
||||
checking_dialog.configure(bg=COLORS['bg_primary'])
|
||||
checking_dialog.resizable(False, False)
|
||||
checking_dialog.attributes('-topmost', True)
|
||||
|
||||
panel = tk.Frame(
|
||||
checking_dialog,
|
||||
bg=COLORS['bg_card'],
|
||||
highlightbackground=COLORS['border_light'],
|
||||
highlightcolor=COLORS['border_light'],
|
||||
highlightthickness=1,
|
||||
)
|
||||
panel.pack(fill='both', expand=True, padx=14, pady=14)
|
||||
|
||||
label = tk.Label(
|
||||
panel,
|
||||
text="Checking for updates...",
|
||||
fg=COLORS['text_primary'],
|
||||
bg=COLORS['bg_card'],
|
||||
font=(FONT_FAMILY, 12, 'bold italic'),
|
||||
)
|
||||
label.pack(expand=True)
|
||||
|
||||
center_window(checking_dialog, 320, 120, parent_window)
|
||||
|
||||
def check_and_close():
|
||||
release_info, error = self.check_for_updates_sync()
|
||||
parent_window.after(0, lambda: checking_dialog.destroy())
|
||||
parent_window.after(0, lambda: check_complete(release_info, error))
|
||||
|
||||
# Start check in background
|
||||
threading.Thread(target=check_and_close, daemon=True).start()
|
||||
|
||||
|
||||
def integrate_update_checker_with_gui(gui_class):
|
||||
"""
|
||||
Decorator to integrate update checker with the main GUI class
|
||||
"""
|
||||
original_init = gui_class.__init__
|
||||
|
||||
def new_init(self, *args, **kwargs):
|
||||
# Call original init
|
||||
original_init(self, *args, **kwargs)
|
||||
|
||||
# Initialize update checker
|
||||
self.update_checker = UpdateChecker()
|
||||
|
||||
# Add update check to menu if it exists
|
||||
self.add_update_menu()
|
||||
|
||||
# Check for updates on startup (after a short delay)
|
||||
self.root.after(5000, self.check_for_updates_on_startup)
|
||||
|
||||
def add_update_menu(self):
|
||||
"""Add update check option to the application"""
|
||||
try:
|
||||
# Try to add to existing menu bar
|
||||
if hasattr(self, 'menubar'):
|
||||
help_menu = tk.Menu(self.menubar, tearoff=0)
|
||||
help_menu.add_command(label="Check for Updates", command=self.manual_update_check)
|
||||
self.menubar.add_cascade(label="Help", menu=help_menu)
|
||||
else:
|
||||
# Create a simple button in the GUI
|
||||
self.add_update_button()
|
||||
except Exception as e:
|
||||
print(f"Could not add update menu: {e}")
|
||||
|
||||
def add_update_button(self):
|
||||
"""Add an update check button to the GUI"""
|
||||
try:
|
||||
# Find a suitable parent frame (try footer first, then main frame)
|
||||
parent_frame = None
|
||||
if hasattr(self, 'footer_frame'):
|
||||
parent_frame = self.footer_frame
|
||||
elif hasattr(self, 'main_frame'):
|
||||
parent_frame = self.main_frame
|
||||
elif hasattr(self, 'root'):
|
||||
parent_frame = self.root
|
||||
|
||||
if parent_frame:
|
||||
update_btn = tk.Button(parent_frame,
|
||||
text="Check Updates",
|
||||
command=self.manual_update_check,
|
||||
bg=COLORS['bg_card'],
|
||||
font=(FONT_FAMILY, 10, 'bold italic'),
|
||||
padx=8, pady=3)
|
||||
style_text_button(update_btn, COLORS['accent_yellow'], COLORS['bg_card'])
|
||||
update_btn.pack(side='right', padx=5, pady=5)
|
||||
except Exception as e:
|
||||
print(f"Could not add update button: {e}")
|
||||
|
||||
def check_for_updates_on_startup(self):
|
||||
"""Check for updates when the application starts"""
|
||||
def update_callback(release_info, error):
|
||||
if error or not release_info:
|
||||
return # Silently fail on startup
|
||||
|
||||
latest_version = release_info['version']
|
||||
if (self.update_checker.is_newer_version(latest_version) and
|
||||
not self.update_checker.should_skip_version(latest_version)):
|
||||
# Show update dialog
|
||||
self.update_checker.show_update_dialog(self.root, release_info)
|
||||
|
||||
self.update_checker.check_for_updates_async(update_callback)
|
||||
|
||||
def manual_update_check(self):
|
||||
"""Manually triggered update check"""
|
||||
self.update_checker.manual_check_for_updates(self.root)
|
||||
|
||||
# Add methods to the class
|
||||
gui_class.add_update_menu = add_update_menu
|
||||
gui_class.add_update_button = add_update_button
|
||||
gui_class.check_for_updates_on_startup = check_for_updates_on_startup
|
||||
gui_class.manual_update_check = manual_update_check
|
||||
gui_class.__init__ = new_init
|
||||
|
||||
return gui_class
|
||||
@@ -0,0 +1,50 @@
|
||||
"""
|
||||
Configuration settings for the update checker
|
||||
"""
|
||||
|
||||
# Update checker configuration
|
||||
UPDATE_CONFIG = {
|
||||
# Current version of the application
|
||||
"current_version": "0.1.1",
|
||||
|
||||
# Repository information
|
||||
"repo_owner": "HRiggs",
|
||||
"repo_name": "ProgressionMods",
|
||||
"api_base_url": "https://git.hudsonriggs.systems/api/v1",
|
||||
|
||||
# Update check frequency (in hours) - set to 0 to check every startup
|
||||
"check_interval_hours": 0,
|
||||
|
||||
# Whether to check for updates on startup
|
||||
"check_on_startup": True,
|
||||
|
||||
# Whether updates are required (blocks app if true)
|
||||
"updates_required": True,
|
||||
|
||||
# Whether to auto-restart after successful update
|
||||
"auto_restart_after_update": True,
|
||||
|
||||
# Whether to include pre-releases in update checks
|
||||
"include_prereleases": True,
|
||||
|
||||
# User agent string for API requests
|
||||
"user_agent": "ProgressionLoader-UpdateChecker/1.0",
|
||||
|
||||
# Request timeout in seconds
|
||||
"request_timeout": 10,
|
||||
|
||||
# File to store last check time and skipped versions (set to None to disable persistence)
|
||||
"last_check_file": None
|
||||
}
|
||||
|
||||
def get_update_config():
|
||||
"""Get the update configuration"""
|
||||
return UPDATE_CONFIG.copy()
|
||||
|
||||
def set_current_version(version):
|
||||
"""Set the current version"""
|
||||
UPDATE_CONFIG["current_version"] = version
|
||||
|
||||
def get_current_version():
|
||||
"""Get the current version"""
|
||||
return UPDATE_CONFIG["current_version"]
|
||||
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Version Manager for Progression Loader
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
|
||||
def get_current_version():
|
||||
"""Get the current version from update_config.py"""
|
||||
try:
|
||||
with open('update_config.py', 'r') as f:
|
||||
content = f.read()
|
||||
match = re.search(r'"current_version":\s*"([^"]+)"', content)
|
||||
if match:
|
||||
return match.group(1)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
return None
|
||||
|
||||
def set_version(new_version):
|
||||
"""Set the version in update_config.py"""
|
||||
try:
|
||||
with open('update_config.py', 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
updated_content = re.sub(
|
||||
r'"current_version":\s*"[^"]+"',
|
||||
f'"current_version": "{new_version}"',
|
||||
content
|
||||
)
|
||||
|
||||
with open('update_config.py', 'w') as f:
|
||||
f.write(updated_content)
|
||||
|
||||
print(f"✅ Updated version to {new_version}")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ Error updating version: {e}")
|
||||
return False
|
||||
|
||||
def validate_version(version_string):
|
||||
"""Validate semantic versioning format"""
|
||||
pattern = r'^(\d+)\.(\d+)\.(\d+)(?:-([a-zA-Z0-9\-\.]+))?$'
|
||||
return re.match(pattern, version_string) is not None
|
||||
|
||||
def suggest_next_version(current_version):
|
||||
"""Suggest next version numbers"""
|
||||
if not current_version:
|
||||
return ["1.0.0"]
|
||||
|
||||
try:
|
||||
parts = current_version.split('.')
|
||||
if len(parts) >= 3:
|
||||
major, minor, patch = int(parts[0]), int(parts[1]), int(parts[2])
|
||||
return [
|
||||
f"{major}.{minor}.{patch + 1}", # Patch
|
||||
f"{major}.{minor + 1}.0", # Minor
|
||||
f"{major + 1}.0.0" # Major
|
||||
]
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return ["1.0.0"]
|
||||
|
||||
def main():
|
||||
"""Main interface"""
|
||||
if len(sys.argv) < 2:
|
||||
current = get_current_version()
|
||||
print(f"Current Version: {current or 'Not found'}")
|
||||
|
||||
if current:
|
||||
suggestions = suggest_next_version(current)
|
||||
print("Suggested versions:", ", ".join(suggestions))
|
||||
|
||||
print("\nUsage:")
|
||||
print(" python version_manager.py <version> # Set version")
|
||||
print(" python version_manager.py current # Show current")
|
||||
print("\nExample: python version_manager.py 1.0.1")
|
||||
return
|
||||
|
||||
command = sys.argv[1]
|
||||
|
||||
if command == "current":
|
||||
current = get_current_version()
|
||||
print(f"Current version: {current or 'Not found'}")
|
||||
return
|
||||
|
||||
# Set version
|
||||
new_version = command
|
||||
|
||||
if not validate_version(new_version):
|
||||
print(f"❌ Invalid version format: {new_version}")
|
||||
print("Use format: MAJOR.MINOR.PATCH (e.g., 1.0.1)")
|
||||
return
|
||||
|
||||
if set_version(new_version):
|
||||
print("Next steps:")
|
||||
print(f" git add update_config.py")
|
||||
print(f" git commit -m 'Bump version to {new_version}'")
|
||||
print(f" git tag v{new_version} && git push --tags")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user