Compare commits
53 Commits
v1.0.2-bet
...
v1.3.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d730fe61ae | ||
|
|
f657c18c54 | ||
|
|
4bf0d1819d | ||
|
|
a05cce1c18 | ||
|
|
ae7dd9dbd0 | ||
|
|
c484c8958c | ||
|
|
6f4f53f7f5 | ||
|
|
cbf791a348 | ||
|
|
9397e1508f | ||
|
|
294ab81211 | ||
|
|
7a631543fa | ||
|
|
e0a62d72d4 | ||
|
|
e54c71abed | ||
|
|
e646858e43 | ||
|
|
dc7c2682cf | ||
|
|
08282c8a22 | ||
|
|
308b284d17 | ||
|
|
51c6b7337b | ||
|
|
bb73d535ce | ||
|
|
38f536bc1c | ||
|
|
686a5219eb | ||
|
|
9f3cf1cb1f | ||
|
|
0a5f00d3fb | ||
|
|
931ecc0d92 | ||
|
|
f7f70a0b8a | ||
|
|
d280c6c5f3 | ||
|
|
9bbe1c7de8 | ||
|
|
18a51e37a1 | ||
|
|
1eb8f92946 | ||
|
|
62b80cc565 | ||
|
|
82bd475383 | ||
|
|
53be3e3bb2 | ||
|
|
69135fc4a4 | ||
|
|
d1871a5384 | ||
|
|
acce153720 | ||
|
|
a97dc69cee | ||
|
|
c8318ede9f | ||
|
|
c7593b6c6c | ||
|
|
a460e9d3b7 | ||
|
|
6559b15894 | ||
|
|
653c301ba9 | ||
|
|
a6f21c34b1 | ||
|
|
a2d5a38f68 | ||
|
|
ec95d8e975 | ||
|
|
cd80e81d0b | ||
|
|
2324afaa50 | ||
|
|
68a458e612 | ||
|
|
5a6ec9e6cf | ||
|
|
2c0e67eaf3 | ||
|
|
039d0702c7 | ||
|
|
37f872c6bd | ||
|
|
2ad81160ba | ||
|
|
caae074587 |
1
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -31,6 +31,7 @@ If applicable, add screenshots to help explain your problem.
|
||||
- Desktop Environment: [e.g. GNOME, KDE, etc.]
|
||||
- CreamLinux Version: [e.g. 0.1.0]
|
||||
- Steam Version: [e.g. latest]
|
||||
- Graphics card: [e.g. 2060 rtx]
|
||||
|
||||
## Game Information
|
||||
|
||||
|
||||
144
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,144 @@
|
||||
name: 'Build and Release'
|
||||
|
||||
on:
|
||||
workflow_dispatch: # Allows manual triggering
|
||||
|
||||
jobs:
|
||||
create-release:
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: 'ubuntu-24.04'
|
||||
outputs:
|
||||
release_id: ${{ steps.create-release.outputs.result }}
|
||||
version: ${{ steps.get-version.outputs.version }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: 'npm'
|
||||
|
||||
- name: get version
|
||||
id: get-version
|
||||
run: |
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Package version: $VERSION"
|
||||
|
||||
- name: get changelog notes for version
|
||||
id: changelog
|
||||
env:
|
||||
VERSION: ${{ steps.get-version.outputs.version }}
|
||||
run: |
|
||||
NOTES="$(awk -v ver="$VERSION" '
|
||||
BEGIN { found=0 }
|
||||
$0 ~ "^## \\[" ver "\\] - " { found=1 }
|
||||
found {
|
||||
if ($0 ~ "^## \\[" && $0 !~ "^## \\[" ver "\\] - " ) exit
|
||||
print
|
||||
}
|
||||
' CHANGELOG.md)"
|
||||
|
||||
if [ -z "$NOTES" ]; then
|
||||
echo "No changelog entry found for version $VERSION" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
{
|
||||
echo "notes<<EOF"
|
||||
echo "$NOTES"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: create draft release
|
||||
id: create-release
|
||||
uses: actions/github-script@v6
|
||||
env:
|
||||
VERSION: ${{ steps.get-version.outputs.version }}
|
||||
NOTES: ${{ steps.changelog.outputs.notes }}
|
||||
with:
|
||||
script: |
|
||||
const { data } = await github.rest.repos.createRelease({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
tag_name: `v${process.env.VERSION}`,
|
||||
name: `v${process.env.VERSION}`,
|
||||
body: process.env.NOTES,
|
||||
draft: true,
|
||||
prerelease: false
|
||||
})
|
||||
return data.id
|
||||
|
||||
build-tauri:
|
||||
needs: create-release
|
||||
permissions:
|
||||
contents: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- platform: 'ubuntu-24.04'
|
||||
args: ''
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Rust cache
|
||||
uses: swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: './src-tauri -> target'
|
||||
|
||||
- name: Install system dependencies (Ubuntu)
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
libwebkit2gtk-4.1-0=2.44.0-2 \
|
||||
libwebkit2gtk-4.1-dev=2.44.0-2 \
|
||||
libjavascriptcoregtk-4.1-0=2.44.0-2 \
|
||||
libjavascriptcoregtk-4.1-dev=2.44.0-2 \
|
||||
gir1.2-javascriptcoregtk-4.1=2.44.0-2 \
|
||||
gir1.2-webkit2-4.1=2.44.0-2 \
|
||||
libappindicator3-dev \
|
||||
librsvg2-dev \
|
||||
patchelf \
|
||||
build-essential \
|
||||
curl \
|
||||
wget \
|
||||
file \
|
||||
libssl-dev \
|
||||
libgtk-3-dev
|
||||
|
||||
- name: Install frontend dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build Tauri app with updater
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||
with:
|
||||
releaseId: ${{ needs.create-release.outputs.release_id }}
|
||||
projectPath: '.'
|
||||
includeDebug: false
|
||||
includeRelease: true
|
||||
includeUpdaterJson: true
|
||||
tauriScript: 'npm run tauri'
|
||||
args: ${{ matrix.args }}
|
||||
15
CHANGELOG.md
@@ -0,0 +1,15 @@
|
||||
## [1.3.0] - 22-12-2025
|
||||
|
||||
### Added
|
||||
- New icons
|
||||
- Unlockers are now cached in `~/.cache/creamlinux/` with automatic version management
|
||||
- Check for new SmokeAPI/CreamLinux versions on every app startup
|
||||
- Each game gets a `creamlinux.json` manifest tracking installed versions
|
||||
- Outdated installations automatically sync with latest cached versions
|
||||
|
||||
### Changed
|
||||
- Polished toast notifications alot
|
||||
- Complete modular rewrite with clear separation of concerns
|
||||
|
||||
### Fixed
|
||||
- Fixed toast message where uninstall actions incorrectly showed success notifications
|
||||
12
README.md
@@ -29,21 +29,21 @@ While the core functionality is working, please be aware that this is an early r
|
||||
|
||||
### AppImage (Recommended)
|
||||
|
||||
1. Download the latest `CreamLinux.AppImage` from the [Releases](https://github.com/Novattz/creamlinux-installer/releases) page
|
||||
1. Download the latest `creamlinux.AppImage` from the [Releases](https://github.com/Novattz/creamlinux-installer/releases) page
|
||||
2. Make it executable:
|
||||
```bash
|
||||
chmod +x CreamLinux.AppImage
|
||||
chmod +x creamlinux.AppImage
|
||||
```
|
||||
3. Run it:
|
||||
|
||||
```bash
|
||||
./CreamLinux.AppImage
|
||||
./creamlinux.AppImage
|
||||
```
|
||||
|
||||
For Nvidia users use this command:
|
||||
|
||||
```
|
||||
WEBKIT_DISABLE_DMABUF_RENDERER=1 ./creamlinux.appimage
|
||||
WEBKIT_DISABLE_DMABUF_RENDERER=1 ./creamlinux.AppImage
|
||||
```
|
||||
|
||||
### Building from Source
|
||||
@@ -52,7 +52,7 @@ While the core functionality is working, please be aware that this is an early r
|
||||
|
||||
- Rust 1.77.2 or later
|
||||
- Node.js 18 or later
|
||||
- webkit2gtk-4.1
|
||||
- webkit2gtk-4.1 (libwebkit2gtk-4.1 for debian)
|
||||
- npm or yarn
|
||||
|
||||
#### Steps
|
||||
@@ -60,7 +60,7 @@ While the core functionality is working, please be aware that this is an early r
|
||||
1. Clone the repository:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/novattz/creamlinux.git
|
||||
git clone https://github.com/Novattz/creamlinux-installer.git
|
||||
cd creamlinux
|
||||
```
|
||||
|
||||
|
||||
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "creamlinux",
|
||||
"version": "1.0.2",
|
||||
"version": "1.3.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "creamlinux",
|
||||
"version": "1.0.2",
|
||||
"version": "1.3.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.5.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "creamlinux",
|
||||
"private": true,
|
||||
"version": "1.0.2",
|
||||
"version": "1.3.0",
|
||||
"type": "module",
|
||||
"author": "Tickbase",
|
||||
"repository": "https://github.com/Novattz/creamlinux-installer",
|
||||
@@ -13,7 +13,7 @@
|
||||
"preview": "vite preview",
|
||||
"tauri": "tauri",
|
||||
"optimize-svg": "node scripts/optimize-svg.js",
|
||||
"prepare-release": "node scripts/prepare-release.js"
|
||||
"set-version": "node scripts/set-version.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.5.0",
|
||||
|
||||
92
scripts/set-version.js
Normal file
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
// Get version from command line argument
|
||||
const newVersion = process.argv[2]
|
||||
|
||||
if (!newVersion) {
|
||||
console.error('Error: No version specified')
|
||||
console.log('Usage: npm run set-version <version>')
|
||||
console.log('Example: npm run set-version 1.2.3')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Validate version format (basic semver check)
|
||||
if (!/^\d+\.\d+\.\d+$/.test(newVersion)) {
|
||||
console.error('Error: Invalid version format. Use semver format: X.Y.Z')
|
||||
console.log('Example: 1.2.3')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log(`Setting version to ${newVersion}...\n`)
|
||||
|
||||
let errors = 0
|
||||
|
||||
// 1. Update package.json
|
||||
try {
|
||||
const packageJsonPath = path.join(process.cwd(), 'package.json')
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
|
||||
packageJson.version = newVersion
|
||||
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n')
|
||||
console.log('Updated package.json')
|
||||
} catch (err) {
|
||||
console.error('Failed to update package.json:', err.message)
|
||||
errors++
|
||||
}
|
||||
|
||||
// 2. Update package-lock.json
|
||||
try {
|
||||
const packageLockPath = path.join(process.cwd(), 'package-lock.json')
|
||||
if (fs.existsSync(packageLockPath)) {
|
||||
const packageLock = JSON.parse(fs.readFileSync(packageLockPath, 'utf8'))
|
||||
packageLock.version = newVersion
|
||||
if (packageLock.packages && packageLock.packages['']) {
|
||||
packageLock.packages[''].version = newVersion
|
||||
}
|
||||
fs.writeFileSync(packageLockPath, JSON.stringify(packageLock, null, 2) + '\n')
|
||||
console.log('Updated package-lock.json')
|
||||
} else {
|
||||
console.log('package-lock.json not found (skipping)')
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to update package-lock.json:', err.message)
|
||||
errors++
|
||||
}
|
||||
|
||||
// 3. Update Cargo.toml
|
||||
try {
|
||||
const cargoTomlPath = path.join(process.cwd(), 'src-tauri', 'Cargo.toml')
|
||||
let cargoToml = fs.readFileSync(cargoTomlPath, 'utf8')
|
||||
|
||||
// Replace version in [package] section
|
||||
cargoToml = cargoToml.replace(/^version\s*=\s*"[^"]*"/m, `version = "${newVersion}"`)
|
||||
|
||||
fs.writeFileSync(cargoTomlPath, cargoToml)
|
||||
console.log('Updated Cargo.toml')
|
||||
} catch (err) {
|
||||
console.error('Failed to update Cargo.toml:', err.message)
|
||||
errors++
|
||||
}
|
||||
|
||||
// 4. Update tauri.conf.json
|
||||
try {
|
||||
const tauriConfPath = path.join(process.cwd(), 'src-tauri', 'tauri.conf.json')
|
||||
const tauriConf = JSON.parse(fs.readFileSync(tauriConfPath, 'utf8'))
|
||||
tauriConf.version = newVersion
|
||||
fs.writeFileSync(tauriConfPath, JSON.stringify(tauriConf, null, 2) + '\n')
|
||||
console.log('Updated tauri.conf.json')
|
||||
} catch (err) {
|
||||
console.error('Failed to update tauri.conf.json:', err.message)
|
||||
errors++
|
||||
}
|
||||
|
||||
// Summary
|
||||
console.log('\n' + '='.repeat(50))
|
||||
if (errors === 0) {
|
||||
console.log(`Successfully set version to ${newVersion} in all files!`)
|
||||
} else {
|
||||
console.log(`Completed with ${errors} error(s)`)
|
||||
process.exit(1)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "app"
|
||||
version = "1.0.1"
|
||||
name = "creamlinux-installer"
|
||||
version = "1.3.0"
|
||||
description = "DLC Manager for Steam games on Linux"
|
||||
authors = ["tickbase"]
|
||||
license = "MIT"
|
||||
@@ -31,6 +31,7 @@ tauri-plugin-dialog = "2.0.0-rc"
|
||||
tauri-plugin-fs = "2.0.0-rc"
|
||||
num_cpus = "1.16.0"
|
||||
tauri-plugin-process = "2"
|
||||
async-trait = "0.1.89"
|
||||
|
||||
[features]
|
||||
custom-protocol = ["tauri/custom-protocol"]
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
"identifier": "default",
|
||||
"description": "enables the default permissions",
|
||||
"windows": ["main"],
|
||||
"permissions": ["core:default"]
|
||||
"permissions": ["core:default", "updater:default", "process:default"]
|
||||
}
|
||||
|
||||
@@ -9,6 +9,5 @@
|
||||
"main"
|
||||
],
|
||||
"permissions": [
|
||||
"updater:default"
|
||||
]
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
// This is a placeholder file - cache functionality has been removed
|
||||
// and now only exists in memory within the App state
|
||||
|
||||
pub fn cache_dlcs(_game_id: &str, _dlcs: &[crate::dlc_manager::DlcInfoWithState]) -> std::io::Result<()> {
|
||||
// This function is kept only for compatibility, but now does nothing
|
||||
// The DLCs are only cached in memory
|
||||
log::info!("Cache functionality has been removed - DLCs are only stored in memory");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_cached_dlcs(_game_id: &str) -> Option<Vec<crate::dlc_manager::DlcInfoWithState>> {
|
||||
// This function is kept only for compatibility, but now always returns None
|
||||
log::info!("Cache functionality has been removed - DLCs are only stored in memory");
|
||||
None
|
||||
}
|
||||
|
||||
pub fn clear_all_caches() -> std::io::Result<()> {
|
||||
// This function is kept only for compatibility, but now does nothing
|
||||
log::info!("Cache functionality has been removed - DLCs are only stored in memory");
|
||||
Ok(())
|
||||
}
|
||||
246
src-tauri/src/cache/mod.rs
vendored
Normal file
@@ -0,0 +1,246 @@
|
||||
mod storage;
|
||||
mod version;
|
||||
|
||||
pub use storage::{
|
||||
get_creamlinux_version_dir, get_smokeapi_version_dir, is_cache_initialized,
|
||||
list_creamlinux_files, list_smokeapi_dlls, read_versions, update_creamlinux_version,
|
||||
update_smokeapi_version,
|
||||
};
|
||||
|
||||
pub use version::{
|
||||
read_manifest, remove_creamlinux_version, remove_smokeapi_version,
|
||||
update_creamlinux_version as update_game_creamlinux_version,
|
||||
update_smokeapi_version as update_game_smokeapi_version,
|
||||
};
|
||||
|
||||
use crate::unlockers::{CreamLinux, SmokeAPI, Unlocker};
|
||||
use log::{error, info, warn};
|
||||
use std::collections::HashMap;
|
||||
|
||||
// Initialize the cache on app startup
|
||||
// Downloads both unlockers if they don't exist
|
||||
pub async fn initialize_cache() -> Result<(), String> {
|
||||
info!("Initializing cache...");
|
||||
|
||||
// Check if cache is already initialized
|
||||
if is_cache_initialized()? {
|
||||
info!("Cache already initialized");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
info!("Cache not initialized, downloading unlockers...");
|
||||
|
||||
// Download SmokeAPI
|
||||
match SmokeAPI::download_to_cache().await {
|
||||
Ok(version) => {
|
||||
info!("Downloaded SmokeAPI version: {}", version);
|
||||
update_smokeapi_version(&version)?;
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to download SmokeAPI: {}", e);
|
||||
return Err(format!("Failed to download SmokeAPI: {}", e));
|
||||
}
|
||||
}
|
||||
|
||||
// Download CreamLinux
|
||||
match CreamLinux::download_to_cache().await {
|
||||
Ok(version) => {
|
||||
info!("Downloaded CreamLinux version: {}", version);
|
||||
update_creamlinux_version(&version)?;
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to download CreamLinux: {}", e);
|
||||
return Err(format!("Failed to download CreamLinux: {}", e));
|
||||
}
|
||||
}
|
||||
|
||||
info!("Cache initialization complete");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Check for updates and download new versions if available
|
||||
pub async fn check_and_update_cache() -> Result<UpdateResult, String> {
|
||||
info!("Checking for unlocker updates...");
|
||||
|
||||
let mut result = UpdateResult::default();
|
||||
|
||||
// Check SmokeAPI
|
||||
let current_smokeapi = read_versions()?.smokeapi.latest;
|
||||
match SmokeAPI::get_latest_version().await {
|
||||
Ok(latest_version) => {
|
||||
if current_smokeapi != latest_version {
|
||||
info!(
|
||||
"SmokeAPI update available: {} -> {}",
|
||||
current_smokeapi, latest_version
|
||||
);
|
||||
|
||||
match SmokeAPI::download_to_cache().await {
|
||||
Ok(version) => {
|
||||
update_smokeapi_version(&version)?;
|
||||
result.smokeapi_updated = true;
|
||||
result.new_smokeapi_version = Some(version);
|
||||
info!("SmokeAPI updated successfully");
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to download SmokeAPI update: {}", e);
|
||||
return Err(format!("Failed to download SmokeAPI update: {}", e));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
info!("SmokeAPI is up to date: {}", current_smokeapi);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Failed to check SmokeAPI version: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Check CreamLinux
|
||||
let current_creamlinux = read_versions()?.creamlinux.latest;
|
||||
match CreamLinux::get_latest_version().await {
|
||||
Ok(latest_version) => {
|
||||
if current_creamlinux != latest_version {
|
||||
info!(
|
||||
"CreamLinux update available: {} -> {}",
|
||||
current_creamlinux, latest_version
|
||||
);
|
||||
|
||||
match CreamLinux::download_to_cache().await {
|
||||
Ok(version) => {
|
||||
update_creamlinux_version(&version)?;
|
||||
result.creamlinux_updated = true;
|
||||
result.new_creamlinux_version = Some(version);
|
||||
info!("CreamLinux updated successfully");
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to download CreamLinux update: {}", e);
|
||||
return Err(format!("Failed to download CreamLinux update: {}", e));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
info!("CreamLinux is up to date: {}", current_creamlinux);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Failed to check CreamLinux version: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
// Update all games that have outdated unlocker versions
|
||||
pub async fn update_outdated_games(
|
||||
games: &HashMap<String, crate::installer::Game>,
|
||||
) -> Result<GameUpdateStats, String> {
|
||||
info!("Checking for outdated game installations...");
|
||||
|
||||
let cached_versions = read_versions()?;
|
||||
let mut stats = GameUpdateStats::default();
|
||||
|
||||
for (game_id, game) in games {
|
||||
// Read the game's manifest
|
||||
let manifest = match read_manifest(&game.path) {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
warn!("Failed to read manifest for {}: {}", game.title, e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
// Check if SmokeAPI needs updating
|
||||
if manifest.has_smokeapi()
|
||||
&& manifest.is_smokeapi_outdated(&cached_versions.smokeapi.latest)
|
||||
{
|
||||
info!(
|
||||
"Game '{}' has outdated SmokeAPI, updating...",
|
||||
game.title
|
||||
);
|
||||
|
||||
// Convert api_files Vec to comma-separated string
|
||||
let api_files_str = game.api_files.join(",");
|
||||
match SmokeAPI::install_to_game(&game.path, &api_files_str).await {
|
||||
Ok(_) => {
|
||||
update_game_smokeapi_version(&game.path, cached_versions.smokeapi.latest.clone())?;
|
||||
stats.smokeapi_updated += 1;
|
||||
info!("Updated SmokeAPI for '{}'", game.title);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to update SmokeAPI for '{}': {}", game.title, e);
|
||||
stats.smokeapi_failed += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if CreamLinux needs updating
|
||||
if manifest.has_creamlinux()
|
||||
&& manifest.is_creamlinux_outdated(&cached_versions.creamlinux.latest)
|
||||
{
|
||||
info!(
|
||||
"Game '{}' has outdated CreamLinux, updating...",
|
||||
game.title
|
||||
);
|
||||
|
||||
// For CreamLinux, we need to preserve the DLC configuration
|
||||
match CreamLinux::install_to_game(&game.path, game_id).await {
|
||||
Ok(_) => {
|
||||
update_game_creamlinux_version(&game.path, cached_versions.creamlinux.latest.clone())?;
|
||||
stats.creamlinux_updated += 1;
|
||||
info!("Updated CreamLinux for '{}'", game.title);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to update CreamLinux for '{}': {}", game.title, e);
|
||||
stats.creamlinux_failed += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info!(
|
||||
"Game update complete - SmokeAPI: {} updated, {} failed | CreamLinux: {} updated, {} failed",
|
||||
stats.smokeapi_updated,
|
||||
stats.smokeapi_failed,
|
||||
stats.creamlinux_updated,
|
||||
stats.creamlinux_failed
|
||||
);
|
||||
|
||||
Ok(stats)
|
||||
}
|
||||
|
||||
// Result of checking for cache updates
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct UpdateResult {
|
||||
pub smokeapi_updated: bool,
|
||||
pub creamlinux_updated: bool,
|
||||
pub new_smokeapi_version: Option<String>,
|
||||
pub new_creamlinux_version: Option<String>,
|
||||
}
|
||||
|
||||
impl UpdateResult {
|
||||
pub fn any_updated(&self) -> bool {
|
||||
self.smokeapi_updated || self.creamlinux_updated
|
||||
}
|
||||
}
|
||||
|
||||
// Statistics about game updates
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct GameUpdateStats {
|
||||
pub smokeapi_updated: u32,
|
||||
pub smokeapi_failed: u32,
|
||||
pub creamlinux_updated: u32,
|
||||
pub creamlinux_failed: u32,
|
||||
}
|
||||
|
||||
impl GameUpdateStats {
|
||||
pub fn total_updated(&self) -> u32 {
|
||||
self.smokeapi_updated + self.creamlinux_updated
|
||||
}
|
||||
|
||||
pub fn total_failed(&self) -> u32 {
|
||||
self.smokeapi_failed + self.creamlinux_failed
|
||||
}
|
||||
|
||||
pub fn has_failures(&self) -> bool {
|
||||
self.total_failed() > 0
|
||||
}
|
||||
}
|
||||
292
src-tauri/src/cache/storage.rs
vendored
Normal file
@@ -0,0 +1,292 @@
|
||||
use log::{info, warn};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
// Represents the versions.json file in the cache root
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct CacheVersions {
|
||||
pub smokeapi: VersionInfo,
|
||||
pub creamlinux: VersionInfo,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct VersionInfo {
|
||||
pub latest: String,
|
||||
}
|
||||
|
||||
impl Default for CacheVersions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
smokeapi: VersionInfo {
|
||||
latest: String::new(),
|
||||
},
|
||||
creamlinux: VersionInfo {
|
||||
latest: String::new(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get the cache directory path (~/.cache/creamlinux)
|
||||
pub fn get_cache_dir() -> Result<PathBuf, String> {
|
||||
let xdg_dirs = xdg::BaseDirectories::with_prefix("creamlinux")
|
||||
.map_err(|e| format!("Failed to get XDG directories: {}", e))?;
|
||||
|
||||
let cache_dir = xdg_dirs
|
||||
.get_cache_home()
|
||||
.parent()
|
||||
.ok_or_else(|| "Failed to get cache parent directory".to_string())?
|
||||
.join("creamlinux");
|
||||
|
||||
// Create the directory if it doesn't exist
|
||||
if !cache_dir.exists() {
|
||||
fs::create_dir_all(&cache_dir)
|
||||
.map_err(|e| format!("Failed to create cache directory: {}", e))?;
|
||||
info!("Created cache directory: {}", cache_dir.display());
|
||||
}
|
||||
|
||||
Ok(cache_dir)
|
||||
}
|
||||
|
||||
// Get the SmokeAPI cache directory path
|
||||
pub fn get_smokeapi_dir() -> Result<PathBuf, String> {
|
||||
let cache_dir = get_cache_dir()?;
|
||||
let smokeapi_dir = cache_dir.join("smokeapi");
|
||||
|
||||
if !smokeapi_dir.exists() {
|
||||
fs::create_dir_all(&smokeapi_dir)
|
||||
.map_err(|e| format!("Failed to create SmokeAPI directory: {}", e))?;
|
||||
info!("Created SmokeAPI directory: {}", smokeapi_dir.display());
|
||||
}
|
||||
|
||||
Ok(smokeapi_dir)
|
||||
}
|
||||
|
||||
// Get the CreamLinux cache directory path
|
||||
pub fn get_creamlinux_dir() -> Result<PathBuf, String> {
|
||||
let cache_dir = get_cache_dir()?;
|
||||
let creamlinux_dir = cache_dir.join("creamlinux");
|
||||
|
||||
if !creamlinux_dir.exists() {
|
||||
fs::create_dir_all(&creamlinux_dir)
|
||||
.map_err(|e| format!("Failed to create CreamLinux directory: {}", e))?;
|
||||
info!("Created CreamLinux directory: {}", creamlinux_dir.display());
|
||||
}
|
||||
|
||||
Ok(creamlinux_dir)
|
||||
}
|
||||
|
||||
// Get the path to a versioned SmokeAPI directory
|
||||
pub fn get_smokeapi_version_dir(version: &str) -> Result<PathBuf, String> {
|
||||
let smokeapi_dir = get_smokeapi_dir()?;
|
||||
let version_dir = smokeapi_dir.join(version);
|
||||
|
||||
if !version_dir.exists() {
|
||||
fs::create_dir_all(&version_dir)
|
||||
.map_err(|e| format!("Failed to create SmokeAPI version directory: {}", e))?;
|
||||
info!(
|
||||
"Created SmokeAPI version directory: {}",
|
||||
version_dir.display()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(version_dir)
|
||||
}
|
||||
|
||||
// Get the path to a versioned CreamLinux directory
|
||||
pub fn get_creamlinux_version_dir(version: &str) -> Result<PathBuf, String> {
|
||||
let creamlinux_dir = get_creamlinux_dir()?;
|
||||
let version_dir = creamlinux_dir.join(version);
|
||||
|
||||
if !version_dir.exists() {
|
||||
fs::create_dir_all(&version_dir)
|
||||
.map_err(|e| format!("Failed to create CreamLinux version directory: {}", e))?;
|
||||
info!(
|
||||
"Created CreamLinux version directory: {}",
|
||||
version_dir.display()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(version_dir)
|
||||
}
|
||||
|
||||
// Read the versions.json file from cache
|
||||
pub fn read_versions() -> Result<CacheVersions, String> {
|
||||
let cache_dir = get_cache_dir()?;
|
||||
let versions_path = cache_dir.join("versions.json");
|
||||
|
||||
if !versions_path.exists() {
|
||||
info!("versions.json doesn't exist, creating default");
|
||||
return Ok(CacheVersions::default());
|
||||
}
|
||||
|
||||
let content = fs::read_to_string(&versions_path)
|
||||
.map_err(|e| format!("Failed to read versions.json: {}", e))?;
|
||||
|
||||
let versions: CacheVersions = serde_json::from_str(&content)
|
||||
.map_err(|e| format!("Failed to parse versions.json: {}", e))?;
|
||||
|
||||
info!(
|
||||
"Read cached versions - SmokeAPI: {}, CreamLinux: {}",
|
||||
versions.smokeapi.latest, versions.creamlinux.latest
|
||||
);
|
||||
|
||||
Ok(versions)
|
||||
}
|
||||
|
||||
// Write the versions.json file to cache
|
||||
pub fn write_versions(versions: &CacheVersions) -> Result<(), String> {
|
||||
let cache_dir = get_cache_dir()?;
|
||||
let versions_path = cache_dir.join("versions.json");
|
||||
|
||||
let content = serde_json::to_string_pretty(versions)
|
||||
.map_err(|e| format!("Failed to serialize versions: {}", e))?;
|
||||
|
||||
fs::write(&versions_path, content)
|
||||
.map_err(|e| format!("Failed to write versions.json: {}", e))?;
|
||||
|
||||
info!(
|
||||
"Wrote versions.json - SmokeAPI: {}, CreamLinux: {}",
|
||||
versions.smokeapi.latest, versions.creamlinux.latest
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Update the SmokeAPI version in versions.json and clean old version directories
|
||||
pub fn update_smokeapi_version(new_version: &str) -> Result<(), String> {
|
||||
let mut versions = read_versions()?;
|
||||
let old_version = versions.smokeapi.latest.clone();
|
||||
|
||||
versions.smokeapi.latest = new_version.to_string();
|
||||
write_versions(&versions)?;
|
||||
|
||||
// Delete old version directory if it exists and is different
|
||||
if !old_version.is_empty() && old_version != new_version {
|
||||
let old_dir = get_smokeapi_dir()?.join(&old_version);
|
||||
if old_dir.exists() {
|
||||
match fs::remove_dir_all(&old_dir) {
|
||||
Ok(_) => info!("Deleted old SmokeAPI version directory: {}", old_version),
|
||||
Err(e) => warn!(
|
||||
"Failed to delete old SmokeAPI version directory: {}",
|
||||
e
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Update the CreamLinux version in versions.json and clean old version directories
|
||||
pub fn update_creamlinux_version(new_version: &str) -> Result<(), String> {
|
||||
let mut versions = read_versions()?;
|
||||
let old_version = versions.creamlinux.latest.clone();
|
||||
|
||||
versions.creamlinux.latest = new_version.to_string();
|
||||
write_versions(&versions)?;
|
||||
|
||||
// Delete old version directory if it exists and is different
|
||||
if !old_version.is_empty() && old_version != new_version {
|
||||
let old_dir = get_creamlinux_dir()?.join(&old_version);
|
||||
if old_dir.exists() {
|
||||
match fs::remove_dir_all(&old_dir) {
|
||||
Ok(_) => info!("Deleted old CreamLinux version directory: {}", old_version),
|
||||
Err(e) => warn!(
|
||||
"Failed to delete old CreamLinux version directory: {}",
|
||||
e
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Check if the cache is initialized (has both unlockers cached)
|
||||
pub fn is_cache_initialized() -> Result<bool, String> {
|
||||
let versions = read_versions()?;
|
||||
Ok(!versions.smokeapi.latest.is_empty() && !versions.creamlinux.latest.is_empty())
|
||||
}
|
||||
|
||||
// Get the SmokeAPI DLL path for the latest cached version
|
||||
#[allow(dead_code)]
|
||||
pub fn get_smokeapi_dll_path() -> Result<PathBuf, String> {
|
||||
let versions = read_versions()?;
|
||||
if versions.smokeapi.latest.is_empty() {
|
||||
return Err("SmokeAPI is not cached".to_string());
|
||||
}
|
||||
|
||||
let version_dir = get_smokeapi_version_dir(&versions.smokeapi.latest)?;
|
||||
Ok(version_dir.join("SmokeAPI.dll"))
|
||||
}
|
||||
|
||||
// Get the CreamLinux files directory path for the latest cached version
|
||||
#[allow(dead_code)]
|
||||
pub fn get_creamlinux_files_dir() -> Result<PathBuf, String> {
|
||||
let versions = read_versions()?;
|
||||
if versions.creamlinux.latest.is_empty() {
|
||||
return Err("CreamLinux is not cached".to_string());
|
||||
}
|
||||
|
||||
get_creamlinux_version_dir(&versions.creamlinux.latest)
|
||||
}
|
||||
|
||||
// List all SmokeAPI DLL files in the cached version directory
|
||||
pub fn list_smokeapi_dlls() -> Result<Vec<PathBuf>, String> {
|
||||
let versions = read_versions()?;
|
||||
if versions.smokeapi.latest.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let version_dir = get_smokeapi_version_dir(&versions.smokeapi.latest)?;
|
||||
|
||||
if !version_dir.exists() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let entries = fs::read_dir(&version_dir)
|
||||
.map_err(|e| format!("Failed to read SmokeAPI directory: {}", e))?;
|
||||
|
||||
let mut dlls = Vec::new();
|
||||
for entry in entries {
|
||||
if let Ok(entry) = entry {
|
||||
let path = entry.path();
|
||||
if path.extension().and_then(|s| s.to_str()) == Some("dll") {
|
||||
dlls.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(dlls)
|
||||
}
|
||||
|
||||
// List all CreamLinux files in the cached version directory
|
||||
pub fn list_creamlinux_files() -> Result<Vec<PathBuf>, String> {
|
||||
let versions = read_versions()?;
|
||||
if versions.creamlinux.latest.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let version_dir = get_creamlinux_version_dir(&versions.creamlinux.latest)?;
|
||||
|
||||
if !version_dir.exists() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let entries = fs::read_dir(&version_dir)
|
||||
.map_err(|e| format!("Failed to read CreamLinux directory: {}", e))?;
|
||||
|
||||
let mut files = Vec::new();
|
||||
for entry in entries {
|
||||
if let Ok(entry) = entry {
|
||||
let path = entry.path();
|
||||
if path.is_file() {
|
||||
files.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(files)
|
||||
}
|
||||
177
src-tauri/src/cache/version.rs
vendored
Normal file
@@ -0,0 +1,177 @@
|
||||
use log::{info};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
// Represents the version manifest stored in each game directory
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
pub struct GameManifest {
|
||||
pub smokeapi_version: Option<String>,
|
||||
pub creamlinux_version: Option<String>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl GameManifest {
|
||||
// Create a new manifest with SmokeAPI version
|
||||
pub fn with_smokeapi(version: String) -> Self {
|
||||
Self {
|
||||
smokeapi_version: Some(version),
|
||||
creamlinux_version: None,
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new manifest with CreamLinux version
|
||||
pub fn with_creamlinux(version: String) -> Self {
|
||||
Self {
|
||||
smokeapi_version: None,
|
||||
creamlinux_version: Some(version),
|
||||
}
|
||||
}
|
||||
|
||||
// Check if SmokeAPI is installed
|
||||
pub fn has_smokeapi(&self) -> bool {
|
||||
self.smokeapi_version.is_some()
|
||||
}
|
||||
|
||||
// Check if CreamLinux is installed
|
||||
pub fn has_creamlinux(&self) -> bool {
|
||||
self.creamlinux_version.is_some()
|
||||
}
|
||||
|
||||
// Check if SmokeAPI version is outdated
|
||||
pub fn is_smokeapi_outdated(&self, latest_version: &str) -> bool {
|
||||
match &self.smokeapi_version {
|
||||
Some(version) => version != latest_version,
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
// Check if CreamLinux version is outdated
|
||||
pub fn is_creamlinux_outdated(&self, latest_version: &str) -> bool {
|
||||
match &self.creamlinux_version {
|
||||
Some(version) => version != latest_version,
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Read the creamlinux.json manifest from a game directory
|
||||
pub fn read_manifest(game_path: &str) -> Result<GameManifest, String> {
|
||||
let manifest_path = Path::new(game_path).join("creamlinux.json");
|
||||
|
||||
if !manifest_path.exists() {
|
||||
return Ok(GameManifest::default());
|
||||
}
|
||||
|
||||
let content = fs::read_to_string(&manifest_path)
|
||||
.map_err(|e| format!("Failed to read manifest: {}", e))?;
|
||||
|
||||
let manifest: GameManifest = serde_json::from_str(&content)
|
||||
.map_err(|e| format!("Failed to parse manifest: {}", e))?;
|
||||
|
||||
info!(
|
||||
"Read manifest from {}: SmokeAPI: {:?}, CreamLinux: {:?}",
|
||||
game_path, manifest.smokeapi_version, manifest.creamlinux_version
|
||||
);
|
||||
|
||||
Ok(manifest)
|
||||
}
|
||||
|
||||
// Write the creamlinux.json manifest to a game directory
|
||||
pub fn write_manifest(game_path: &str, manifest: &GameManifest) -> Result<(), String> {
|
||||
let manifest_path = Path::new(game_path).join("creamlinux.json");
|
||||
|
||||
let content = serde_json::to_string_pretty(manifest)
|
||||
.map_err(|e| format!("Failed to serialize manifest: {}", e))?;
|
||||
|
||||
fs::write(&manifest_path, content)
|
||||
.map_err(|e| format!("Failed to write manifest: {}", e))?;
|
||||
|
||||
info!(
|
||||
"Wrote manifest to {}: SmokeAPI: {:?}, CreamLinux: {:?}",
|
||||
game_path, manifest.smokeapi_version, manifest.creamlinux_version
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Update the SmokeAPI version in the manifest
|
||||
pub fn update_smokeapi_version(game_path: &str, version: String) -> Result<(), String> {
|
||||
let mut manifest = read_manifest(game_path)?;
|
||||
manifest.smokeapi_version = Some(version);
|
||||
write_manifest(game_path, &manifest)
|
||||
}
|
||||
|
||||
// Update the CreamLinux version in the manifest
|
||||
pub fn update_creamlinux_version(game_path: &str, version: String) -> Result<(), String> {
|
||||
let mut manifest = read_manifest(game_path)?;
|
||||
manifest.creamlinux_version = Some(version);
|
||||
write_manifest(game_path, &manifest)
|
||||
}
|
||||
|
||||
// Remove SmokeAPI version from the manifest
|
||||
pub fn remove_smokeapi_version(game_path: &str) -> Result<(), String> {
|
||||
let mut manifest = read_manifest(game_path)?;
|
||||
manifest.smokeapi_version = None;
|
||||
|
||||
// If both versions are None, delete the manifest file
|
||||
if manifest.smokeapi_version.is_none() && manifest.creamlinux_version.is_none() {
|
||||
let manifest_path = Path::new(game_path).join("creamlinux.json");
|
||||
if manifest_path.exists() {
|
||||
fs::remove_file(&manifest_path)
|
||||
.map_err(|e| format!("Failed to delete manifest: {}", e))?;
|
||||
info!("Deleted empty manifest from {}", game_path);
|
||||
}
|
||||
} else {
|
||||
write_manifest(game_path, &manifest)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Remove CreamLinux version from the manifest
|
||||
pub fn remove_creamlinux_version(game_path: &str) -> Result<(), String> {
|
||||
let mut manifest = read_manifest(game_path)?;
|
||||
manifest.creamlinux_version = None;
|
||||
|
||||
// If both versions are None, delete the manifest file
|
||||
if manifest.smokeapi_version.is_none() && manifest.creamlinux_version.is_none() {
|
||||
let manifest_path = Path::new(game_path).join("creamlinux.json");
|
||||
if manifest_path.exists() {
|
||||
fs::remove_file(&manifest_path)
|
||||
.map_err(|e| format!("Failed to delete manifest: {}", e))?;
|
||||
info!("Deleted empty manifest from {}", game_path);
|
||||
}
|
||||
} else {
|
||||
write_manifest(game_path, &manifest)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_manifest_creation() {
|
||||
let manifest = GameManifest::with_smokeapi("v1.0.0".to_string());
|
||||
assert_eq!(manifest.smokeapi_version, Some("v1.0.0".to_string()));
|
||||
assert_eq!(manifest.creamlinux_version, None);
|
||||
|
||||
let manifest = GameManifest::with_creamlinux("v2.0.0".to_string());
|
||||
assert_eq!(manifest.smokeapi_version, None);
|
||||
assert_eq!(manifest.creamlinux_version, Some("v2.0.0".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_outdated_check() {
|
||||
let mut manifest = GameManifest::with_smokeapi("v1.0.0".to_string());
|
||||
assert!(manifest.is_smokeapi_outdated("v2.0.0"));
|
||||
assert!(!manifest.is_smokeapi_outdated("v1.0.0"));
|
||||
|
||||
manifest.creamlinux_version = Some("v1.5.0".to_string());
|
||||
assert!(manifest.is_creamlinux_outdated("v2.0.0"));
|
||||
assert!(!manifest.is_creamlinux_outdated("v1.5.0"));
|
||||
}
|
||||
}
|
||||
@@ -232,15 +232,15 @@ pub fn update_dlc_configuration(
|
||||
}
|
||||
processed_dlcs.insert(appid.to_string());
|
||||
} else {
|
||||
// Not in our list keep the original line
|
||||
// Not in our list, keep the original line
|
||||
new_contents.push(line.to_string());
|
||||
}
|
||||
} else {
|
||||
// Invalid format or not a DLC line keep as is
|
||||
// Invalid format or not a DLC line, keep as is
|
||||
new_contents.push(line.to_string());
|
||||
}
|
||||
} else if !in_dlc_section || trimmed.is_empty() {
|
||||
// Not a DLC line or empty line keep as is
|
||||
// Not a DLC line or empty line, keep as is
|
||||
new_contents.push(line.to_string());
|
||||
}
|
||||
}
|
||||
@@ -274,18 +274,6 @@ pub fn update_dlc_configuration(
|
||||
}
|
||||
}
|
||||
|
||||
// Get app ID from game path by reading cream_api.ini
|
||||
#[allow(dead_code)]
|
||||
fn extract_app_id_from_config(game_path: &str) -> Option<String> {
|
||||
if let Ok(contents) = fs::read_to_string(Path::new(game_path).join("cream_api.ini")) {
|
||||
let re = regex::Regex::new(r"APPID\s*=\s*(\d+)").unwrap();
|
||||
if let Some(cap) = re.captures(&contents) {
|
||||
return Some(cap[1].to_string());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
// Create a custom installation with selected DLCs
|
||||
pub async fn install_cream_with_dlcs(
|
||||
game_id: String,
|
||||
@@ -316,9 +304,6 @@ pub async fn install_cream_with_dlcs(
|
||||
game.title, game_id
|
||||
);
|
||||
|
||||
// Install CreamLinux first - but provide the DLCs directly instead of fetching them again
|
||||
use crate::installer::install_creamlinux_with_dlcs;
|
||||
|
||||
// Convert DlcInfoWithState to installer::DlcInfo for those that are enabled
|
||||
let enabled_dlcs = selected_dlcs
|
||||
.iter()
|
||||
@@ -329,40 +314,40 @@ pub async fn install_cream_with_dlcs(
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let app_handle_clone = app_handle.clone();
|
||||
let game_title = game.title.clone();
|
||||
// Install CreamLinux binaries from cache
|
||||
use crate::unlockers::{CreamLinux, Unlocker};
|
||||
|
||||
// Use direct installation with provided DLCs instead of re-fetching
|
||||
match install_creamlinux_with_dlcs(
|
||||
&game.path,
|
||||
&game_id,
|
||||
enabled_dlcs,
|
||||
move |progress, message| {
|
||||
// Emit progress updates during installation
|
||||
use crate::installer::emit_progress;
|
||||
emit_progress(
|
||||
&app_handle_clone,
|
||||
&format!("Installing CreamLinux for {}", game_title),
|
||||
message,
|
||||
progress * 100.0, // Scale progress from 0 to 100%
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
);
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
info!(
|
||||
"CreamLinux installation completed successfully for game: {}",
|
||||
game.title
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to install CreamLinux: {}", e);
|
||||
Err(format!("Failed to install CreamLinux: {}", e))
|
||||
}
|
||||
let game_path = game.path.clone();
|
||||
|
||||
// Install binaries
|
||||
CreamLinux::install_to_game(&game.path, &game_id)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to install CreamLinux binaries: {}", e))?;
|
||||
|
||||
// Write cream_api.ini with DLCs
|
||||
let cream_api_path = Path::new(&game_path).join("cream_api.ini");
|
||||
let mut config = String::new();
|
||||
|
||||
config.push_str(&format!("APPID = {}\n[config]\n", game_id));
|
||||
config.push_str("issubscribedapp_on_false_use_real = true\n");
|
||||
config.push_str("[methods]\n");
|
||||
config.push_str("disable_steamapps_issubscribedapp = false\n");
|
||||
config.push_str("[dlc]\n");
|
||||
|
||||
for dlc in &enabled_dlcs {
|
||||
config.push_str(&format!("{} = {}\n", dlc.appid, dlc.name));
|
||||
}
|
||||
}
|
||||
|
||||
fs::write(&cream_api_path, config)
|
||||
.map_err(|e| format!("Failed to write cream_api.ini: {}", e))?;
|
||||
|
||||
// Update version manifest
|
||||
let cached_versions = crate::cache::read_versions()?;
|
||||
crate::cache::update_game_creamlinux_version(&game_path, cached_versions.creamlinux.latest)?;
|
||||
|
||||
info!(
|
||||
"CreamLinux installation completed successfully for game: {}",
|
||||
game.title
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
44
src-tauri/src/installer/file_ops.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
// This module contains helper functions for file operations during installation
|
||||
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
// Copy a file with backup
|
||||
#[allow(dead_code)]
|
||||
pub fn copy_with_backup(src: &Path, dest: &Path) -> io::Result<()> {
|
||||
// If destination exists, create a backup
|
||||
if dest.exists() {
|
||||
let backup = dest.with_extension("bak");
|
||||
fs::copy(dest, &backup)?;
|
||||
}
|
||||
|
||||
fs::copy(src, dest)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Safely remove a file (doesn't error if it doesn't exist)
|
||||
#[allow(dead_code)]
|
||||
pub fn safe_remove(path: &Path) -> io::Result<()> {
|
||||
if path.exists() {
|
||||
fs::remove_file(path)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Make a file executable (Unix only)
|
||||
#[cfg(unix)]
|
||||
#[allow(dead_code)]
|
||||
pub fn make_executable(path: &Path) -> io::Result<()> {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
let mut perms = fs::metadata(path)?.permissions();
|
||||
perms.set_mode(0o755);
|
||||
fs::set_permissions(path, perms)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
pub fn make_executable(_path: &Path) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
655
src-tauri/src/installer/mod.rs
Normal file
@@ -0,0 +1,655 @@
|
||||
mod file_ops;
|
||||
|
||||
use crate::cache::{
|
||||
remove_creamlinux_version, remove_smokeapi_version,
|
||||
update_game_creamlinux_version, update_game_smokeapi_version,
|
||||
};
|
||||
use crate::unlockers::{CreamLinux, SmokeAPI, Unlocker};
|
||||
use crate::AppState;
|
||||
use log::{error, info, warn};
|
||||
use reqwest;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::time::Duration;
|
||||
use tauri::Manager;
|
||||
use tauri::{AppHandle, Emitter};
|
||||
|
||||
// Type of installer
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum InstallerType {
|
||||
Cream,
|
||||
Smoke,
|
||||
}
|
||||
|
||||
// Action to perform
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum InstallerAction {
|
||||
Install,
|
||||
Uninstall,
|
||||
}
|
||||
|
||||
// DLC Information structure
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct DlcInfo {
|
||||
pub appid: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
// Struct to hold installation instructions for the frontend
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct InstallationInstructions {
|
||||
#[serde(rename = "type")]
|
||||
pub type_: String,
|
||||
pub command: String,
|
||||
pub game_title: String,
|
||||
pub dlc_count: Option<usize>,
|
||||
}
|
||||
|
||||
// Game information structure
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct Game {
|
||||
pub id: String,
|
||||
pub title: String,
|
||||
pub path: String,
|
||||
pub native: bool,
|
||||
pub api_files: Vec<String>,
|
||||
pub cream_installed: bool,
|
||||
pub smoke_installed: bool,
|
||||
pub installing: bool,
|
||||
}
|
||||
|
||||
// Emit a progress update to the frontend
|
||||
pub fn emit_progress(
|
||||
app_handle: &AppHandle,
|
||||
title: &str,
|
||||
message: &str,
|
||||
progress: f32,
|
||||
complete: bool,
|
||||
show_instructions: bool,
|
||||
instructions: Option<InstallationInstructions>,
|
||||
) {
|
||||
let mut payload = json!({
|
||||
"title": title,
|
||||
"message": message,
|
||||
"progress": progress,
|
||||
"complete": complete,
|
||||
"show_instructions": show_instructions
|
||||
});
|
||||
|
||||
if let Some(inst) = instructions {
|
||||
payload["instructions"] = serde_json::to_value(inst).unwrap_or_default();
|
||||
}
|
||||
|
||||
if let Err(e) = app_handle.emit("installation-progress", payload) {
|
||||
warn!("Failed to emit progress event: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Process a single game action (install/uninstall Cream/Smoke)
|
||||
pub async fn process_action(
|
||||
game_id: String,
|
||||
installer_type: InstallerType,
|
||||
action: InstallerAction,
|
||||
game: Game,
|
||||
app_handle: AppHandle,
|
||||
) -> Result<(), String> {
|
||||
match (installer_type, action) {
|
||||
(InstallerType::Cream, InstallerAction::Install) => {
|
||||
install_creamlinux(game_id, game, app_handle).await
|
||||
}
|
||||
(InstallerType::Cream, InstallerAction::Uninstall) => {
|
||||
uninstall_creamlinux(game, app_handle).await
|
||||
}
|
||||
(InstallerType::Smoke, InstallerAction::Install) => {
|
||||
install_smokeapi(game, app_handle).await
|
||||
}
|
||||
(InstallerType::Smoke, InstallerAction::Uninstall) => {
|
||||
uninstall_smokeapi(game, app_handle).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Install CreamLinux to a game
|
||||
async fn install_creamlinux(
|
||||
game_id: String,
|
||||
game: Game,
|
||||
app_handle: AppHandle,
|
||||
) -> Result<(), String> {
|
||||
if !game.native {
|
||||
return Err("CreamLinux can only be installed on native Linux games".to_string());
|
||||
}
|
||||
|
||||
info!("Installing CreamLinux for game: {}", game.title);
|
||||
let game_title = game.title.clone();
|
||||
|
||||
emit_progress(
|
||||
&app_handle,
|
||||
&format!("Installing CreamLinux for {}", game_title),
|
||||
"Fetching DLC list...",
|
||||
10.0,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
);
|
||||
|
||||
// Fetch DLC list
|
||||
let dlcs = match fetch_dlc_details(&game_id).await {
|
||||
Ok(dlcs) => dlcs,
|
||||
Err(e) => {
|
||||
error!("Failed to fetch DLC details: {}", e);
|
||||
return Err(format!("Failed to fetch DLC details: {}", e));
|
||||
}
|
||||
};
|
||||
|
||||
let dlc_count = dlcs.len();
|
||||
info!("Found {} DLCs for {}", dlc_count, game_title);
|
||||
|
||||
emit_progress(
|
||||
&app_handle,
|
||||
&format!("Installing CreamLinux for {}", game_title),
|
||||
"Installing from cache...",
|
||||
50.0,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
);
|
||||
|
||||
// Install CreamLinux binaries from cache
|
||||
CreamLinux::install_to_game(&game.path, &game_id)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to install CreamLinux: {}", e))?;
|
||||
|
||||
emit_progress(
|
||||
&app_handle,
|
||||
&format!("Installing CreamLinux for {}", game_title),
|
||||
"Writing DLC configuration...",
|
||||
80.0,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
);
|
||||
|
||||
// Write cream_api.ini with DLCs
|
||||
write_cream_api_ini(&game.path, &game_id, &dlcs)?;
|
||||
|
||||
// Update version manifest
|
||||
let cached_versions = crate::cache::read_versions()?;
|
||||
update_game_creamlinux_version(&game.path, cached_versions.creamlinux.latest)?;
|
||||
|
||||
// Emit completion with instructions
|
||||
let instructions = InstallationInstructions {
|
||||
type_: "cream_install".to_string(),
|
||||
command: "sh ./cream.sh %command%".to_string(),
|
||||
game_title: game_title.clone(),
|
||||
dlc_count: Some(dlc_count),
|
||||
};
|
||||
|
||||
emit_progress(
|
||||
&app_handle,
|
||||
&format!("Installation Completed: {}", game_title),
|
||||
"CreamLinux has been installed successfully!",
|
||||
100.0,
|
||||
true,
|
||||
true,
|
||||
Some(instructions),
|
||||
);
|
||||
|
||||
info!("CreamLinux installation completed for: {}", game_title);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Uninstall CreamLinux from a game
|
||||
async fn uninstall_creamlinux(game: Game, app_handle: AppHandle) -> Result<(), String> {
|
||||
if !game.native {
|
||||
return Err("CreamLinux can only be uninstalled from native Linux games".to_string());
|
||||
}
|
||||
|
||||
let game_title = game.title.clone();
|
||||
info!("Uninstalling CreamLinux from game: {}", game_title);
|
||||
|
||||
emit_progress(
|
||||
&app_handle,
|
||||
&format!("Uninstalling CreamLinux from {}", game_title),
|
||||
"Removing CreamLinux files...",
|
||||
50.0,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
);
|
||||
|
||||
CreamLinux::uninstall_from_game(&game.path, &game.id)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to uninstall CreamLinux: {}", e))?;
|
||||
|
||||
// Remove version from manifest
|
||||
remove_creamlinux_version(&game.path)?;
|
||||
|
||||
emit_progress(
|
||||
&app_handle,
|
||||
&format!("Uninstallation Completed: {}", game_title),
|
||||
"CreamLinux has been removed successfully!",
|
||||
100.0,
|
||||
true,
|
||||
false,
|
||||
None,
|
||||
);
|
||||
|
||||
info!("CreamLinux uninstallation completed for: {}", game_title);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Install SmokeAPI to a game
|
||||
async fn install_smokeapi(game: Game, app_handle: AppHandle) -> Result<(), String> {
|
||||
if game.native {
|
||||
return Err("SmokeAPI can only be installed on Proton/Windows games".to_string());
|
||||
}
|
||||
|
||||
info!("Installing SmokeAPI for game: {}", game.title);
|
||||
let game_title = game.title.clone();
|
||||
|
||||
emit_progress(
|
||||
&app_handle,
|
||||
&format!("Installing SmokeAPI for {}", game_title),
|
||||
"Installing from cache...",
|
||||
50.0,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
);
|
||||
|
||||
// Join api_files into a comma-separated string for the context
|
||||
let api_files_str = game.api_files.join(",");
|
||||
|
||||
// Install SmokeAPI from cache
|
||||
SmokeAPI::install_to_game(&game.path, &api_files_str)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to install SmokeAPI: {}", e))?;
|
||||
|
||||
// Update version manifest
|
||||
let cached_versions = crate::cache::read_versions()?;
|
||||
update_game_smokeapi_version(&game.path, cached_versions.smokeapi.latest)?;
|
||||
|
||||
emit_progress(
|
||||
&app_handle,
|
||||
&format!("Installation Completed: {}", game_title),
|
||||
"SmokeAPI has been installed successfully!",
|
||||
100.0,
|
||||
true,
|
||||
false,
|
||||
None,
|
||||
);
|
||||
|
||||
info!("SmokeAPI installation completed for: {}", game_title);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Uninstall SmokeAPI from a game
|
||||
async fn uninstall_smokeapi(game: Game, app_handle: AppHandle) -> Result<(), String> {
|
||||
if game.native {
|
||||
return Err("SmokeAPI can only be uninstalled from Proton/Windows games".to_string());
|
||||
}
|
||||
|
||||
let game_title = game.title.clone();
|
||||
info!("Uninstalling SmokeAPI from game: {}", game_title);
|
||||
|
||||
emit_progress(
|
||||
&app_handle,
|
||||
&format!("Uninstalling SmokeAPI from {}", game_title),
|
||||
"Removing SmokeAPI files...",
|
||||
50.0,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
);
|
||||
|
||||
// Join api_files into a comma-separated string for the context
|
||||
let api_files_str = game.api_files.join(",");
|
||||
|
||||
SmokeAPI::uninstall_from_game(&game.path, &api_files_str)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to uninstall SmokeAPI: {}", e))?;
|
||||
|
||||
// Remove version from manifest
|
||||
remove_smokeapi_version(&game.path)?;
|
||||
|
||||
emit_progress(
|
||||
&app_handle,
|
||||
&format!("Uninstallation Completed: {}", game_title),
|
||||
"SmokeAPI has been removed successfully!",
|
||||
100.0,
|
||||
true,
|
||||
false,
|
||||
None,
|
||||
);
|
||||
|
||||
info!("SmokeAPI uninstallation completed for: {}", game_title);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Fetch DLC details from Steam API (simple version without progress)
|
||||
pub async fn fetch_dlc_details(app_id: &str) -> Result<Vec<DlcInfo>, String> {
|
||||
let client = reqwest::Client::new();
|
||||
let base_url = format!(
|
||||
"https://store.steampowered.com/api/appdetails?appids={}",
|
||||
app_id
|
||||
);
|
||||
|
||||
let response = client
|
||||
.get(&base_url)
|
||||
.timeout(Duration::from_secs(10))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to fetch game details: {}", e))?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
return Err(format!(
|
||||
"Failed to fetch game details: HTTP {}",
|
||||
response.status()
|
||||
));
|
||||
}
|
||||
|
||||
let data: serde_json::Value = response
|
||||
.json()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to parse response: {}", e))?;
|
||||
|
||||
let dlc_ids = match data
|
||||
.get(app_id)
|
||||
.and_then(|app| app.get("data"))
|
||||
.and_then(|data| data.get("dlc"))
|
||||
{
|
||||
Some(dlc_array) => match dlc_array.as_array() {
|
||||
Some(array) => array
|
||||
.iter()
|
||||
.filter_map(|id| id.as_u64().map(|n| n.to_string()))
|
||||
.collect::<Vec<String>>(),
|
||||
_ => Vec::new(),
|
||||
},
|
||||
_ => Vec::new(),
|
||||
};
|
||||
|
||||
info!("Found {} DLCs for game ID {}", dlc_ids.len(), app_id);
|
||||
|
||||
let mut dlc_details = Vec::new();
|
||||
|
||||
for dlc_id in dlc_ids {
|
||||
let dlc_url = format!(
|
||||
"https://store.steampowered.com/api/appdetails?appids={}",
|
||||
dlc_id
|
||||
);
|
||||
|
||||
// Add a small delay to avoid rate limiting
|
||||
tokio::time::sleep(Duration::from_millis(300)).await;
|
||||
|
||||
let dlc_response = client
|
||||
.get(&dlc_url)
|
||||
.timeout(Duration::from_secs(10))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to fetch DLC details: {}", e))?;
|
||||
|
||||
if dlc_response.status().is_success() {
|
||||
let dlc_data: serde_json::Value = dlc_response
|
||||
.json()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to parse DLC response: {}", e))?;
|
||||
|
||||
let dlc_name = match dlc_data
|
||||
.get(&dlc_id)
|
||||
.and_then(|app| app.get("data"))
|
||||
.and_then(|data| data.get("name"))
|
||||
{
|
||||
Some(name) => match name.as_str() {
|
||||
Some(s) => s.to_string(),
|
||||
_ => "Unknown DLC".to_string(),
|
||||
},
|
||||
_ => "Unknown DLC".to_string(),
|
||||
};
|
||||
|
||||
info!("Found DLC: {} ({})", dlc_name, dlc_id);
|
||||
dlc_details.push(DlcInfo {
|
||||
appid: dlc_id,
|
||||
name: dlc_name,
|
||||
});
|
||||
} else if dlc_response.status() == reqwest::StatusCode::TOO_MANY_REQUESTS {
|
||||
// If rate limited, wait longer
|
||||
error!("Rate limited by Steam API, waiting 10 seconds");
|
||||
tokio::time::sleep(Duration::from_secs(10)).await;
|
||||
}
|
||||
}
|
||||
|
||||
info!(
|
||||
"Successfully retrieved details for {} DLCs",
|
||||
dlc_details.len()
|
||||
);
|
||||
Ok(dlc_details)
|
||||
}
|
||||
|
||||
// Fetch DLC details from Steam API with progress updates
|
||||
pub async fn fetch_dlc_details_with_progress(
|
||||
app_id: &str,
|
||||
app_handle: &tauri::AppHandle,
|
||||
) -> Result<Vec<DlcInfo>, String> {
|
||||
info!(
|
||||
"Starting DLC details fetch with progress for game ID: {}",
|
||||
app_id
|
||||
);
|
||||
|
||||
// Get a reference to a cancellation flag from app state
|
||||
let state = app_handle.state::<AppState>();
|
||||
let should_cancel = state.fetch_cancellation.clone();
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let base_url = format!(
|
||||
"https://store.steampowered.com/api/appdetails?appids={}",
|
||||
app_id
|
||||
);
|
||||
|
||||
// Emit initial progress
|
||||
emit_dlc_progress(app_handle, "Looking up game details...", 5, None);
|
||||
info!("Emitted initial DLC progress: 5%");
|
||||
|
||||
let response = client
|
||||
.get(&base_url)
|
||||
.timeout(Duration::from_secs(10))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to fetch game details: {}", e))?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
let error_msg = format!("Failed to fetch game details: HTTP {}", response.status());
|
||||
error!("{}", error_msg);
|
||||
return Err(error_msg);
|
||||
}
|
||||
|
||||
let data: serde_json::Value = response
|
||||
.json()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to parse response: {}", e))?;
|
||||
|
||||
let dlc_ids = match data
|
||||
.get(app_id)
|
||||
.and_then(|app| app.get("data"))
|
||||
.and_then(|data| data.get("dlc"))
|
||||
{
|
||||
Some(dlc_array) => match dlc_array.as_array() {
|
||||
Some(array) => array
|
||||
.iter()
|
||||
.filter_map(|id| id.as_u64().map(|n| n.to_string()))
|
||||
.collect::<Vec<String>>(),
|
||||
_ => Vec::new(),
|
||||
},
|
||||
_ => Vec::new(),
|
||||
};
|
||||
|
||||
info!("Found {} DLCs for game ID {}", dlc_ids.len(), app_id);
|
||||
emit_dlc_progress(
|
||||
app_handle,
|
||||
&format!("Found {} DLCs. Fetching details...", dlc_ids.len()),
|
||||
10,
|
||||
None,
|
||||
);
|
||||
info!("Emitted DLC progress: 10%, found {} DLCs", dlc_ids.len());
|
||||
|
||||
let mut dlc_details = Vec::new();
|
||||
let total_dlcs = dlc_ids.len();
|
||||
|
||||
for (index, dlc_id) in dlc_ids.iter().enumerate() {
|
||||
// Check if cancellation was requested
|
||||
if should_cancel.load(Ordering::SeqCst) {
|
||||
info!("DLC fetch cancelled for game {}", app_id);
|
||||
return Err("Operation cancelled by user".to_string());
|
||||
}
|
||||
|
||||
let progress_percent = 10.0 + (index as f32 / total_dlcs as f32) * 90.0;
|
||||
let progress_rounded = progress_percent as u32;
|
||||
let remaining_dlcs = total_dlcs - index;
|
||||
|
||||
// Estimate time remaining (rough calculation - 300ms per DLC)
|
||||
let est_time_left = if remaining_dlcs > 0 {
|
||||
let seconds = (remaining_dlcs as f32 * 0.3).ceil() as u32;
|
||||
if seconds < 60 {
|
||||
format!("~{} seconds", seconds)
|
||||
} else {
|
||||
format!("~{} minute(s)", (seconds as f32 / 60.0).ceil() as u32)
|
||||
}
|
||||
} else {
|
||||
"almost done".to_string()
|
||||
};
|
||||
|
||||
info!(
|
||||
"Processing DLC {}/{} - Progress: {}%",
|
||||
index + 1,
|
||||
total_dlcs,
|
||||
progress_rounded
|
||||
);
|
||||
emit_dlc_progress(
|
||||
app_handle,
|
||||
&format!("Processing DLC {}/{}", index + 1, total_dlcs),
|
||||
progress_rounded,
|
||||
Some(&est_time_left),
|
||||
);
|
||||
|
||||
let dlc_url = format!(
|
||||
"https://store.steampowered.com/api/appdetails?appids={}",
|
||||
dlc_id
|
||||
);
|
||||
|
||||
// Add a small delay to avoid rate limiting
|
||||
tokio::time::sleep(Duration::from_millis(300)).await;
|
||||
|
||||
let dlc_response = client
|
||||
.get(&dlc_url)
|
||||
.timeout(Duration::from_secs(10))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to fetch DLC details: {}", e))?;
|
||||
|
||||
if dlc_response.status().is_success() {
|
||||
let dlc_data: serde_json::Value = dlc_response
|
||||
.json()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to parse DLC response: {}", e))?;
|
||||
|
||||
let dlc_name = match dlc_data
|
||||
.get(&dlc_id)
|
||||
.and_then(|app| app.get("data"))
|
||||
.and_then(|data| data.get("name"))
|
||||
{
|
||||
Some(name) => match name.as_str() {
|
||||
Some(s) => s.to_string(),
|
||||
_ => "Unknown DLC".to_string(),
|
||||
},
|
||||
_ => "Unknown DLC".to_string(),
|
||||
};
|
||||
|
||||
info!("Found DLC: {} ({})", dlc_name, dlc_id);
|
||||
let dlc_info = DlcInfo {
|
||||
appid: dlc_id.clone(),
|
||||
name: dlc_name,
|
||||
};
|
||||
|
||||
// Emit each DLC as we find it
|
||||
if let Ok(json) = serde_json::to_string(&dlc_info) {
|
||||
if let Err(e) = app_handle.emit("dlc-found", json) {
|
||||
warn!("Failed to emit dlc-found event: {}", e);
|
||||
} else {
|
||||
info!("Emitted dlc-found event for DLC: {}", dlc_id);
|
||||
}
|
||||
}
|
||||
|
||||
dlc_details.push(dlc_info);
|
||||
} else if dlc_response.status() == reqwest::StatusCode::TOO_MANY_REQUESTS {
|
||||
// If rate limited, wait longer
|
||||
error!("Rate limited by Steam API, waiting 10 seconds");
|
||||
emit_dlc_progress(
|
||||
app_handle,
|
||||
"Rate limited by Steam. Waiting...",
|
||||
progress_rounded,
|
||||
None,
|
||||
);
|
||||
tokio::time::sleep(Duration::from_secs(10)).await;
|
||||
}
|
||||
}
|
||||
|
||||
// Final progress update
|
||||
info!(
|
||||
"Completed DLC fetch. Found {} DLCs in total",
|
||||
dlc_details.len()
|
||||
);
|
||||
emit_dlc_progress(
|
||||
app_handle,
|
||||
&format!("Completed! Found {} DLCs", dlc_details.len()),
|
||||
100,
|
||||
None,
|
||||
);
|
||||
info!("Emitted final DLC progress: 100%");
|
||||
|
||||
Ok(dlc_details)
|
||||
}
|
||||
|
||||
// Emit DLC progress updates to the frontend
|
||||
fn emit_dlc_progress(
|
||||
app_handle: &tauri::AppHandle,
|
||||
message: &str,
|
||||
progress: u32,
|
||||
time_left: Option<&str>,
|
||||
) {
|
||||
let mut payload = json!({
|
||||
"message": message,
|
||||
"progress": progress
|
||||
});
|
||||
|
||||
if let Some(time) = time_left {
|
||||
payload["timeLeft"] = json!(time);
|
||||
}
|
||||
|
||||
if let Err(e) = app_handle.emit("dlc-progress", payload) {
|
||||
warn!("Failed to emit dlc-progress event: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Write cream_api.ini configuration file
|
||||
fn write_cream_api_ini(game_path: &str, app_id: &str, dlcs: &[DlcInfo]) -> Result<(), String> {
|
||||
let cream_api_path = Path::new(game_path).join("cream_api.ini");
|
||||
let mut config = String::new();
|
||||
|
||||
config.push_str(&format!("APPID = {}\n[config]\n", app_id));
|
||||
config.push_str("issubscribedapp_on_false_use_real = true\n");
|
||||
config.push_str("[methods]\n");
|
||||
config.push_str("disable_steamapps_issubscribedapp = false\n");
|
||||
config.push_str("[dlc]\n");
|
||||
|
||||
for dlc in dlcs {
|
||||
config.push_str(&format!("{} = {}\n", dlc.appid, dlc.name));
|
||||
}
|
||||
|
||||
fs::write(&cream_api_path, config)
|
||||
.map_err(|e| format!("Failed to write cream_api.ini: {}", e))?;
|
||||
|
||||
info!("Wrote cream_api.ini to {}", cream_api_path.display());
|
||||
Ok(())
|
||||
}
|
||||
@@ -3,9 +3,11 @@
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
|
||||
mod cache;
|
||||
mod dlc_manager;
|
||||
mod installer;
|
||||
mod searcher;
|
||||
mod unlockers;
|
||||
|
||||
use dlc_manager::DlcInfoWithState;
|
||||
use installer::{Game, InstallerAction, InstallerType};
|
||||
@@ -18,6 +20,7 @@ use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use tauri::State;
|
||||
use tauri::{Emitter, Manager};
|
||||
use tauri_plugin_updater::Builder as UpdaterBuilder;
|
||||
use tokio::time::Instant;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
@@ -26,7 +29,6 @@ pub struct GameAction {
|
||||
action: String,
|
||||
}
|
||||
|
||||
// Mark fields with # to allow unused fields
|
||||
#[derive(Debug, Clone)]
|
||||
struct DlcCache {
|
||||
#[allow(dead_code)]
|
||||
@@ -36,7 +38,7 @@ struct DlcCache {
|
||||
}
|
||||
|
||||
// Structure to hold the state of installed games
|
||||
struct AppState {
|
||||
pub struct AppState {
|
||||
games: Mutex<HashMap<String, Game>>,
|
||||
dlc_cache: Mutex<HashMap<String, DlcCache>>,
|
||||
fetch_cancellation: Arc<AtomicBool>,
|
||||
@@ -48,7 +50,6 @@ fn get_all_dlcs_command(game_path: String) -> Result<Vec<DlcInfoWithState>, Stri
|
||||
dlc_manager::get_all_dlcs(&game_path)
|
||||
}
|
||||
|
||||
// Scan and get the list of Steam games
|
||||
#[tauri::command]
|
||||
async fn scan_steam_games(
|
||||
state: State<'_, AppState>,
|
||||
@@ -57,14 +58,11 @@ async fn scan_steam_games(
|
||||
info!("Starting Steam games scan");
|
||||
emit_scan_progress(&app_handle, "Locating Steam libraries...", 10);
|
||||
|
||||
// Get default Steam paths
|
||||
let paths = searcher::get_default_steam_paths();
|
||||
|
||||
// Find Steam libraries
|
||||
emit_scan_progress(&app_handle, "Finding Steam libraries...", 15);
|
||||
let libraries = searcher::find_steam_libraries(&paths);
|
||||
|
||||
// Group libraries by path to avoid duplicates in logs
|
||||
let mut unique_libraries = std::collections::HashSet::new();
|
||||
for lib in &libraries {
|
||||
unique_libraries.insert(lib.to_string_lossy().to_string());
|
||||
@@ -87,7 +85,6 @@ async fn scan_steam_games(
|
||||
20,
|
||||
);
|
||||
|
||||
// Find installed games
|
||||
let games_info = searcher::find_installed_games(&libraries).await;
|
||||
|
||||
emit_scan_progress(
|
||||
@@ -96,7 +93,6 @@ async fn scan_steam_games(
|
||||
90,
|
||||
);
|
||||
|
||||
// Log summary of games found
|
||||
info!("Games scan complete - Found {} games", games_info.len());
|
||||
info!(
|
||||
"Native games: {}",
|
||||
@@ -115,12 +111,10 @@ async fn scan_steam_games(
|
||||
games_info.iter().filter(|g| g.smoke_installed).count()
|
||||
);
|
||||
|
||||
// Convert to our Game struct
|
||||
let mut result = Vec::new();
|
||||
|
||||
info!("Processing games into application state...");
|
||||
for game_info in games_info {
|
||||
// Only log detailed game info at Debug level to keep Info logs cleaner
|
||||
debug!(
|
||||
"Processing game: {}, Native: {}, CreamLinux: {}, SmokeAPI: {}",
|
||||
game_info.title, game_info.native, game_info.cream_installed, game_info.smoke_installed
|
||||
@@ -138,8 +132,6 @@ async fn scan_steam_games(
|
||||
};
|
||||
|
||||
result.push(game.clone());
|
||||
|
||||
// Store in state for later use
|
||||
state.games.lock().insert(game.id.clone(), game);
|
||||
}
|
||||
|
||||
@@ -153,9 +145,7 @@ async fn scan_steam_games(
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
// Helper function to emit scan progress events
|
||||
fn emit_scan_progress(app_handle: &tauri::AppHandle, message: &str, progress: u32) {
|
||||
// Log first, then emit the event
|
||||
info!("Scan progress: {}% - {}", progress, message);
|
||||
|
||||
let payload = serde_json::json!({
|
||||
@@ -168,7 +158,6 @@ fn emit_scan_progress(app_handle: &tauri::AppHandle, message: &str, progress: u3
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch game info by ID - useful for single game updates
|
||||
#[tauri::command]
|
||||
fn get_game_info(game_id: String, state: State<AppState>) -> Result<Game, String> {
|
||||
let games = state.games.lock();
|
||||
@@ -178,14 +167,12 @@ fn get_game_info(game_id: String, state: State<AppState>) -> Result<Game, String
|
||||
.ok_or_else(|| format!("Game with ID {} not found", game_id))
|
||||
}
|
||||
|
||||
// Unified action handler for installation and uninstallation
|
||||
#[tauri::command]
|
||||
async fn process_game_action(
|
||||
game_action: GameAction,
|
||||
state: State<'_, AppState>,
|
||||
app_handle: tauri::AppHandle,
|
||||
) -> Result<Game, String> {
|
||||
// Clone the information we need from state to avoid lifetime issues
|
||||
let game = {
|
||||
let games = state.games.lock();
|
||||
games
|
||||
@@ -194,7 +181,6 @@ async fn process_game_action(
|
||||
.ok_or_else(|| format!("Game with ID {} not found", game_action.game_id))?
|
||||
};
|
||||
|
||||
// Parse the action string to determine type and operation
|
||||
let (installer_type, action) = match game_action.action.as_str() {
|
||||
"install_cream" => (InstallerType::Cream, InstallerAction::Install),
|
||||
"uninstall_cream" => (InstallerType::Cream, InstallerAction::Uninstall),
|
||||
@@ -203,7 +189,6 @@ async fn process_game_action(
|
||||
_ => return Err(format!("Invalid action: {}", game_action.action)),
|
||||
};
|
||||
|
||||
// Execute the action
|
||||
installer::process_action(
|
||||
game_action.game_id.clone(),
|
||||
installer_type,
|
||||
@@ -213,7 +198,6 @@ async fn process_game_action(
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Update game status in state based on the action
|
||||
let updated_game = {
|
||||
let mut games_map = state.games.lock();
|
||||
let game = games_map.get_mut(&game_action.game_id).ok_or_else(|| {
|
||||
@@ -223,7 +207,6 @@ async fn process_game_action(
|
||||
)
|
||||
})?;
|
||||
|
||||
// Update installation status
|
||||
match (installer_type, action) {
|
||||
(InstallerType::Cream, InstallerAction::Install) => {
|
||||
game.cream_installed = true;
|
||||
@@ -239,14 +222,10 @@ async fn process_game_action(
|
||||
}
|
||||
}
|
||||
|
||||
// Reset installing flag
|
||||
game.installing = false;
|
||||
|
||||
// Return updated game info
|
||||
game.clone()
|
||||
};
|
||||
|
||||
// Emit an event to update the UI for this specific game
|
||||
if let Err(e) = app_handle.emit("game-updated", &updated_game) {
|
||||
warn!("Failed to emit game-updated event: {}", e);
|
||||
}
|
||||
@@ -254,18 +233,19 @@ async fn process_game_action(
|
||||
Ok(updated_game)
|
||||
}
|
||||
|
||||
// Fetch DLC list for a game
|
||||
#[tauri::command]
|
||||
async fn fetch_game_dlcs(
|
||||
game_id: String,
|
||||
app_handle: tauri::AppHandle,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<Vec<DlcInfoWithState>, String> {
|
||||
info!("Fetching DLCs for game ID: {}", game_id);
|
||||
info!("Fetching DLC list for game ID: {}", game_id);
|
||||
|
||||
// Fetch DLC data
|
||||
// Fetch DLC data from API
|
||||
match installer::fetch_dlc_details(&game_id).await {
|
||||
Ok(dlcs) => {
|
||||
// Convert to DlcInfoWithState
|
||||
info!("Successfully fetched {} DLCs for game {}", dlcs.len(), game_id);
|
||||
|
||||
// Convert to DLCInfoWithState for in-memory caching
|
||||
let dlcs_with_state = dlcs
|
||||
.into_iter()
|
||||
.map(|dlc| DlcInfoWithState {
|
||||
@@ -275,31 +255,31 @@ async fn fetch_game_dlcs(
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Cache in memory for this session
|
||||
let state = app_handle.state::<AppState>();
|
||||
let mut cache = state.dlc_cache.lock();
|
||||
cache.insert(
|
||||
// Update in-memory cache
|
||||
let mut dlc_cache = state.dlc_cache.lock();
|
||||
dlc_cache.insert(
|
||||
game_id.clone(),
|
||||
DlcCache {
|
||||
data: dlcs_with_state.clone(),
|
||||
timestamp: Instant::now(),
|
||||
timestamp: tokio::time::Instant::now(),
|
||||
},
|
||||
);
|
||||
|
||||
Ok(dlcs_with_state)
|
||||
}
|
||||
Err(e) => Err(format!("Failed to fetch DLC details: {}", e)),
|
||||
Err(e) => {
|
||||
error!("Failed to fetch DLC details: {}", e);
|
||||
Err(format!("Failed to fetch DLC details: {}", e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn abort_dlc_fetch(game_id: String, app_handle: tauri::AppHandle) -> Result<(), String> {
|
||||
info!("Request to abort DLC fetch for game ID: {}", game_id);
|
||||
|
||||
let state = app_handle.state::<AppState>();
|
||||
fn abort_dlc_fetch(state: State<'_, AppState>, app_handle: tauri::AppHandle) -> Result<(), String> {
|
||||
info!("Aborting DLC fetch request received");
|
||||
state.fetch_cancellation.store(true, Ordering::SeqCst);
|
||||
|
||||
// Reset after a short delay
|
||||
// Reset cancellation flag after a short delay
|
||||
std::thread::spawn(move || {
|
||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
let state = app_handle.state::<AppState>();
|
||||
@@ -309,7 +289,6 @@ fn abort_dlc_fetch(game_id: String, app_handle: tauri::AppHandle) -> Result<(),
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Fetch DLC list with progress updates (streaming)
|
||||
#[tauri::command]
|
||||
async fn stream_game_dlcs(game_id: String, app_handle: tauri::AppHandle) -> Result<(), String> {
|
||||
info!("Streaming DLCs for game ID: {}", game_id);
|
||||
@@ -333,7 +312,7 @@ async fn stream_game_dlcs(game_id: String, app_handle: tauri::AppHandle) -> Resu
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Update in-memory
|
||||
// Update in-memory cache
|
||||
let state = app_handle.state::<AppState>();
|
||||
let mut dlc_cache = state.dlc_cache.lock();
|
||||
dlc_cache.insert(
|
||||
@@ -362,21 +341,18 @@ async fn stream_game_dlcs(game_id: String, app_handle: tauri::AppHandle) -> Resu
|
||||
}
|
||||
}
|
||||
|
||||
// Clear caches command renamed to flush_data for clarity
|
||||
#[tauri::command]
|
||||
fn clear_caches() -> Result<(), String> {
|
||||
info!("Data flush requested - cleaning in-memory state only");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Get the list of enabled DLCs for a game
|
||||
#[tauri::command]
|
||||
fn get_enabled_dlcs_command(game_path: String) -> Result<Vec<String>, String> {
|
||||
info!("Getting enabled DLCs for: {}", game_path);
|
||||
dlc_manager::get_enabled_dlcs(&game_path)
|
||||
}
|
||||
|
||||
// Update the DLC configuration for a game
|
||||
#[tauri::command]
|
||||
fn update_dlc_configuration_command(
|
||||
game_path: String,
|
||||
@@ -386,7 +362,6 @@ fn update_dlc_configuration_command(
|
||||
dlc_manager::update_dlc_configuration(&game_path, dlcs)
|
||||
}
|
||||
|
||||
// Install CreamLinux with selected DLCs
|
||||
#[tauri::command]
|
||||
async fn install_cream_with_dlcs_command(
|
||||
game_id: String,
|
||||
@@ -459,7 +434,6 @@ async fn install_cream_with_dlcs_command(
|
||||
}
|
||||
}
|
||||
|
||||
// Setup logging
|
||||
fn setup_logging() -> Result<(), Box<dyn std::error::Error>> {
|
||||
use log::LevelFilter;
|
||||
use log4rs::append::file::FileAppender;
|
||||
@@ -467,30 +441,25 @@ fn setup_logging() -> Result<(), Box<dyn std::error::Error>> {
|
||||
use log4rs::encode::pattern::PatternEncoder;
|
||||
use std::fs;
|
||||
|
||||
// Get XDG cache directory
|
||||
let xdg_dirs = xdg::BaseDirectories::with_prefix("creamlinux")?;
|
||||
let log_path = xdg_dirs.place_cache_file("creamlinux.log")?;
|
||||
|
||||
// Clear the log file on startup
|
||||
if log_path.exists() {
|
||||
if let Err(e) = fs::write(&log_path, "") {
|
||||
eprintln!("Warning: Failed to clear log file: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Create a file appender
|
||||
let file = FileAppender::builder()
|
||||
.encoder(Box::new(PatternEncoder::new(
|
||||
"[{d(%Y-%m-%d %H:%M:%S)}] {l}: {m}\n",
|
||||
)))
|
||||
.build(log_path)?;
|
||||
|
||||
// Build the config
|
||||
let config = Config::builder()
|
||||
.appender(Appender::builder().build("file", Box::new(file)))
|
||||
.build(Root::builder().appender("file").build(LevelFilter::Info))?;
|
||||
|
||||
// Initialize log4rs with this config
|
||||
log4rs::init_config(config)?;
|
||||
|
||||
info!("CreamLinux started with a clean log file");
|
||||
@@ -498,26 +467,18 @@ fn setup_logging() -> Result<(), Box<dyn std::error::Error>> {
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Set up logging first
|
||||
if let Err(e) = setup_logging() {
|
||||
eprintln!("Warning: Failed to initialize logging: {}", e);
|
||||
}
|
||||
|
||||
info!("Initializing CreamLinux application");
|
||||
|
||||
let app_state = AppState {
|
||||
games: Mutex::new(HashMap::new()),
|
||||
dlc_cache: Mutex::new(HashMap::new()),
|
||||
fetch_cancellation: Arc::new(AtomicBool::new(false)),
|
||||
};
|
||||
|
||||
tauri::Builder::default()
|
||||
.plugin(UpdaterBuilder::new().build())
|
||||
.plugin(tauri_plugin_process::init())
|
||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
.manage(app_state)
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
scan_steam_games,
|
||||
get_game_info,
|
||||
@@ -532,7 +493,6 @@ fn main() {
|
||||
abort_dlc_fetch,
|
||||
])
|
||||
.setup(|app| {
|
||||
// Add a setup handler to do any initialization work
|
||||
info!("Tauri application setup");
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -543,8 +503,71 @@ fn main() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let app_handle = app.handle().clone();
|
||||
let state = AppState {
|
||||
games: Mutex::new(HashMap::new()),
|
||||
dlc_cache: Mutex::new(HashMap::new()),
|
||||
fetch_cancellation: Arc::new(AtomicBool::new(false)),
|
||||
};
|
||||
app.manage(state);
|
||||
|
||||
// Initialize cache on startup in a background task
|
||||
tauri::async_runtime::spawn(async move {
|
||||
info!("Starting cache initialization...");
|
||||
|
||||
// Step 1: Initialize cache if needed (downloads unlockers)
|
||||
if let Err(e) = cache::initialize_cache().await {
|
||||
error!("Failed to initialize cache: {}", e);
|
||||
return;
|
||||
}
|
||||
|
||||
info!("Cache initialized successfully");
|
||||
|
||||
// Step 2: Check for updates
|
||||
match cache::check_and_update_cache().await {
|
||||
Ok(result) => {
|
||||
if result.any_updated() {
|
||||
info!(
|
||||
"Updates found - SmokeAPI: {:?}, CreamLinux: {:?}",
|
||||
result.new_smokeapi_version, result.new_creamlinux_version
|
||||
);
|
||||
|
||||
// Step 3: Update outdated games
|
||||
let state_for_update = app_handle.state::<AppState>();
|
||||
let games = state_for_update.games.lock().clone();
|
||||
|
||||
match cache::update_outdated_games(&games).await {
|
||||
Ok(stats) => {
|
||||
info!(
|
||||
"Game updates complete - {} games updated, {} failed",
|
||||
stats.total_updated(),
|
||||
stats.total_failed()
|
||||
);
|
||||
|
||||
if stats.has_failures() {
|
||||
warn!(
|
||||
"Some game updates failed: SmokeAPI failed: {}, CreamLinux failed: {}",
|
||||
stats.smokeapi_failed, stats.creamlinux_failed
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to update games: {}", e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
info!("All unlockers are up to date");
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to check for updates: {}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
}
|
||||
@@ -426,6 +426,7 @@ fn scan_game_directory(game_path: &Path) -> (bool, Vec<String>) {
|
||||
}
|
||||
|
||||
// Detection logic with priority system
|
||||
let has_steam_api_dll = !steam_api_files.is_empty();
|
||||
let is_native = determine_platform(
|
||||
has_libsteam_api,
|
||||
has_linux_steam_libs,
|
||||
@@ -434,6 +435,7 @@ fn scan_game_directory(game_path: &Path) -> (bool, Vec<String>) {
|
||||
found_main_executable,
|
||||
linux_binary_count,
|
||||
windows_exe_count,
|
||||
has_steam_api_dll,
|
||||
);
|
||||
|
||||
debug!(
|
||||
@@ -458,6 +460,7 @@ fn determine_platform(
|
||||
found_main_executable: bool,
|
||||
linux_binary_count: usize,
|
||||
windows_exe_count: usize,
|
||||
has_steam_api_dll: bool,
|
||||
) -> bool {
|
||||
// Priority 1: Strong Linux indicators
|
||||
if has_libsteam_api {
|
||||
@@ -470,25 +473,31 @@ fn determine_platform(
|
||||
return true;
|
||||
}
|
||||
|
||||
// Priority 2: High confidence Linux indicators
|
||||
// Priority 2: Strong Windows indicators - DLL files are Windows-only
|
||||
if has_steam_api_dll {
|
||||
debug!("Detected as Windows/Proton: steam_api.dll or steam_api64.dll found");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Priority 3: High confidence Linux indicators
|
||||
if found_linux_binary && linux_binary_count >= 3 && !found_main_executable {
|
||||
debug!("Detected as native: Multiple Linux binaries, no main Windows executable");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Priority 3: Balanced assessment
|
||||
// Priority 4: Balanced assessment
|
||||
if found_linux_binary && !found_main_executable && windows_exe_count <= 2 {
|
||||
debug!("Detected as native: Linux binaries present, only installer/utility Windows files");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Priority 4: Windows indicators
|
||||
// Priority 5: Windows indicators
|
||||
if found_main_executable || (found_exe && !found_linux_binary) {
|
||||
debug!("Detected as Windows/Proton: Main executable or only Windows files found");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Priority 5: Default fallback
|
||||
// Priority 6: Default fallback
|
||||
if found_linux_binary {
|
||||
debug!("Detected as native: Linux binaries found (default fallback)");
|
||||
return true;
|
||||
@@ -695,4 +704,4 @@ pub async fn find_installed_games(steamapps_paths: &[PathBuf]) -> Vec<GameInfo>
|
||||
|
||||
info!("Found {} installed games", games.len());
|
||||
games
|
||||
}
|
||||
}
|
||||
225
src-tauri/src/unlockers/creamlinux.rs
Normal file
@@ -0,0 +1,225 @@
|
||||
use super::Unlocker;
|
||||
use async_trait::async_trait;
|
||||
use log::{info, warn};
|
||||
use reqwest;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
use tempfile::tempdir;
|
||||
use zip::ZipArchive;
|
||||
|
||||
pub struct CreamLinux;
|
||||
|
||||
#[async_trait]
|
||||
impl Unlocker for CreamLinux {
|
||||
async fn get_latest_version() -> Result<String, String> {
|
||||
info!("Fetching latest CreamLinux version...");
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
// Fetch the latest release from GitHub API
|
||||
let api_url = "https://api.github.com/repos/anticitizn/creamlinux/releases/latest";
|
||||
|
||||
let response = client
|
||||
.get(api_url)
|
||||
.header("User-Agent", "CreamLinux-Installer")
|
||||
.timeout(Duration::from_secs(10))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to fetch CreamLinux releases: {}", e))?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
return Err(format!(
|
||||
"Failed to fetch CreamLinux releases: HTTP {}",
|
||||
response.status()
|
||||
));
|
||||
}
|
||||
|
||||
let release_info: serde_json::Value = response
|
||||
.json()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to parse release info: {}", e))?;
|
||||
|
||||
let version = release_info
|
||||
.get("tag_name")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or_else(|| "Failed to extract version from release info".to_string())?
|
||||
.to_string();
|
||||
|
||||
info!("Latest CreamLinux version: {}", version);
|
||||
Ok(version)
|
||||
}
|
||||
|
||||
async fn download_to_cache() -> Result<String, String> {
|
||||
let version = Self::get_latest_version().await?;
|
||||
info!("Downloading CreamLinux version {}...", version);
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
// Construct the download URL using the version
|
||||
let download_url = format!(
|
||||
"https://github.com/anticitizn/creamlinux/releases/download/{}/creamlinux.zip",
|
||||
version
|
||||
);
|
||||
|
||||
// Download the zip
|
||||
let response = client
|
||||
.get(&download_url)
|
||||
.timeout(Duration::from_secs(30))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to download CreamLinux: {}", e))?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
return Err(format!(
|
||||
"Failed to download CreamLinux: HTTP {}",
|
||||
response.status()
|
||||
));
|
||||
}
|
||||
|
||||
// Save to temporary file
|
||||
let temp_dir = tempdir().map_err(|e| format!("Failed to create temp dir: {}", e))?;
|
||||
let zip_path = temp_dir.path().join("creamlinux.zip");
|
||||
let content = response
|
||||
.bytes()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to read response bytes: {}", e))?;
|
||||
fs::write(&zip_path, &content).map_err(|e| format!("Failed to write zip file: {}", e))?;
|
||||
|
||||
// Extract to cache directory
|
||||
let version_dir = crate::cache::get_creamlinux_version_dir(&version)?;
|
||||
let file = fs::File::open(&zip_path).map_err(|e| format!("Failed to open zip: {}", e))?;
|
||||
let mut archive =
|
||||
ZipArchive::new(file).map_err(|e| format!("Failed to read zip archive: {}", e))?;
|
||||
|
||||
// Extract all files
|
||||
for i in 0..archive.len() {
|
||||
let mut file = archive
|
||||
.by_index(i)
|
||||
.map_err(|e| format!("Failed to access zip entry: {}", e))?;
|
||||
|
||||
let file_name = file.name().to_string(); // Clone the name early
|
||||
|
||||
// Skip directories
|
||||
if file_name.ends_with('/') {
|
||||
continue;
|
||||
}
|
||||
|
||||
let output_path = version_dir.join(
|
||||
Path::new(&file_name)
|
||||
.file_name()
|
||||
.unwrap_or_else(|| std::ffi::OsStr::new(&file_name)),
|
||||
);
|
||||
|
||||
let mut outfile = fs::File::create(&output_path)
|
||||
.map_err(|e| format!("Failed to create output file: {}", e))?;
|
||||
io::copy(&mut file, &mut outfile)
|
||||
.map_err(|e| format!("Failed to extract file: {}", e))?;
|
||||
|
||||
// Make .sh files executable
|
||||
if file_name.ends_with(".sh") {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let mut perms = fs::metadata(&output_path)
|
||||
.map_err(|e| format!("Failed to get file metadata: {}", e))?
|
||||
.permissions();
|
||||
perms.set_mode(0o755);
|
||||
fs::set_permissions(&output_path, perms)
|
||||
.map_err(|e| format!("Failed to set permissions: {}", e))?;
|
||||
}
|
||||
}
|
||||
|
||||
info!("Extracted: {}", output_path.display());
|
||||
}
|
||||
|
||||
info!(
|
||||
"CreamLinux version {} downloaded to cache successfully",
|
||||
version
|
||||
);
|
||||
Ok(version)
|
||||
}
|
||||
|
||||
async fn install_to_game(game_path: &str, _game_id: &str) -> Result<(), String> {
|
||||
info!("Installing CreamLinux to {}", game_path);
|
||||
|
||||
// Get the cached CreamLinux files
|
||||
let cached_files = crate::cache::list_creamlinux_files()?;
|
||||
if cached_files.is_empty() {
|
||||
return Err("No CreamLinux files found in cache".to_string());
|
||||
}
|
||||
|
||||
let game_path_obj = Path::new(game_path);
|
||||
|
||||
// Copy all files to the game directory
|
||||
for file in &cached_files {
|
||||
let file_name = file.file_name().ok_or_else(|| {
|
||||
format!("Failed to get filename from: {}", file.display())
|
||||
})?;
|
||||
|
||||
let dest_path = game_path_obj.join(file_name);
|
||||
|
||||
fs::copy(file, &dest_path)
|
||||
.map_err(|e| format!("Failed to copy {} to game directory: {}", file_name.to_string_lossy(), e))?;
|
||||
|
||||
// Make .sh files executable
|
||||
if file_name.to_string_lossy().ends_with(".sh") {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let mut perms = fs::metadata(&dest_path)
|
||||
.map_err(|e| format!("Failed to get file metadata: {}", e))?
|
||||
.permissions();
|
||||
perms.set_mode(0o755);
|
||||
fs::set_permissions(&dest_path, perms)
|
||||
.map_err(|e| format!("Failed to set permissions: {}", e))?;
|
||||
}
|
||||
}
|
||||
|
||||
info!("Installed: {}", dest_path.display());
|
||||
}
|
||||
|
||||
// Note: cream_api.ini is managed separately by dlc_manager
|
||||
// This function only installs the binaries
|
||||
|
||||
info!("CreamLinux installation completed for: {}", game_path);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn uninstall_from_game(game_path: &str, _game_id: &str) -> Result<(), String> {
|
||||
info!("Uninstalling CreamLinux from: {}", game_path);
|
||||
|
||||
let game_path_obj = Path::new(game_path);
|
||||
|
||||
// List of CreamLinux files to remove
|
||||
let files_to_remove = vec![
|
||||
"cream.sh",
|
||||
"lib32Creamlinux.so",
|
||||
"lib64Creamlinux.so",
|
||||
"cream_api.ini",
|
||||
];
|
||||
|
||||
for file_name in files_to_remove {
|
||||
let file_path = game_path_obj.join(file_name);
|
||||
|
||||
if file_path.exists() {
|
||||
match fs::remove_file(&file_path) {
|
||||
Ok(_) => info!("Removed: {}", file_path.display()),
|
||||
Err(e) => warn!(
|
||||
"Failed to remove {}: {}",
|
||||
file_path.display(),
|
||||
e
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info!("CreamLinux uninstallation completed for: {}", game_path);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn name() -> &'static str {
|
||||
"CreamLinux"
|
||||
}
|
||||
}
|
||||
27
src-tauri/src/unlockers/mod.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
mod creamlinux;
|
||||
mod smokeapi;
|
||||
|
||||
pub use creamlinux::CreamLinux;
|
||||
pub use smokeapi::SmokeAPI;
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
// Common trait for all unlockers (CreamLinux, SmokeAPI)
|
||||
#[async_trait]
|
||||
pub trait Unlocker {
|
||||
// Get the latest version from the remote source
|
||||
async fn get_latest_version() -> Result<String, String>;
|
||||
|
||||
// Download the unlocker to the cache directory
|
||||
async fn download_to_cache() -> Result<String, String>;
|
||||
|
||||
// Install the unlocker from cache to a game directory
|
||||
async fn install_to_game(game_path: &str, context: &str) -> Result<(), String>;
|
||||
|
||||
// Uninstall the unlocker from a game directory
|
||||
async fn uninstall_from_game(game_path: &str, context: &str) -> Result<(), String>;
|
||||
|
||||
// Get the name of the unlocker
|
||||
#[allow(dead_code)]
|
||||
fn name() -> &'static str;
|
||||
}
|
||||
260
src-tauri/src/unlockers/smokeapi.rs
Normal file
@@ -0,0 +1,260 @@
|
||||
use super::Unlocker;
|
||||
use async_trait::async_trait;
|
||||
use log::{error, info, warn};
|
||||
use reqwest;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
use tempfile::tempdir;
|
||||
use zip::ZipArchive;
|
||||
|
||||
const SMOKEAPI_REPO: &str = "acidicoala/SmokeAPI";
|
||||
|
||||
pub struct SmokeAPI;
|
||||
|
||||
#[async_trait]
|
||||
impl Unlocker for SmokeAPI {
|
||||
async fn get_latest_version() -> Result<String, String> {
|
||||
info!("Fetching latest SmokeAPI version...");
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let releases_url = format!(
|
||||
"https://api.github.com/repos/{}/releases/latest",
|
||||
SMOKEAPI_REPO
|
||||
);
|
||||
|
||||
let response = client
|
||||
.get(&releases_url)
|
||||
.header("User-Agent", "CreamLinux")
|
||||
.timeout(Duration::from_secs(10))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to fetch SmokeAPI releases: {}", e))?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
return Err(format!(
|
||||
"Failed to fetch SmokeAPI releases: HTTP {}",
|
||||
response.status()
|
||||
));
|
||||
}
|
||||
|
||||
let release_info: serde_json::Value = response
|
||||
.json()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to parse release info: {}", e))?;
|
||||
|
||||
let version = release_info
|
||||
.get("tag_name")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or_else(|| "Failed to extract version from release info".to_string())?
|
||||
.to_string();
|
||||
|
||||
info!("Latest SmokeAPI version: {}", version);
|
||||
Ok(version)
|
||||
}
|
||||
|
||||
async fn download_to_cache() -> Result<String, String> {
|
||||
let version = Self::get_latest_version().await?;
|
||||
info!("Downloading SmokeAPI version {}...", version);
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let zip_url = format!(
|
||||
"https://github.com/{}/releases/download/{}/SmokeAPI-{}.zip",
|
||||
SMOKEAPI_REPO, version, version
|
||||
);
|
||||
|
||||
// Download the zip
|
||||
let response = client
|
||||
.get(&zip_url)
|
||||
.timeout(Duration::from_secs(30))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to download SmokeAPI: {}", e))?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
return Err(format!(
|
||||
"Failed to download SmokeAPI: HTTP {}",
|
||||
response.status()
|
||||
));
|
||||
}
|
||||
|
||||
// Save to temporary file
|
||||
let temp_dir = tempdir().map_err(|e| format!("Failed to create temp dir: {}", e))?;
|
||||
let zip_path = temp_dir.path().join("smokeapi.zip");
|
||||
let content = response
|
||||
.bytes()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to read response bytes: {}", e))?;
|
||||
fs::write(&zip_path, &content).map_err(|e| format!("Failed to write zip file: {}", e))?;
|
||||
|
||||
// Extract to cache directory
|
||||
let version_dir = crate::cache::get_smokeapi_version_dir(&version)?;
|
||||
let file = fs::File::open(&zip_path).map_err(|e| format!("Failed to open zip: {}", e))?;
|
||||
let mut archive =
|
||||
ZipArchive::new(file).map_err(|e| format!("Failed to read zip archive: {}", e))?;
|
||||
|
||||
// Extract all DLL files
|
||||
for i in 0..archive.len() {
|
||||
let mut file = archive
|
||||
.by_index(i)
|
||||
.map_err(|e| format!("Failed to access zip entry: {}", e))?;
|
||||
|
||||
let file_name = file.name();
|
||||
|
||||
// Only extract DLL files
|
||||
if file_name.to_lowercase().ends_with(".dll") {
|
||||
let output_path = version_dir.join(
|
||||
Path::new(file_name)
|
||||
.file_name()
|
||||
.unwrap_or_else(|| std::ffi::OsStr::new(file_name)),
|
||||
);
|
||||
|
||||
let mut outfile = fs::File::create(&output_path)
|
||||
.map_err(|e| format!("Failed to create output file: {}", e))?;
|
||||
io::copy(&mut file, &mut outfile)
|
||||
.map_err(|e| format!("Failed to extract file: {}", e))?;
|
||||
|
||||
info!("Extracted: {}", output_path.display());
|
||||
}
|
||||
}
|
||||
|
||||
info!(
|
||||
"SmokeAPI version {} downloaded to cache successfully",
|
||||
version
|
||||
);
|
||||
Ok(version)
|
||||
}
|
||||
|
||||
async fn install_to_game(game_path: &str, api_files_str: &str) -> Result<(), String> {
|
||||
// Parse api_files from the context string (comma-separated)
|
||||
let api_files: Vec<String> = api_files_str.split(',').map(|s| s.to_string()).collect();
|
||||
|
||||
info!(
|
||||
"Installing SmokeAPI to {} for {} API files",
|
||||
game_path,
|
||||
api_files.len()
|
||||
);
|
||||
|
||||
// Get the cached SmokeAPI DLLs
|
||||
let cached_dlls = crate::cache::list_smokeapi_dlls()?;
|
||||
if cached_dlls.is_empty() {
|
||||
return Err("No SmokeAPI DLLs found in cache".to_string());
|
||||
}
|
||||
|
||||
for api_file in &api_files {
|
||||
let api_dir = Path::new(game_path).join(
|
||||
Path::new(api_file)
|
||||
.parent()
|
||||
.unwrap_or_else(|| Path::new("")),
|
||||
);
|
||||
let api_name = Path::new(api_file).file_name().unwrap_or_default();
|
||||
|
||||
// Backup original file
|
||||
let original_path = api_dir.join(api_name);
|
||||
let backup_path = api_dir.join(api_name.to_string_lossy().replace(".dll", "_o.dll"));
|
||||
|
||||
info!("Processing: {}", original_path.display());
|
||||
|
||||
// Only backup if not already backed up
|
||||
if !backup_path.exists() && original_path.exists() {
|
||||
fs::copy(&original_path, &backup_path)
|
||||
.map_err(|e| format!("Failed to backup original file: {}", e))?;
|
||||
info!("Created backup: {}", backup_path.display());
|
||||
}
|
||||
|
||||
// Determine if we need 32-bit or 64-bit SmokeAPI DLL
|
||||
let is_64bit = api_name.to_string_lossy().contains("64");
|
||||
let target_arch = if is_64bit { "64" } else { "32" };
|
||||
|
||||
// Find the matching DLL
|
||||
let matching_dll = cached_dlls
|
||||
.iter()
|
||||
.find(|dll| {
|
||||
let dll_name = dll.file_name().unwrap_or_default().to_string_lossy();
|
||||
dll_name.to_lowercase().contains("smoke")
|
||||
&& dll_name
|
||||
.to_lowercase()
|
||||
.contains(&format!("{}.dll", target_arch))
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"No matching {}-bit SmokeAPI DLL found in cache",
|
||||
target_arch
|
||||
)
|
||||
})?;
|
||||
|
||||
// Copy the DLL to the game directory
|
||||
fs::copy(matching_dll, &original_path)
|
||||
.map_err(|e| format!("Failed to install SmokeAPI DLL: {}", e))?;
|
||||
|
||||
info!(
|
||||
"Installed {} as: {}",
|
||||
matching_dll.display(),
|
||||
original_path.display()
|
||||
);
|
||||
}
|
||||
|
||||
info!("SmokeAPI installation completed for: {}", game_path);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn uninstall_from_game(game_path: &str, api_files_str: &str) -> Result<(), String> {
|
||||
// Parse api_files from the context string (comma-separated)
|
||||
let api_files: Vec<String> = api_files_str.split(',').map(|s| s.to_string()).collect();
|
||||
|
||||
info!("Uninstalling SmokeAPI from: {}", game_path);
|
||||
|
||||
for api_file in &api_files {
|
||||
let api_path = Path::new(game_path).join(api_file);
|
||||
let api_dir = api_path.parent().unwrap_or_else(|| Path::new(game_path));
|
||||
let api_name = api_path.file_name().unwrap_or_default();
|
||||
|
||||
let original_path = api_dir.join(api_name);
|
||||
let backup_path = api_dir.join(api_name.to_string_lossy().replace(".dll", "_o.dll"));
|
||||
|
||||
info!("Processing: {}", original_path.display());
|
||||
|
||||
if backup_path.exists() {
|
||||
// Remove the SmokeAPI version
|
||||
if original_path.exists() {
|
||||
match fs::remove_file(&original_path) {
|
||||
Ok(_) => info!("Removed SmokeAPI file: {}", original_path.display()),
|
||||
Err(e) => warn!(
|
||||
"Failed to remove SmokeAPI file: {}, error: {}",
|
||||
original_path.display(),
|
||||
e
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// Restore the original file
|
||||
match fs::rename(&backup_path, &original_path) {
|
||||
Ok(_) => info!("Restored original file: {}", original_path.display()),
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"Failed to restore original file: {}, error: {}",
|
||||
original_path.display(),
|
||||
e
|
||||
);
|
||||
// Try to copy instead if rename fails
|
||||
if let Err(copy_err) = fs::copy(&backup_path, &original_path)
|
||||
.and_then(|_| fs::remove_file(&backup_path))
|
||||
{
|
||||
error!("Failed to copy backup file: {}", copy_err);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
info!("No backup found for: {}", api_file);
|
||||
}
|
||||
}
|
||||
|
||||
info!("SmokeAPI uninstallation completed for: {}", game_path);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn name() -> &'static str {
|
||||
"SmokeAPI"
|
||||
}
|
||||
}
|
||||
@@ -10,25 +10,17 @@
|
||||
"active": true,
|
||||
"targets": "all",
|
||||
"category": "Utility",
|
||||
"createUpdaterArtifacts": true,
|
||||
"icon": ["icons/128x128.png", "icons/128x128@2x.png", "icons/icon.png"],
|
||||
"resources": ["resources/libs/*"]
|
||||
"icon": [
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.png"
|
||||
],
|
||||
"createUpdaterArtifacts": true
|
||||
},
|
||||
"productName": "Creamlinux",
|
||||
"mainBinaryName": "creamlinux",
|
||||
"version": "1.0.2",
|
||||
"version": "1.3.0",
|
||||
"identifier": "com.creamlinux.dev",
|
||||
"plugins": {
|
||||
"updater": {
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDJDNEI1NzBBRDUxODQ3RjEKUldUeFJ4alZDbGRMTE5Vc241NG5yL080UklnaW1iUGdUWElPRXloRGtKZ3M2SWkzK0RGSDh3Q2kK",
|
||||
"endpoints": [
|
||||
"https://github.com/Novattz/creamlinux-installer/releases/latest/download/latest.json"
|
||||
],
|
||||
"windows": {
|
||||
"installMode": "passive"
|
||||
}
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"withGlobalTauri": false,
|
||||
"windows": [
|
||||
@@ -45,5 +37,13 @@
|
||||
"security": {
|
||||
"csp": null
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"updater": {
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IERENzBFNjU0RTBBMUMyNzgKUldSNHdxSGdWT1p3M1liUE0vOGFCRkc2cEQwdWdRR2UyY2VmN3kzckNONCtsaGF0Y1d2WjdOWVEK",
|
||||
"endpoints": [
|
||||
"https://github.com/Novattz/creamlinux-installer/releases/latest/download/latest.json"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
18
src/App.tsx
@@ -1,11 +1,10 @@
|
||||
import { useState } from 'react'
|
||||
import { useAppContext } from '@/contexts/useAppContext'
|
||||
import { UpdateNotifier } from '@/components/updater'
|
||||
import { useAppLogic } from '@/hooks'
|
||||
import './styles/main.scss'
|
||||
|
||||
// Layout components
|
||||
import { Header, Sidebar, InitialLoadingScreen, ErrorBoundary } from '@/components/layout'
|
||||
import AnimatedBackground from '@/components/layout/AnimatedBackground'
|
||||
import { Header, Sidebar, InitialLoadingScreen, ErrorBoundary, UpdateScreen, AnimatedBackground } from '@/components/layout'
|
||||
|
||||
// Dialog components
|
||||
import { ProgressDialog, DlcSelectionDialog, SettingsDialog } from '@/components/dialogs'
|
||||
@@ -17,6 +16,7 @@ import { GameList } from '@/components/games'
|
||||
* Main application component
|
||||
*/
|
||||
function App() {
|
||||
const [updateComplete, setUpdateComplete] = useState(false)
|
||||
// Get application logic from hook
|
||||
const {
|
||||
filter,
|
||||
@@ -29,7 +29,7 @@ function App() {
|
||||
handleRefresh,
|
||||
isLoading,
|
||||
error,
|
||||
} = useAppLogic({ autoLoad: true })
|
||||
} = useAppLogic({ autoLoad: updateComplete })
|
||||
|
||||
// Get action handlers from context
|
||||
const {
|
||||
@@ -45,7 +45,12 @@ function App() {
|
||||
handleSettingsClose,
|
||||
} = useAppContext()
|
||||
|
||||
// Show loading screen during initial load
|
||||
// Show update screen first
|
||||
if (!updateComplete) {
|
||||
return <UpdateScreen onComplete={() => setUpdateComplete(true)} />
|
||||
}
|
||||
|
||||
// Then show initial loading screen
|
||||
if (isInitialLoad) {
|
||||
return <InitialLoadingScreen message={scanProgress.message} progress={scanProgress.progress} />
|
||||
}
|
||||
@@ -114,9 +119,6 @@ function App() {
|
||||
visible ={settingsDialog.visible}
|
||||
onClose={handleSettingsClose}
|
||||
/>
|
||||
|
||||
{/* Simple update notifier that uses toast - no UI component */}
|
||||
<UpdateNotifier />
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { FC } from 'react'
|
||||
import Button, { ButtonVariant } from '../buttons/Button'
|
||||
import { Icon, layers, download } from '@/components/icons'
|
||||
import { Icon, trash, download } from '@/components/icons'
|
||||
|
||||
// Define available action types
|
||||
export type ActionType = 'install_cream' | 'uninstall_cream' | 'install_smoke' | 'uninstall_smoke'
|
||||
@@ -45,14 +45,12 @@ const ActionButton: FC<ActionButtonProps> = ({
|
||||
|
||||
// Select appropriate icon based on action type and state
|
||||
const getIconInfo = () => {
|
||||
const isCream = action.includes('cream')
|
||||
|
||||
if (isInstalled) {
|
||||
// Uninstall actions
|
||||
return { name: layers, variant: 'bold' }
|
||||
return { name: trash, variant: 'solid' }
|
||||
} else {
|
||||
// Install actions
|
||||
return { name: download, variant: isCream ? 'bold' : 'outline' }
|
||||
return { name: download, variant: 'solid' }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ const AnimatedCheckbox = ({
|
||||
<input type="checkbox" checked={checked} onChange={onChange} className="checkbox-original" />
|
||||
|
||||
<span className={`checkbox-custom ${checked ? 'checked' : ''}`}>
|
||||
{checked && <Icon name={check} variant="bold" size="sm" className="checkbox-icon" />}
|
||||
{checked && <Icon name={check} variant="solid" size="sm" className="checkbox-icon" />}
|
||||
</span>
|
||||
|
||||
{(label || sublabel) && (
|
||||
|
||||
@@ -10,6 +10,7 @@ interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
leftIcon?: React.ReactNode
|
||||
rightIcon?: React.ReactNode
|
||||
fullWidth?: boolean
|
||||
iconOnly?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -23,6 +24,7 @@ const Button: FC<ButtonProps> = ({
|
||||
leftIcon,
|
||||
rightIcon,
|
||||
fullWidth = false,
|
||||
iconOnly = false,
|
||||
className = '',
|
||||
disabled,
|
||||
...props
|
||||
@@ -43,11 +45,14 @@ const Button: FC<ButtonProps> = ({
|
||||
warning: 'btn-warning',
|
||||
}[variant]
|
||||
|
||||
// Determine if this is an icon-only button
|
||||
const isIconOnly = iconOnly || (!children && (leftIcon || rightIcon))
|
||||
|
||||
return (
|
||||
<button
|
||||
className={`btn ${variantClass} ${sizeClass} ${fullWidth ? 'btn-full' : ''} ${
|
||||
isLoading ? 'btn-loading' : ''
|
||||
} ${className}`}
|
||||
} ${isIconOnly ? 'btn-icon-only' : ''} ${className}`}
|
||||
disabled={disabled || isLoading}
|
||||
{...props}
|
||||
>
|
||||
@@ -58,10 +63,10 @@ const Button: FC<ButtonProps> = ({
|
||||
)}
|
||||
|
||||
{leftIcon && !isLoading && <span className="btn-icon btn-icon-left">{leftIcon}</span>}
|
||||
<span className="btn-text">{children}</span>
|
||||
{children && <span className="btn-text">{children}</span>}
|
||||
{rightIcon && !isLoading && <span className="btn-icon btn-icon-right">{rightIcon}</span>}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export default Button
|
||||
export default Button
|
||||
22
src/components/common/ProgressBar.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
interface ProgressBarProps {
|
||||
progress: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple progress bar component
|
||||
*/
|
||||
const ProgressBar = ({ progress }: ProgressBarProps) => {
|
||||
return (
|
||||
<div className="progress-container">
|
||||
<div className="progress-bar">
|
||||
<div
|
||||
className="progress-fill"
|
||||
style={{ width: `${Math.min(progress, 100)}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className="progress-text">{Math.round(progress)}%</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ProgressBar
|
||||
@@ -1,3 +1,4 @@
|
||||
export { default as LoadingIndicator } from './LoadingIndicator'
|
||||
export { default as ProgressBar } from './ProgressBar'
|
||||
|
||||
export type { LoadingSize, LoadingType } from './LoadingIndicator'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { getVersion } from '@tauri-apps/api/app'
|
||||
import {
|
||||
Dialog,
|
||||
DialogHeader,
|
||||
@@ -19,11 +20,28 @@ interface SettingsDialogProps {
|
||||
* Contains application settings and configuration options
|
||||
*/
|
||||
const SettingsDialog: React.FC<SettingsDialogProps> = ({ visible, onClose }) => {
|
||||
const [appVersion, setAppVersion] = useState<string>('Loading...')
|
||||
|
||||
useEffect(() => {
|
||||
// Fetch app version when component mounts
|
||||
const fetchVersion = async () => {
|
||||
try {
|
||||
const version = await getVersion()
|
||||
setAppVersion(version)
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch app version:', error)
|
||||
setAppVersion('Unknown')
|
||||
}
|
||||
}
|
||||
|
||||
fetchVersion()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Dialog visible={visible} onClose={onClose} size="medium">
|
||||
<DialogHeader onClose={onClose} hideCloseButton={true}>
|
||||
<div className="settings-header">
|
||||
<Icon name={settings} variant="bold" size="md" />
|
||||
<Icon name={settings} variant="solid" size="md" />
|
||||
<h3>Settings</h3>
|
||||
</div>
|
||||
</DialogHeader>
|
||||
@@ -37,7 +55,7 @@ const SettingsDialog: React.FC<SettingsDialogProps> = ({ visible, onClose }) =>
|
||||
</p>
|
||||
|
||||
<div className="settings-placeholder">
|
||||
<div className="placeholder-icon"> <Icon name={settings} variant="bold" size="xl" /> </div>
|
||||
<div className="placeholder-icon"> <Icon name={settings} variant="solid" size="xl" /> </div>
|
||||
<div className="placeholder-text">
|
||||
<h5>Settings Coming Soon</h5>
|
||||
<p>
|
||||
@@ -59,7 +77,7 @@ const SettingsDialog: React.FC<SettingsDialogProps> = ({ visible, onClose }) =>
|
||||
<div className="app-info">
|
||||
<div className="info-row">
|
||||
<span className="info-label">Version:</span>
|
||||
<span className="info-value">1.0.2</span>
|
||||
<span className="info-value">{appVersion}</span>
|
||||
</div>
|
||||
<div className="info-row">
|
||||
<span className="info-label">Build:</span>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useState, useEffect } from 'react'
|
||||
import { findBestGameImage } from '@/services/ImageService'
|
||||
import { Game } from '@/types'
|
||||
import { ActionButton, ActionType, Button } from '@/components/buttons'
|
||||
import { Icon } from '@/components/icons'
|
||||
|
||||
interface GameItemProps {
|
||||
game: Game
|
||||
@@ -150,10 +151,10 @@ const GameItem = ({ game, onAction, onEdit }: GameItemProps) => {
|
||||
onClick={handleEdit}
|
||||
disabled={!game.cream_installed || !!game.installing}
|
||||
title="Manage DLCs"
|
||||
className="edit-button"
|
||||
>
|
||||
Manage DLCs
|
||||
</Button>
|
||||
className="edit-button settings-icon-button"
|
||||
leftIcon={<Icon name="Settings" variant="solid" size="md" />}
|
||||
iconOnly
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,25 +4,25 @@
|
||||
import React from 'react'
|
||||
|
||||
// Import all icon variants
|
||||
import * as OutlineIcons from './ui/outline'
|
||||
import * as BoldIcons from './ui/bold'
|
||||
import * as StrokeIcons from './ui/stroke'
|
||||
import * as SolidIcons from './ui/solid'
|
||||
import * as BrandIcons from './brands'
|
||||
|
||||
export type IconSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | number
|
||||
export type IconVariant = 'bold' | 'outline' | 'brand' | undefined
|
||||
export type IconName = keyof typeof OutlineIcons | keyof typeof BoldIcons | keyof typeof BrandIcons
|
||||
export type IconVariant = 'solid' | 'stroke' | 'brand' | undefined
|
||||
export type IconName = keyof typeof StrokeIcons | keyof typeof SolidIcons | keyof typeof BrandIcons
|
||||
|
||||
// Sets of icon names by type for determining default variants
|
||||
const BRAND_ICON_NAMES = new Set(Object.keys(BrandIcons))
|
||||
const OUTLINE_ICON_NAMES = new Set(Object.keys(OutlineIcons))
|
||||
const BOLD_ICON_NAMES = new Set(Object.keys(BoldIcons))
|
||||
const STROKE_ICON_NAMES = new Set(Object.keys(StrokeIcons))
|
||||
const SOLID_ICON_NAMES = new Set(Object.keys(SolidIcons))
|
||||
|
||||
export interface IconProps extends React.SVGProps<SVGSVGElement> {
|
||||
/** Name of the icon to render */
|
||||
name: IconName | string
|
||||
/** Size of the icon */
|
||||
size?: IconSize
|
||||
/** Icon variant - bold, outline, or brand */
|
||||
/** Icon variant - solid, stroke, or brand */
|
||||
variant?: IconVariant | string
|
||||
/** Title for accessibility */
|
||||
title?: string
|
||||
@@ -60,26 +60,26 @@ const getIconComponent = (
|
||||
): React.ComponentType<React.SVGProps<SVGSVGElement>> | null => {
|
||||
// Normalize variant to ensure it's a valid IconVariant
|
||||
const normalizedVariant =
|
||||
variant === 'bold' || variant === 'outline' || variant === 'brand'
|
||||
variant === 'solid' || variant === 'stroke' || variant === 'brand'
|
||||
? (variant as IconVariant)
|
||||
: undefined
|
||||
|
||||
// Try to get the icon from the specified variant
|
||||
switch (normalizedVariant) {
|
||||
case 'outline':
|
||||
return OutlineIcons[name as keyof typeof OutlineIcons] || null
|
||||
case 'bold':
|
||||
return BoldIcons[name as keyof typeof BoldIcons] || null
|
||||
case 'stroke':
|
||||
return StrokeIcons[name as keyof typeof StrokeIcons] || null
|
||||
case 'solid':
|
||||
return SolidIcons[name as keyof typeof SolidIcons] || null
|
||||
case 'brand':
|
||||
return BrandIcons[name as keyof typeof BrandIcons] || null
|
||||
default:
|
||||
// If no variant specified, determine best default
|
||||
if (BRAND_ICON_NAMES.has(name)) {
|
||||
return BrandIcons[name as keyof typeof BrandIcons] || null
|
||||
} else if (OUTLINE_ICON_NAMES.has(name)) {
|
||||
return OutlineIcons[name as keyof typeof OutlineIcons] || null
|
||||
} else if (BOLD_ICON_NAMES.has(name)) {
|
||||
return BoldIcons[name as keyof typeof BoldIcons] || null
|
||||
} else if (STROKE_ICON_NAMES.has(name)) {
|
||||
return StrokeIcons[name as keyof typeof StrokeIcons] || null
|
||||
} else if (SOLID_ICON_NAMES.has(name)) {
|
||||
return SolidIcons[name as keyof typeof SolidIcons] || null
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
// BROKEN
|
||||
|
||||
//import React from 'react'
|
||||
//import Icon from './Icon'
|
||||
//import type { IconProps, IconVariant } from './Icon'
|
||||
//
|
||||
//export const createIconComponent = (
|
||||
// name: string,
|
||||
// defaultVariant: IconVariant = 'outline'
|
||||
//): React.FC<Omit<IconProps, 'name'>> => {
|
||||
// const IconComponent: React.FC<Omit<IconProps, 'name'>> = (props) => {
|
||||
// return (
|
||||
// <Icon
|
||||
// name={name}
|
||||
// variant={(props as any).variant ?? defaultVariant}
|
||||
// {...props}
|
||||
// />
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// IconComponent.displayName = `${name}Icon`
|
||||
// return IconComponent
|
||||
//}
|
||||
//
|
||||
@@ -3,11 +3,11 @@ export { default as Icon } from './Icon'
|
||||
export type { IconProps, IconSize, IconVariant, IconName } from './Icon'
|
||||
|
||||
// Re-export all icons by category for convenience
|
||||
import * as OutlineIcons from './ui/outline'
|
||||
import * as BoldIcons from './ui/bold'
|
||||
import * as StrokeIcons from './ui/stroke'
|
||||
import * as SolidIcons from './ui/solid'
|
||||
import * as BrandIcons from './brands'
|
||||
|
||||
export { OutlineIcons, BoldIcons, BrandIcons }
|
||||
export { StrokeIcons, SolidIcons, BrandIcons }
|
||||
|
||||
// Export individual icon names as constants
|
||||
// UI icons
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 5v14m6-8l-6-6m-6 6l6-6"/></svg>
|
||||
|
Before Width: | Height: | Size: 225 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="m9.55 15.15l8.475-8.475q.3-.3.7-.3t.7.3t.3.713t-.3.712l-9.175 9.2q-.3.3-.7.3t-.7-.3L4.55 13q-.3-.3-.288-.712t.313-.713t.713-.3t.712.3z"/></svg>
|
||||
|
Before Width: | Height: | Size: 255 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="m12 13.4l-4.9 4.9q-.275.275-.7.275t-.7-.275t-.275-.7t.275-.7l4.9-4.9l-4.9-4.9q-.275-.275-.275-.7t.275-.7t.7-.275t.7.275l4.9 4.9l4.9-4.9q.275-.275.7-.275t.7.275t.275.7t-.275.7L13.4 12l4.9 4.9q.275.275.275.7t-.275.7t-.7.275t-.7-.275z"/></svg>
|
||||
|
Before Width: | Height: | Size: 352 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M4.725 20q-1.5 0-2.562-1.075t-1.113-2.6q0-.225.025-.45t.075-.45l2.1-8.4q.35-1.35 1.425-2.187T7.125 4h9.75q1.375 0 2.45.838t1.425 2.187l2.1 8.4q.05.225.088.463t.037.462q0 1.525-1.088 2.588T19.276 20q-1.05 0-1.95-.55t-1.35-1.5l-.7-1.45q-.125-.25-.375-.375T14.375 16h-4.75q-.275 0-.525.125t-.375.375l-.7 1.45q-.45.95-1.35 1.5t-1.95.55m8.775-9q.425 0 .713-.288T14.5 10t-.288-.712T13.5 9t-.712.288T12.5 10t.288.713t.712.287m2-2q.425 0 .713-.288T16.5 8t-.288-.712T15.5 7t-.712.288T14.5 8t.288.713T15.5 9m0 4q.425 0 .713-.288T16.5 12t-.288-.712T15.5 11t-.712.288T14.5 12t.288.713t.712.287m2-2q.425 0 .713-.288T18.5 10t-.288-.712T17.5 9t-.712.288T16.5 10t.288.713t.712.287m-9 1.5q.325 0 .538-.213t.212-.537v-1h1q.325 0 .538-.213T11 10t-.213-.537t-.537-.213h-1v-1q0-.325-.213-.537T8.5 7.5t-.537.213t-.213.537v1h-1q-.325 0-.537.213T6 10t.213.538t.537.212h1v1q0 .325.213.538t.537.212"/></svg>
|
||||
|
Before Width: | Height: | Size: 993 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M15.24 2h-3.894c-1.764 0-3.162 0-4.255.148c-1.126.152-2.037.472-2.755 1.193c-.719.721-1.038 1.636-1.189 2.766C3 7.205 3 8.608 3 10.379v5.838c0 1.508.92 2.8 2.227 3.342c-.067-.91-.067-2.185-.067-3.247v-5.01c0-1.281 0-2.386.118-3.27c.127-.948.413-1.856 1.147-2.593s1.639-1.024 2.583-1.152c.88-.118 1.98-.118 3.257-.118h3.07c1.276 0 2.374 0 3.255.118A3.6 3.6 0 0 0 15.24 2"/><path fill="currentColor" d="M6.6 11.397c0-2.726 0-4.089.844-4.936c.843-.847 2.2-.847 4.916-.847h2.88c2.715 0 4.073 0 4.917.847S21 8.671 21 11.397v4.82c0 2.726 0 4.089-.843 4.936c-.844.847-2.202.847-4.917.847h-2.88c-2.715 0-4.073 0-4.916-.847c-.844-.847-.844-2.21-.844-4.936z"/></svg>
|
||||
|
Before Width: | Height: | Size: 768 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M9.2 8.25h5.6L12.15 3h-.3zm2.05 11.85V9.75H2.625zm1.5 0l8.625-10.35H12.75zm3.7-11.85h5.175L19.55 4.1q-.275-.5-.737-.8T17.775 3H13.85zm-14.075 0H7.55L10.15 3H6.225q-.575 0-1.037.3t-.738.8z"/></svg>
|
||||
|
Before Width: | Height: | Size: 308 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M21.086 8.804v2.21a.75.75 0 1 1-1.5 0v-2.21a2 2 0 0 0-.13-.76l-7.3 4.38v8.19q.172-.051.33-.14l2.53-1.4a.75.75 0 0 1 1 .29a.75.75 0 0 1-.3 1l-2.52 1.4a3.72 3.72 0 0 1-3.62 0l-6-3.3a3.79 3.79 0 0 1-1.92-3.27v-6.39c0-.669.18-1.325.52-1.9q.086-.155.2-.29l.12-.15a3.45 3.45 0 0 1 1.08-.93l6-3.31a3.81 3.81 0 0 1 3.62 0l6 3.31c.42.231.788.548 1.08.93a1 1 0 0 1 .12.15q.113.135.2.29a3.64 3.64 0 0 1 .49 1.9"/><path fill="currentColor" d="m22.196 17.624l-2 2a1.2 1.2 0 0 1-.39.26a1.1 1.1 0 0 1-.46.1q-.239 0-.46-.09a1.3 1.3 0 0 1-.4-.27l-2-2a.74.74 0 0 1 0-1.06a.75.75 0 0 1 1.06 0l1 1v-3.36a.75.75 0 0 1 1.5 0v3.38l1-1a.75.75 0 0 1 1.079-.02a.75.75 0 0 1-.02 1.08z"/></svg>
|
||||
|
Before Width: | Height: | Size: 778 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M12 15.575q-.2 0-.375-.062T11.3 15.3l-3.6-3.6q-.3-.3-.288-.7t.288-.7q.3-.3.713-.312t.712.287L11 12.15V5q0-.425.288-.712T12 4t.713.288T13 5v7.15l1.875-1.875q.3-.3.713-.288t.712.313q.275.3.288.7t-.288.7l-3.6 3.6q-.15.15-.325.213t-.375.062M6 20q-.825 0-1.412-.587T4 18v-2q0-.425.288-.712T5 15t.713.288T6 16v2h12v-2q0-.425.288-.712T19 15t.713.288T20 16v2q0 .825-.587 1.413T18 20z"/></svg>
|
||||
|
Before Width: | Height: | Size: 496 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M4 21q-.425 0-.712-.288T3 20v-2.425q0-.4.15-.763t.425-.637L16.2 3.575q.3-.275.663-.425t.762-.15t.775.15t.65.45L20.425 5q.3.275.437.65T21 6.4q0 .4-.138.763t-.437.662l-12.6 12.6q-.275.275-.638.425t-.762.15zM17.6 7.8L19 6.4L17.6 5l-1.4 1.4z"/></svg>
|
||||
|
Before Width: | Height: | Size: 358 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M12 17q.425 0 .713-.288T13 16t-.288-.712T12 15t-.712.288T11 16t.288.713T12 17m0-4q.425 0 .713-.288T13 12V8q0-.425-.288-.712T12 7t-.712.288T11 8v4q0 .425.288.713T12 13m0 9q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22"/></svg>
|
||||
|
Before Width: | Height: | Size: 453 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M12 17q.425 0 .713-.288T13 16v-4q0-.425-.288-.712T12 11t-.712.288T11 12v4q0 .425.288.713T12 17m0-8q.425 0 .713-.288T13 8t-.288-.712T12 7t-.712.288T11 8t.288.713T12 9m0 13q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22"/></svg>
|
||||
|
Before Width: | Height: | Size: 453 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M4.025 14.85q-.4-.3-.387-.787t.412-.788q.275-.2.6-.2t.6.2L12 18.5l6.75-5.225q.275-.2.6-.2t.6.2q.4.3.413.787t-.388.788l-6.75 5.25q-.55.425-1.225.425t-1.225-.425zm6.75.2l-5.75-4.475Q4.25 9.975 4.25 9t.775-1.575l5.75-4.475q.55-.425 1.225-.425t1.225.425l5.75 4.475q.775.6.775 1.575t-.775 1.575l-5.75 4.475q-.55.425-1.225.425t-1.225-.425"/></svg>
|
||||
|
Before Width: | Height: | Size: 453 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M12 20q-3.35 0-5.675-2.325T4 12t2.325-5.675T12 4q1.725 0 3.3.712T18 6.75V5q0-.425.288-.712T19 4t.713.288T20 5v5q0 .425-.288.713T19 11h-5q-.425 0-.712-.288T13 10t.288-.712T14 9h3.2q-.8-1.4-2.187-2.2T12 6Q9.5 6 7.75 7.75T6 12t1.75 4.25T12 18q1.7 0 3.113-.862t2.187-2.313q.2-.35.563-.487t.737-.013q.4.125.575.525t-.025.75q-1.025 2-2.925 3.2T12 20"/></svg>
|
||||
|
Before Width: | Height: | Size: 464 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M9.5 16q-2.725 0-4.612-1.888T3 9.5t1.888-4.612T9.5 3t4.613 1.888T16 9.5q0 1.1-.35 2.075T14.7 13.3l5.6 5.6q.275.275.275.7t-.275.7t-.7.275t-.7-.275l-5.6-5.6q-.75.6-1.725.95T9.5 16m0-2q1.875 0 3.188-1.312T14 9.5t-1.312-3.187T9.5 5T6.313 6.313T5 9.5t1.313 3.188T9.5 14"/></svg>
|
||||
|
Before Width: | Height: | Size: 385 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M10.825 22q-.675 0-1.162-.45t-.588-1.1L8.85 18.8q-.325-.125-.612-.3t-.563-.375l-1.55.65q-.625.275-1.25.05t-.975-.8l-1.175-2.05q-.35-.575-.2-1.225t.675-1.075l1.325-1Q4.5 12.5 4.5 12.337v-.675q0-.162.025-.337l-1.325-1Q2.675 9.9 2.525 9.25t.2-1.225L3.9 5.975q.35-.575.975-.8t1.25.05l1.55.65q.275-.2.575-.375t.6-.3l.225-1.65q.1-.65.588-1.1T10.825 2h2.35q.675 0 1.163.45t.587 1.1l.225 1.65q.325.125.613.3t.562.375l1.55-.65q.625-.275 1.25-.05t.975.8l1.175 2.05q.35.575.2 1.225t-.675 1.075l-1.325 1q.025.175.025.338v.674q0 .163-.05.338l1.325 1q.525.425.675 1.075t-.2 1.225l-1.2 2.05q-.35.575-.975.8t-1.25-.05l-1.5-.65q-.275.2-.575.375t-.6.3l-.225 1.65q-.1.65-.587 1.1t-1.163.45zm1.225-6.5q1.45 0 2.475-1.025T15.55 12t-1.025-2.475T12.05 8.5q-1.475 0-2.488 1.025T8.55 12t1.013 2.475T12.05 15.5"/></svg>
|
||||
|
Before Width: | Height: | Size: 905 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M20 6a1 1 0 0 1 .117 1.993L20 8h-.081L19 19a3 3 0 0 1-2.824 2.995L16 22H8c-1.598 0-2.904-1.249-2.992-2.75l-.005-.167L4.08 8H4a1 1 0 0 1-.117-1.993L4 6zm-6-4a2 2 0 0 1 2 2a1 1 0 0 1-1.993.117L14 4h-4l-.007.117A1 1 0 0 1 8 4a2 2 0 0 1 1.85-1.995L10 2z"/></svg>
|
||||
|
Before Width: | Height: | Size: 370 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M2.725 21q-.275 0-.5-.137t-.35-.363t-.137-.488t.137-.512l9.25-16q.15-.25.388-.375T12 3t.488.125t.387.375l9.25 16q.15.25.138.513t-.138.487t-.35.363t-.5.137zM12 18q.425 0 .713-.288T13 17t-.288-.712T12 16t-.712.288T11 17t.288.713T12 18m0-3q.425 0 .713-.288T13 14v-3q0-.425-.288-.712T12 10t-.712.288T11 11v3q0 .425.288.713T12 15"/></svg>
|
||||
|
Before Width: | Height: | Size: 445 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" fill-rule="evenodd" d="M11.47 3.47a.75.75 0 0 1 1.06 0l6 6a.75.75 0 1 1-1.06 1.06l-4.72-4.72V20a.75.75 0 0 1-1.5 0V5.81l-4.72 4.72a.75.75 0 1 1-1.06-1.06z" clip-rule="evenodd"/></svg>
|
||||
|
Before Width: | Height: | Size: 292 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="m9.55 15.88l8.802-8.801q.146-.146.344-.156t.363.156t.166.357t-.165.356l-8.944 8.95q-.243.243-.566.243t-.566-.243l-4.05-4.05q-.146-.146-.152-.347t.158-.366t.357-.165t.357.165z"/></svg>
|
||||
|
Before Width: | Height: | Size: 295 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="m12 12.708l-5.246 5.246q-.14.14-.344.15t-.364-.15t-.16-.354t.16-.354L11.292 12L6.046 6.754q-.14-.14-.15-.344t.15-.364t.354-.16t.354.16L12 11.292l5.246-5.246q.14-.14.345-.15q.203-.01.363.15t.16.354t-.16.354L12.708 12l5.246 5.246q.14.14.15.345q.01.203-.15.363t-.354.16t-.354-.16z"/></svg>
|
||||
|
Before Width: | Height: | Size: 398 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M4.725 20q-1.5 0-2.562-1.075t-1.113-2.6q0-.225.025-.45t.075-.45l2.1-8.4q.35-1.35 1.425-2.187T7.125 4h9.75q1.375 0 2.45.838t1.425 2.187l2.1 8.4q.05.225.088.463t.037.462q0 1.525-1.088 2.588T19.276 20q-1.05 0-1.95-.55t-1.35-1.5l-.7-1.45q-.125-.25-.375-.375T14.375 16h-4.75q-.275 0-.525.125t-.375.375l-.7 1.45q-.45.95-1.35 1.5t-1.95.55m.075-2q.475 0 .863-.25t.587-.675l.7-1.425q.375-.775 1.1-1.213T9.625 14h4.75q.85 0 1.575.45t1.125 1.2l.7 1.425q.2.425.588.675t.862.25q.7 0 1.2-.462t.525-1.163q0 .025-.05-.475l-2.1-8.375q-.175-.675-.7-1.1T16.875 6h-9.75q-.7 0-1.237.425t-.688 1.1L3.1 15.9q-.05.15-.05.45q0 .7.513 1.175T4.8 18m8.7-7q.425 0 .713-.287T14.5 10t-.288-.712T13.5 9t-.712.288T12.5 10t.288.713t.712.287m2-2q.425 0 .713-.288T16.5 8t-.288-.712T15.5 7t-.712.288T14.5 8t.288.713T15.5 9m0 4q.425 0 .713-.288T16.5 12t-.288-.712T15.5 11t-.712.288T14.5 12t.288.713t.712.287m2-2q.425 0 .713-.288T18.5 10t-.288-.712T17.5 9t-.712.288T16.5 10t.288.713t.712.287m-9 1.5q.325 0 .538-.213t.212-.537v-1h1q.325 0 .538-.213T11 10t-.213-.537t-.537-.213h-1v-1q0-.325-.213-.537T8.5 7.5t-.537.213t-.213.537v1h-1q-.325 0-.537.213T6 10t.213.538t.537.212h1v1q0 .325.213.538t.537.212M12 12"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" fill-rule="evenodd" d="M15 1.25h-4.056c-1.838 0-3.294 0-4.433.153c-1.172.158-2.121.49-2.87 1.238c-.748.749-1.08 1.698-1.238 2.87c-.153 1.14-.153 2.595-.153 4.433V16a3.75 3.75 0 0 0 3.166 3.705c.137.764.402 1.416.932 1.947c.602.602 1.36.86 2.26.982c.867.116 1.97.116 3.337.116h3.11c1.367 0 2.47 0 3.337-.116c.9-.122 1.658-.38 2.26-.982s.86-1.36.982-2.26c.116-.867.116-1.97.116-3.337v-5.11c0-1.367 0-2.47-.116-3.337c-.122-.9-.38-1.658-.982-2.26c-.531-.53-1.183-.795-1.947-.932A3.75 3.75 0 0 0 15 1.25m2.13 3.021A2.25 2.25 0 0 0 15 2.75h-4c-1.907 0-3.261.002-4.29.14c-1.005.135-1.585.389-2.008.812S4.025 4.705 3.89 5.71c-.138 1.029-.14 2.383-.14 4.29v6a2.25 2.25 0 0 0 1.521 2.13c-.021-.61-.021-1.3-.021-2.075v-5.11c0-1.367 0-2.47.117-3.337c.12-.9.38-1.658.981-2.26c.602-.602 1.36-.86 2.26-.981c.867-.117 1.97-.117 3.337-.117h3.11c.775 0 1.464 0 2.074.021M7.408 6.41c.277-.277.665-.457 1.4-.556c.754-.101 1.756-.103 3.191-.103h3c1.435 0 2.436.002 3.192.103c.734.099 1.122.28 1.399.556c.277.277.457.665.556 1.4c.101.754.103 1.756.103 3.191v5c0 1.435-.002 2.436-.103 3.192c-.099.734-.28 1.122-.556 1.399c-.277.277-.665.457-1.4.556c-.755.101-1.756.103-3.191.103h-3c-1.435 0-2.437-.002-3.192-.103c-.734-.099-1.122-.28-1.399-.556c-.277-.277-.457-.665-.556-1.4c-.101-.755-.103-1.756-.103-3.191v-5c0-1.435.002-2.437.103-3.192c.099-.734.28-1.122.556-1.399" clip-rule="evenodd"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M12 19.875q-.425 0-.825-.187t-.7-.538L2.825 10q-.225-.275-.337-.6t-.113-.675q0-.225.038-.462t.162-.438L4.45 4.1q.275-.5.738-.8T6.225 3h11.55q.575 0 1.038.3t.737.8l1.875 3.725q.125.2.163.437t.037.463q0 .35-.112.675t-.338.6l-7.65 9.15q-.3.35-.7.538t-.825.187M9.625 8h4.75l-1.5-3h-1.75zM11 16.675V10H5.45zm2 0L18.55 10H13zM16.6 8h2.65l-1.5-3H15.1zM4.75 8H7.4l1.5-3H6.25z"/></svg>
|
||||
|
Before Width: | Height: | Size: 488 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-width="1.5"><path stroke-linejoin="round" d="M20.935 11.009V8.793a2.98 2.98 0 0 0-1.529-2.61l-5.957-3.307a2.98 2.98 0 0 0-2.898 0L4.594 6.182a2.98 2.98 0 0 0-1.529 2.611v6.414a2.98 2.98 0 0 0 1.529 2.61l5.957 3.307a2.98 2.98 0 0 0 2.898 0l2.522-1.4"/><path stroke-linejoin="round" d="M20.33 6.996L12 12L3.67 6.996M12 21.49V12"/><path stroke-miterlimit="10" d="M19.97 19.245v-5"/><path stroke-linejoin="round" d="m17.676 17.14l1.968 1.968a.46.46 0 0 0 .65 0l1.968-1.968"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 631 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M12 15.248q-.161 0-.298-.053t-.267-.184l-2.62-2.619q-.146-.146-.152-.344t.152-.363q.166-.166.357-.169q.192-.003.357.163L11.5 13.65V5.5q0-.213.143-.357T12 5t.357.143t.143.357v8.15l1.971-1.971q.146-.146.347-.153t.366.159q.16.165.163.354t-.162.353l-2.62 2.62q-.13.13-.267.183q-.136.053-.298.053M6.616 19q-.691 0-1.153-.462T5 17.384v-1.923q0-.213.143-.356t.357-.144t.357.144t.143.356v1.923q0 .231.192.424t.423.192h10.77q.23 0 .423-.192t.192-.424v-1.923q0-.213.143-.356t.357-.144t.357.144t.143.356v1.923q0 .691-.462 1.153T17.384 19z"/></svg>
|
||||
|
Before Width: | Height: | Size: 648 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M5 19h1.425L16.2 9.225L14.775 7.8L5 17.575zm-1 2q-.425 0-.712-.288T3 20v-2.425q0-.4.15-.763t.425-.637L16.2 3.575q.3-.275.663-.425t.762-.15t.775.15t.65.45L20.425 5q.3.275.437.65T21 6.4q0 .4-.138.763t-.437.662l-12.6 12.6q-.275.275-.638.425t-.762.15zM19 6.4L17.6 5zm-3.525 2.125l-.7-.725L16.2 9.225z"/></svg>
|
||||
|
Before Width: | Height: | Size: 417 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M12 17q.425 0 .713-.288T13 16t-.288-.712T12 15t-.712.288T11 16t.288.713T12 17m0-4q.425 0 .713-.288T13 12V8q0-.425-.288-.712T12 7t-.712.288T11 8v4q0 .425.288.713T12 13m0 9q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4T6.325 6.325T4 12t2.325 5.675T12 20m0-8"/></svg>
|
||||
|
Before Width: | Height: | Size: 539 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M12 17q.425 0 .713-.288T13 16v-4q0-.425-.288-.712T12 11t-.712.288T11 12v4q0 .425.288.713T12 17m0-8q.425 0 .713-.288T13 8t-.288-.712T12 7t-.712.288T11 8t.288.713T12 9m0 13q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4T6.325 6.325T4 12t2.325 5.675T12 20m0-8"/></svg>
|
||||
|
Before Width: | Height: | Size: 539 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M4.025 14.85q-.4-.3-.387-.787t.412-.788q.275-.2.6-.2t.6.2L12 18.5l6.75-5.225q.275-.2.6-.2t.6.2q.4.3.413.787t-.388.788l-6.75 5.25q-.55.425-1.225.425t-1.225-.425zm6.75.2l-5.75-4.475Q4.25 9.975 4.25 9t.775-1.575l5.75-4.475q.55-.425 1.225-.425t1.225.425l5.75 4.475q.775.6.775 1.575t-.775 1.575l-5.75 4.475q-.55.425-1.225.425t-1.225-.425M12 13.45L17.75 9L12 4.55L6.25 9zM12 9"/></svg>
|
||||
|
Before Width: | Height: | Size: 491 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M12.077 19q-2.931 0-4.966-2.033q-2.034-2.034-2.034-4.964t2.034-4.966T12.077 5q1.783 0 3.339.847q1.555.847 2.507 2.365V5.5q0-.213.144-.356T18.424 5t.356.144t.143.356v3.923q0 .343-.232.576t-.576.232h-3.923q-.212 0-.356-.144t-.144-.357t.144-.356t.356-.143h3.2q-.78-1.496-2.197-2.364Q13.78 6 12.077 6q-2.5 0-4.25 1.75T6.077 12t1.75 4.25t4.25 1.75q1.787 0 3.271-.968q1.485-.969 2.202-2.573q.085-.196.274-.275q.19-.08.388-.013q.211.067.28.275t-.015.404q-.833 1.885-2.56 3.017T12.077 19"/></svg>
|
||||
|
Before Width: | Height: | Size: 600 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M9.539 15.23q-2.398 0-4.065-1.666Q3.808 11.899 3.808 9.5t1.666-4.065T9.539 3.77t4.064 1.666T15.269 9.5q0 1.042-.369 2.017t-.97 1.668l5.909 5.907q.14.14.15.345q.009.203-.15.363q-.16.16-.354.16t-.354-.16l-5.908-5.908q-.75.639-1.725.989t-1.96.35m0-1q1.99 0 3.361-1.37q1.37-1.37 1.37-3.361T12.9 6.14T9.54 4.77q-1.991 0-3.361 1.37T4.808 9.5t1.37 3.36t3.36 1.37"/></svg>
|
||||
|
Before Width: | Height: | Size: 476 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M10.825 22q-.675 0-1.162-.45t-.588-1.1L8.85 18.8q-.325-.125-.612-.3t-.563-.375l-1.55.65q-.625.275-1.25.05t-.975-.8l-1.175-2.05q-.35-.575-.2-1.225t.675-1.075l1.325-1Q4.5 12.5 4.5 12.337v-.675q0-.162.025-.337l-1.325-1Q2.675 9.9 2.525 9.25t.2-1.225L3.9 5.975q.35-.575.975-.8t1.25.05l1.55.65q.275-.2.575-.375t.6-.3l.225-1.65q.1-.65.588-1.1T10.825 2h2.35q.675 0 1.163.45t.587 1.1l.225 1.65q.325.125.613.3t.562.375l1.55-.65q.625-.275 1.25-.05t.975.8l1.175 2.05q.35.575.2 1.225t-.675 1.075l-1.325 1q.025.175.025.338v.674q0 .163-.05.338l1.325 1q.525.425.675 1.075t-.2 1.225l-1.2 2.05q-.35.575-.975.8t-1.25-.05l-1.5-.65q-.275.2-.575.375t-.6.3l-.225 1.65q-.1.65-.587 1.1t-1.163.45zM11 20h1.975l.35-2.65q.775-.2 1.438-.587t1.212-.938l2.475 1.025l.975-1.7l-2.15-1.625q.125-.35.175-.737T17.5 12t-.05-.787t-.175-.738l2.15-1.625l-.975-1.7l-2.475 1.05q-.55-.575-1.212-.962t-1.438-.588L13 4h-1.975l-.35 2.65q-.775.2-1.437.588t-1.213.937L5.55 7.15l-.975 1.7l2.15 1.6q-.125.375-.175.75t-.05.8q0 .4.05.775t.175.75l-2.15 1.625l.975 1.7l2.475-1.05q.55.575 1.213.963t1.437.587zm1.05-4.5q1.45 0 2.475-1.025T15.55 12t-1.025-2.475T12.05 8.5q-1.475 0-2.487 1.025T8.55 12t1.013 2.475T12.05 15.5M12 12"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7h16m-10 4v6m4-6v6M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2l1-12M9 7V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v3"/></svg>
|
||||
|
Before Width: | Height: | Size: 302 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M2.725 21q-.275 0-.5-.137t-.35-.363t-.137-.488t.137-.512l9.25-16q.15-.25.388-.375T12 3t.488.125t.387.375l9.25 16q.15.25.138.513t-.138.487t-.35.363t-.5.137zm1.725-2h15.1L12 6zM12 18q.425 0 .713-.288T13 17t-.288-.712T12 16t-.712.288T11 17t.288.713T12 18m0-3q.425 0 .713-.288T13 14v-3q0-.425-.288-.712T12 10t-.712.288T11 11v3q0 .425.288.713T12 15m0-2.5"/></svg>
|
||||
|
Before Width: | Height: | Size: 470 B |
3
src/components/icons/ui/solid/arrow-up.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" color="#000000" fill="none">
|
||||
<path d="M12.9999 19.002C12.9999 19.5542 12.5522 20.002 11.9999 20.002C11.4476 20.0019 10.9999 19.5542 10.9999 19.002V6.74121C10.4264 7.25563 9.78116 7.94409 9.16198 8.65723C8.52607 9.38966 7.93619 10.1256 7.50378 10.6797C7.2881 10.9561 6.86131 11.5134 6.74011 11.6738C6.39996 12.0494 5.82401 12.1144 5.4071 11.8076C4.9626 11.4802 4.86709 10.8538 5.19421 10.4092C5.32082 10.2415 5.70349 9.73516 5.92663 9.44922C6.37213 8.87836 6.9859 8.11413 7.65222 7.34668C8.31419 6.58424 9.04804 5.79591 9.73327 5.19043C10.0746 4.8888 10.4273 4.61078 10.7714 4.40332C11.0881 4.2124 11.5238 4.00198 11.9999 4.00195L12.1766 4.01172C12.5829 4.05466 12.9504 4.23637 13.2274 4.40332C13.5716 4.61079 13.925 4.88871 14.2665 5.19043C14.9517 5.7959 15.6856 6.58427 16.3475 7.34668C17.0138 8.1141 17.6276 8.87836 18.0731 9.44922C18.2962 9.73513 18.6789 10.2415 18.8055 10.4092C19.1327 10.8538 19.0372 11.4792 18.5926 11.8066C18.1757 12.1137 17.5999 12.0496 17.2596 11.6738C17.2596 11.6738 17.0692 11.4269 17.0087 11.3467C16.8875 11.1862 16.7117 10.9561 16.496 10.6797C16.0635 10.1256 15.4737 9.38965 14.8378 8.65723C14.2185 7.94406 13.5734 7.25564 12.9999 6.74121V19.002Z" fill="currentColor" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
3
src/components/icons/ui/solid/check.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" color="#000000" fill="none">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.6905 5.77665C20.09 6.15799 20.1047 6.79098 19.7234 7.19048L9.22336 18.1905C9.03745 18.3852 8.78086 18.4968 8.51163 18.4999C8.2424 18.5031 7.98328 18.3975 7.79289 18.2071L4.29289 14.7071C3.90237 14.3166 3.90237 13.6834 4.29289 13.2929C4.68342 12.9024 5.31658 12.9024 5.70711 13.2929L8.48336 16.0692L18.2766 5.80953C18.658 5.41003 19.291 5.39531 19.6905 5.77665Z" fill="currentColor" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 560 B |
3
src/components/icons/ui/solid/close.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" color="#000000" fill="none">
|
||||
<path d="M17.293 5.29295C17.6835 4.90243 18.3165 4.90243 18.707 5.29295C19.0976 5.68348 19.0976 6.31649 18.707 6.70702L13.4131 12L18.7061 17.293L18.7754 17.3691C19.0954 17.7619 19.0721 18.341 18.7061 18.707C18.3399 19.0731 17.7609 19.0958 17.3682 18.7754L17.292 18.707L11.999 13.414L6.70802 18.706C6.3175 19.0966 5.68449 19.0965 5.29396 18.706C4.90344 18.3155 4.90344 17.6825 5.29396 17.292L10.585 12L5.29298 6.70799L5.22462 6.63182C4.90423 6.23907 4.92691 5.66007 5.29298 5.29393C5.65897 4.92794 6.23811 4.9046 6.63087 5.22459L6.70705 5.29393L11.999 10.5859L17.293 5.29295Z" fill="currentColor" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 721 B |
4
src/components/icons/ui/solid/controller.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" color="#000000" fill="none">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.9319 3.87827C8.06823 4.41346 7.74489 4.95784 7.2097 5.09417L5.24685 5.59417C4.71165 5.7305 4.16728 5.40716 4.03095 4.87197C3.89461 4.33677 4.21796 3.79239 4.75315 3.65606L6.716 3.15606C7.25119 3.01973 7.79557 3.34308 7.9319 3.87827ZM16.0299 3.88258C16.1638 3.34679 16.7067 3.02103 17.2425 3.15498L19.2425 3.65498C19.7783 3.78892 20.1041 4.33186 19.9701 4.86765C19.8362 5.40345 19.2933 5.72921 18.7575 5.59526L16.7575 5.09526C16.2217 4.96131 15.8959 4.41838 16.0299 3.88258Z" fill="currentColor" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.92509 6.17612C9.34102 5.1078 14.659 5.1078 19.0749 6.17612C20.1031 6.42487 20.8958 7.16741 21.2589 8.13694C21.8432 9.697 22.5131 12.3264 22.7403 15.8863C22.9054 18.4745 20.9807 19.8307 19.2803 20.6872C18.8223 20.9179 18.3491 20.9286 17.9171 20.7592C17.5185 20.603 17.2028 20.3134 16.96 20.025C16.4777 19.4522 16.1003 18.6588 15.8299 18.0493C15.6563 17.6579 15.2779 17.4105 14.8281 17.4105H9.17196C8.72218 17.4105 8.34378 17.6579 8.17012 18.0493C7.89974 18.6588 7.52233 19.4522 7.04 20.025C6.79722 20.3134 6.48151 20.603 6.08295 20.7592C5.6509 20.9286 5.17774 20.9179 4.7197 20.6872C3.03995 19.8411 1.09341 18.4935 1.25978 15.8863C1.48693 12.3264 2.15683 9.697 2.74109 8.13694C3.10419 7.16741 3.89689 6.42487 4.92509 6.17612ZM9.70691 9.41777C10.0974 9.8083 10.0974 10.4415 9.70691 10.832L8.91401 11.6249L9.70691 12.4178C10.0974 12.8083 10.0974 13.4415 9.70691 13.832C9.31638 14.2225 8.68322 14.2225 8.2927 13.832L7.4998 13.0391L6.70691 13.832C6.31638 14.2225 5.68322 14.2225 5.2927 13.832C4.90217 13.4415 4.90217 12.8083 5.2927 12.4178L6.08559 11.6249L5.2927 10.832C4.90217 10.4415 4.90217 9.8083 5.2927 9.41777C5.68322 9.02725 6.31638 9.02725 6.70691 9.41777L7.4998 10.2107L8.29269 9.41777C8.68322 9.02725 9.31638 9.02725 9.70691 9.41777ZM15.9971 11.1249H15.9881C15.4358 11.1249 14.9881 10.6772 14.9881 10.1249C14.9881 9.57259 15.4358 9.12488 15.9881 9.12488H15.9971C16.5493 9.12488 16.9971 9.57259 16.9971 10.1249C16.9971 10.6772 16.5493 11.1249 15.9971 11.1249ZM16.9881 13.1249C16.9881 12.5726 17.4358 12.1249 17.9881 12.1249H17.9971C18.5493 12.1249 18.9971 12.5726 18.9971 13.1249C18.9971 13.6772 18.5493 14.1249 17.9971 14.1249H17.9881C17.4358 14.1249 16.9881 13.6772 16.9881 13.1249Z" fill="currentColor" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
4
src/components/icons/ui/solid/copy.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" color="#000000" fill="none">
|
||||
<path d="M6.74994 14.8569C6.74985 13.5627 6.74977 12.3758 6.87984 11.4084C7.02314 10.3425 7.36028 9.21504 8.28763 8.28769C9.21498 7.36034 10.3425 7.0232 11.4083 6.8799C12.3758 6.74983 13.5627 6.74991 14.8569 6.75L17.0931 6.75C17.3891 6.75 17.5371 6.75 17.6261 6.65419C17.7151 6.55838 17.7045 6.4142 17.6832 6.12584C17.6648 5.87546 17.6412 5.63892 17.6111 5.41544C17.4818 4.45589 17.2231 3.6585 16.6717 2.98663C16.4744 2.74612 16.2538 2.52558 16.0133 2.3282C15.3044 1.74638 14.4557 1.49055 13.4247 1.36868C12.4205 1.24998 11.1511 1.24999 9.54887 1.25H9.45103C7.84877 1.24999 6.57941 1.24998 5.57519 1.36868C4.54422 1.49054 3.69552 1.74638 2.98657 2.3282C2.74606 2.52558 2.52552 2.74612 2.32814 2.98663C1.74632 3.69558 1.49048 4.54428 1.36862 5.57525C1.24992 6.57947 1.24993 7.84882 1.24994 9.45108V9.54891C1.24993 11.1512 1.24992 12.4205 1.36862 13.4247C1.49048 14.4557 1.74632 15.3044 2.32814 16.0134C2.52552 16.2539 2.74606 16.4744 2.98657 16.6718C3.65844 17.2232 4.45583 17.4818 5.41538 17.6111C5.63886 17.6412 5.8754 17.6648 6.12578 17.6833C6.41414 17.7045 6.55831 17.7151 6.65413 17.6261C6.74994 17.5371 6.74994 17.3891 6.74994 17.0931V14.8569Z" fill="currentColor" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.5535 9.09108C12.6229 9.62041 12.25 10.1058 11.7207 10.1752C11.0847 10.2586 10.7687 10.3983 10.5613 10.5688C10.3784 10.7193 10.2497 10.9156 10.1643 11.3404C10.0592 11.8638 9.54968 12.2029 9.02627 12.0978C8.50285 11.9927 8.16377 11.4831 8.2689 10.9597C8.41673 10.2237 8.71907 9.58093 9.33313 9.07578C9.93546 8.58028 10.6592 8.36455 11.4693 8.25831C11.9987 8.1889 12.4841 8.56174 12.5535 9.09108ZM18.4463 9.09108C18.5157 8.56174 19.0011 8.1889 19.5304 8.25831C20.3405 8.36455 21.0643 8.58028 21.6666 9.07578C22.2807 9.58093 22.583 10.2237 22.7308 10.9597C22.836 11.4831 22.4969 11.9927 21.9735 12.0978C21.4501 12.2029 20.9405 11.8638 20.8354 11.3404C20.7501 10.9156 20.6213 10.7193 20.4384 10.5688C20.2311 10.3983 19.915 10.2586 19.279 10.1752C18.7497 10.1058 18.3769 9.62041 18.4463 9.09108ZM13.0832 9.21676C13.0832 8.68289 13.516 8.25011 14.0499 8.25011H16.9498C17.4837 8.25011 17.9165 8.68289 17.9165 9.21676C17.9165 9.75063 17.4837 10.1834 16.9498 10.1834H14.0499C13.516 10.1834 13.0832 9.75063 13.0832 9.21676ZM9.21663 13.0834C9.75049 13.0834 10.1833 13.5162 10.1833 14.05V16.95C10.1833 17.4838 9.75049 17.9166 9.21663 17.9166C8.68276 17.9166 8.24997 17.4838 8.24997 16.95V14.05C8.24997 13.5162 8.68276 13.0834 9.21663 13.0834ZM21.7831 13.0834C22.317 13.0834 22.7498 13.5162 22.7498 14.05V16.95C22.7498 17.4838 22.317 17.9166 21.7831 17.9166C21.2492 17.9166 20.8165 17.4838 20.8165 16.95V14.05C20.8165 13.5162 21.2492 13.0834 21.7831 13.0834ZM9.02627 18.9022C9.54968 18.7971 10.0592 19.1362 10.1643 19.6596C10.2497 20.0844 10.3784 20.2807 10.5613 20.4312C10.7687 20.6017 11.0847 20.7414 11.7207 20.8248C12.25 20.8942 12.6229 21.3796 12.5535 21.9089C12.4841 22.4383 11.9987 22.8111 11.4693 22.7417C10.6592 22.6355 9.93546 22.4197 9.33313 21.9242C8.71906 21.4191 8.41673 20.7763 8.2689 20.0403C8.16377 19.5169 8.50285 19.0073 9.02627 18.9022ZM21.9735 18.9022C22.4969 19.0073 22.836 19.5169 22.7308 20.0403C22.583 20.7763 22.2807 21.4191 21.6666 21.9242C21.0643 22.4197 20.3405 22.6355 19.5304 22.7417C19.0011 22.8111 18.5157 22.4383 18.4463 21.9089C18.3769 21.3796 18.7497 20.8942 19.279 20.8248C19.915 20.7414 20.2311 20.6017 20.4384 20.4312C20.6213 20.2807 20.7501 20.0844 20.8354 19.6596C20.9405 19.1362 21.45 18.7971 21.9735 18.9022ZM13.0832 21.7832C13.0832 21.2494 13.516 20.8166 14.0499 20.8166H16.9498C17.4837 20.8166 17.9165 21.2494 17.9165 21.7832C17.9165 22.3171 17.4837 22.7499 16.9498 22.7499H14.0499C13.516 22.7499 13.0832 22.3171 13.0832 21.7832Z" fill="currentColor" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
3
src/components/icons/ui/solid/diamond.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" color="#000000" fill="none">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.32289 2.25003L9.23265 2.25001C8.47304 2.24984 7.96465 2.24972 7.47672 2.36883C7.25578 2.42277 7.03998 2.49476 6.83189 2.58414C6.37 2.78253 5.97535 3.08651 5.39532 3.53329L5.32472 3.58766L5.29273 3.61228C4.24651 4.41768 3.41746 5.0559 2.80797 5.62799C2.1851 6.21262 1.72591 6.7865 1.48506 7.48693C1.28734 8.06193 1.21295 8.66844 1.26722 9.27182C1.33356 10.0094 1.64582 10.6679 2.11451 11.3677C2.57202 12.0509 3.22764 12.8427 4.05236 13.8386L4.05239 13.8387L4.05242 13.8387L8.11023 18.7392L8.11025 18.7392L8.11026 18.7392C8.84673 19.6287 9.45218 20.3599 10.0079 20.8609C10.5914 21.3869 11.2168 21.75 12 21.75C12.7832 21.75 13.4086 21.3869 13.9921 20.8609C14.5478 20.3599 15.1533 19.6287 15.8898 18.7392L19.9476 13.8386C20.7724 12.8427 21.428 12.0509 21.8855 11.3677C22.3542 10.6679 22.6664 10.0094 22.7328 9.27182C22.7871 8.66844 22.7127 8.06193 22.5149 7.48693C22.2741 6.7865 21.8149 6.21262 21.192 5.62799C20.5825 5.05591 19.7535 4.41769 18.7073 3.6123L18.7072 3.61226L18.6753 3.58766L18.6047 3.53329C18.0246 3.08651 17.63 2.78253 17.1681 2.58414C16.96 2.49476 16.7442 2.42277 16.5233 2.36883C16.0353 2.24972 15.527 2.24984 14.7673 2.25001L14.6771 2.25003H9.32289ZM10 7.75C9.58579 7.75 9.25 8.08579 9.25 8.5C9.25 8.91421 9.58579 9.25 10 9.25H14C14.4142 9.25 14.75 8.91421 14.75 8.5C14.75 8.08579 14.4142 7.75 14 7.75H10Z" fill="currentColor" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
4
src/components/icons/ui/solid/download.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" color="#000000" fill="none">
|
||||
<path d="M20.25 14.75V10.75C20.25 8.83608 20.2477 7.50125 20.1123 6.49411C19.9808 5.51577 19.7401 4.99789 19.3711 4.62888C19.0021 4.25987 18.4842 4.01921 17.5059 3.88767C16.4987 3.75226 15.1639 3.74997 13.25 3.74997H10.75C8.83611 3.74997 7.50128 3.75226 6.49414 3.88767C5.5158 4.01921 4.99792 4.25987 4.62891 4.62888C4.2599 4.99789 4.01924 5.51577 3.8877 6.49411C3.75229 7.50125 3.75 8.83608 3.75 10.75V14.75C3.75 15.3023 3.30229 15.75 2.75 15.75C2.19772 15.75 1.75 15.3023 1.75 14.75V10.75C1.75 8.89262 1.74779 7.39889 1.90528 6.22751C2.06664 5.02741 2.41231 4.01735 3.21485 3.21481C4.01738 2.41228 5.02745 2.06661 6.22754 1.90524C7.39892 1.74776 8.89265 1.74997 10.75 1.74997H13.25C15.1074 1.74997 16.6011 1.74776 17.7725 1.90524C18.9726 2.06661 19.9826 2.41228 20.7852 3.21481C21.5877 4.01735 21.9334 5.02742 22.0947 6.22751C22.2522 7.39889 22.25 8.89262 22.25 10.75V14.75C22.25 15.3023 21.8023 15.75 21.25 15.75C20.6977 15.75 20.25 15.3023 20.25 14.75Z" fill="currentColor" />
|
||||
<path d="M14.0312 5.74997C15.6419 5.74996 16.9169 5.74997 17.9248 5.86911C18.9557 5.99098 19.8048 6.2463 20.5137 6.82809C20.7541 7.02541 20.9746 7.24589 21.1719 7.4863C21.7537 8.19522 22.009 9.04424 22.1309 10.0752C22.25 11.0831 22.25 12.3581 22.25 13.9687V14.0312C22.25 15.6418 22.25 16.9169 22.1309 17.9248C22.009 18.9557 21.7537 19.8047 21.1719 20.5136C20.9746 20.7541 20.7541 20.9745 20.5137 21.1718C19.8048 21.7536 18.9557 22.009 17.9248 22.1308C16.9169 22.25 15.6419 22.25 14.0312 22.25H9.96875C8.35815 22.25 7.0831 22.25 6.0752 22.1308C5.04427 22.009 4.19525 21.7536 3.48633 21.1718C3.24592 20.9745 3.02544 20.7541 2.82812 20.5136C2.24633 19.8047 1.99101 18.9557 1.86914 17.9248C1.75 16.9169 1.75 15.6418 1.75 14.0312V13.9687C1.75 12.3581 1.75 11.0831 1.86914 10.0752C1.99101 9.04424 2.24633 8.19522 2.82812 7.4863C3.02544 7.24589 3.24592 7.02541 3.48633 6.82809C4.19525 6.2463 5.04427 5.99098 6.0752 5.86911C7.0831 5.74997 8.35815 5.74996 9.96875 5.74997H14.0312ZM12 9.49997C11.4477 9.49997 11 9.94768 11 10.5V15.3906C10.6896 15.0331 10.3585 14.6264 10.1455 14.3535C10.0396 14.2178 9.86489 13.9856 9.80566 13.9072C9.47825 13.4626 8.8519 13.3671 8.40723 13.6943C7.96265 14.0217 7.86716 14.6481 8.19434 15.0927C8.259 15.1784 8.45594 15.4386 8.56934 15.584C8.7953 15.8735 9.10761 16.2629 9.44824 16.6552C9.78455 17.0426 10.1683 17.456 10.5352 17.7802C10.7175 17.9414 10.9198 18.1021 11.1279 18.2275C11.3086 18.3364 11.6228 18.5 12 18.5C12.3772 18.5 12.6914 18.3364 12.8721 18.2275C13.0802 18.1021 13.2825 17.9414 13.4648 17.7802C13.8317 17.4561 14.2154 17.0426 14.5518 16.6552C14.8924 16.2629 15.2047 15.8735 15.4307 15.584C15.5441 15.4386 15.741 15.1784 15.8057 15.0927C16.1328 14.6481 16.0373 14.0227 15.5928 13.6953C15.1481 13.3678 14.5219 13.4635 14.1943 13.9082C14.1351 13.9866 13.9604 14.2178 13.8545 14.3535C13.6415 14.6264 13.3104 15.0331 13 15.3906V10.5C13 9.9477 12.5523 9.49999 12 9.49997Z" fill="currentColor" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
4
src/components/icons/ui/solid/edit.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" color="#000000" fill="none">
|
||||
<path d="M18.799 3.0499C17.7324 1.98335 16.0032 1.98337 14.9366 3.04994L13.5236 4.46296L19.537 10.4763L20.9501 9.06321C22.0167 7.99665 22.0166 6.26746 20.9501 5.20092L18.799 3.0499Z" fill="currentColor" />
|
||||
<path d="M18.4764 11.537L12.463 5.52363L4.35808 13.6286C3.66361 14.3231 3.20349 15.2172 3.04202 16.1859L2.26021 20.8767C2.22039 21.1156 2.29841 21.3591 2.46968 21.5303C2.64095 21.7016 2.88439 21.7796 3.12331 21.7398L7.81417 20.958C8.78294 20.7965 9.67706 20.3364 10.3715 19.642L18.4764 11.537Z" fill="currentColor" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 650 B |
3
src/components/icons/ui/solid/error.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" color="#000000" fill="none">
|
||||
<path d="M12 1.25C17.9371 1.25 22.75 6.06294 22.75 12C22.75 17.9371 17.9371 22.75 12 22.75C6.06294 22.75 1.25 17.9371 1.25 12C1.25 6.06294 6.06294 1.25 12 1.25ZM12 14.9883C11.4477 14.9883 11 15.436 11 15.9883V15.998C11 16.5503 11.4477 16.998 12 16.998C12.5523 16.998 13 16.5503 13 15.998V15.9883C13 15.436 12.5523 14.9883 12 14.9883ZM12 7C11.4477 7 11 7.44772 11 8V12.5C11 13.0523 11.4477 13.5 12 13.5C12.5523 13.5 13 13.0523 13 12.5V8C13 7.44772 12.5523 7 12 7Z" fill="currentColor" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 609 B |
@@ -1,19 +1,18 @@
|
||||
// Outline variant icons
|
||||
// Solid variant icons
|
||||
export { ReactComponent as ArrowUp } from './arrow-up.svg'
|
||||
export { ReactComponent as Check } from './check.svg'
|
||||
export { ReactComponent as Close } from './close.svg'
|
||||
export { ReactComponent as Controller } from './controller.svg'
|
||||
export { ReactComponent as Copy } from './copy.svg'
|
||||
export { ReactComponent as Diamond } from './diamond.svg'
|
||||
export { ReactComponent as Download } from './download.svg'
|
||||
export { ReactComponent as Download1 } from './download1.svg'
|
||||
export { ReactComponent as Edit } from './edit.svg'
|
||||
export { ReactComponent as Error } from './error.svg'
|
||||
export { ReactComponent as Info } from './info.svg'
|
||||
export { ReactComponent as Layers } from './layers.svg'
|
||||
export { ReactComponent as Refresh } from './refresh.svg'
|
||||
export { ReactComponent as Search } from './search.svg'
|
||||
export { ReactComponent as Settings } from './settings.svg'
|
||||
export { ReactComponent as Trash } from './trash.svg'
|
||||
export { ReactComponent as Warning } from './warning.svg'
|
||||
export { ReactComponent as Wine } from './wine.svg'
|
||||
export { ReactComponent as Diamond } from './diamond.svg'
|
||||
export { ReactComponent as Settings } from './settings.svg'
|
||||
export { ReactComponent as Wine } from './wine.svg'
|
||||
3
src/components/icons/ui/solid/info.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" color="#000000" fill="none">
|
||||
<path d="M12 1.25C17.9371 1.25 22.75 6.06294 22.75 12C22.75 17.9371 17.9371 22.75 12 22.75C6.06294 22.75 1.25 17.9371 1.25 12C1.25 6.06294 6.06294 1.25 12 1.25ZM12 10.5C11.4477 10.5 11 10.9477 11 11.5V16C11 16.5523 11.4477 17 12 17C12.5523 17 13 16.5523 13 16V11.5C13 10.9477 12.5523 10.5 12 10.5ZM12 6.99805C11.4477 6.99805 11 7.44576 11 7.99805V8.00781C11 8.5601 11.4477 9.00781 12 9.00781C12.5523 9.00781 13 8.5601 13 8.00781V7.99805C13 7.44576 12.5523 6.99805 12 6.99805Z" fill="currentColor" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 622 B |
4
src/components/icons/ui/solid/layers.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" color="#000000" fill="none">
|
||||
<path d="M9.43531 4.06585C10.4757 3.58724 11.2087 3.25 12 3.25C12.7913 3.25 13.5243 3.58724 14.5647 4.06585L19.6573 6.40525C20.513 6.79828 21.2323 7.12867 21.731 7.45333C22.2326 7.77985 22.75 8.25262 22.75 9C22.75 9.74738 22.2326 10.2202 21.731 10.5467C21.2323 10.8713 20.513 11.2017 19.6573 11.5948L14.5647 13.9341C13.5244 14.4128 12.7913 14.75 12 14.75C11.2087 14.75 10.4757 14.4128 9.43532 13.9342L9.4353 13.9341L4.3427 11.5947L4.34269 11.5947C3.487 11.2017 2.76767 10.8713 2.26898 10.5467C1.76745 10.2202 1.25 9.74738 1.25 9C1.25 8.25262 1.76745 7.77985 2.26898 7.45333C2.76767 7.12867 3.48701 6.79827 4.34271 6.40525L9.43531 4.06585Z" fill="currentColor" />
|
||||
<path d="M3.43379 12.8281C2.97382 13.0479 2.57882 13.2518 2.26898 13.4535C1.76745 13.7801 1.25 14.2528 1.25 15.0002C1.25 15.7476 1.76745 16.2204 2.26898 16.5469C2.76766 16.8715 3.48698 17.2019 4.34265 17.5949L9.4353 19.9344C10.4756 20.413 11.2087 20.7502 12 20.7502C12.7913 20.7502 13.5244 20.413 14.5647 19.9344L19.6573 17.595C20.513 17.2019 21.2323 16.8715 21.731 16.5469C22.2326 16.2204 22.75 15.7476 22.75 15.0002C22.75 14.2528 22.2326 13.7801 21.731 13.4535C21.4212 13.2518 21.0262 13.0479 20.5662 12.8281C20.499 12.8591 20.4317 12.8899 20.3646 12.9208L15.0478 15.3634C14.1346 15.785 13.1268 16.2502 12 16.2502C10.8732 16.2502 9.86543 15.785 8.95222 15.3634L3.63539 12.9208C3.56827 12.8899 3.50101 12.8591 3.43379 12.8281Z" fill="currentColor" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
3
src/components/icons/ui/solid/refresh.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" color="#000000" fill="none">
|
||||
<path d="M2.25 12C2.25 6.61522 6.61521 2.25 12 2.25C15.1908 2.25 18.0209 3.78363 19.799 6.15095L19.8 6.15V3.225C19.8 2.68652 20.2365 2.25 20.775 2.25C21.3135 2.25 21.75 2.68652 21.75 3.225V6.15C21.75 6.81187 21.7524 7.40649 21.6881 7.88481C21.62 8.39116 21.4614 8.91039 21.0359 9.33589C20.6104 9.76139 20.0912 9.92002 19.5848 9.98811C19.1065 10.0524 18.5119 10.05 17.85 10.05H14.925C14.3865 10.05 13.95 9.61348 13.95 9.075C13.95 8.53652 14.3865 8.1 14.925 8.1H17.85C18.2154 8.1 18.5087 8.09857 18.7507 8.09238C17.4002 5.76433 14.8824 4.2 12 4.2C7.69217 4.2 4.2 7.69217 4.2 12C4.2 16.3078 7.69217 19.8 12 19.8C15.3946 19.8 18.285 17.631 19.3563 14.6003C19.5357 14.0926 20.093 13.8267 20.6007 14.0062C21.1082 14.1857 21.3742 14.7421 21.1949 15.2497C19.8569 19.0352 16.2467 21.75 12 21.75C6.61521 21.75 2.25 17.3848 2.25 12Z" fill="currentColor" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 968 B |
3
src/components/icons/ui/solid/search.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" color="#000000" fill="none">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 2C6.02944 2 2 6.02944 2 11C2 15.9706 6.02944 20 11 20C13.125 20 15.078 19.2635 16.6177 18.0319L20.2929 21.7071C20.6834 22.0976 21.3166 22.0976 21.7071 21.7071C22.0976 21.3166 22.0976 20.6834 21.7071 20.2929L18.0319 16.6177C19.2635 15.078 20 13.125 20 11C20 6.02944 15.9706 2 11 2ZM4 11C4 7.13401 7.13401 4 11 4C14.866 4 18 7.13401 18 11C18 14.866 14.866 18 11 18C7.13401 18 4 14.866 4 11Z" fill="currentColor" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 588 B |
3
src/components/icons/ui/solid/settings.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" color="#000000" fill="none">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.74975 22.4469C10.0241 22.6444 10.3181 22.7181 10.5977 22.75H13.4023C13.6819 22.7181 13.9759 22.6444 14.2503 22.4469C14.5245 22.2495 14.6873 21.9946 14.8058 21.7402C14.9122 21.5117 15.4485 19.9112 15.4485 19.9112C15.4485 19.9112 15.7104 19.4678 15.9256 19.3266C16.3481 19.0495 16.7744 18.9894 17.0521 19.0495C17.0521 19.0495 18.9336 19.5792 19.1993 19.6213C19.4948 19.6681 19.8181 19.674 20.1524 19.5385C20.4866 19.4031 20.714 19.1739 20.893 18.935L22.3316 16.4526C22.4442 16.1934 22.5272 15.9008 22.4917 15.5633C22.4561 15.2258 22.3138 14.9568 22.1496 14.7266C22.0021 14.5199 20.7592 13.1363 20.5441 12.898C20.5441 12.898 20.2841 12.4993 20.2841 11.9998C20.2841 11.4151 20.5441 11.1019 20.5441 11.1019C20.5441 11.1019 22.0021 9.47998 22.1496 9.2733C22.3138 9.04311 22.4561 8.7741 22.4917 8.4366C22.5272 8.09914 22.4442 7.80652 22.3316 7.54733C22.2304 7.31456 20.893 5.06487 20.893 5.06487C20.714 4.82595 20.4866 4.59685 20.1524 4.46138C19.8181 4.32586 19.4948 4.33183 19.1993 4.3786C18.9336 4.42064 17.382 4.85698 17.0521 4.95035C17.0521 4.95035 16.5227 5.06487 15.9256 4.67329C15.7104 4.53218 15.5437 4.32813 15.4484 4.08872C15.4484 4.08872 14.9122 2.48829 14.8058 2.25981C14.6873 2.00543 14.5245 1.75047 14.2503 1.55308C13.9759 1.3556 13.6819 1.28188 13.4023 1.25H10.5977C10.3181 1.28188 10.0241 1.3556 9.74975 1.55308C9.47551 1.75047 9.31274 2.00543 9.19424 2.25981C9.0878 2.48829 8.55157 4.08872 8.55157 4.08872C8.45627 4.32813 8.28956 4.53218 8.07438 4.67329C7.47727 5.06487 6.94794 4.95035 6.94794 4.95035C6.61796 4.85698 5.06641 4.42064 4.80069 4.3786C4.50518 4.33183 4.18189 4.32586 3.84759 4.46138C3.51342 4.59685 3.28602 4.82595 3.10699 5.06487C3.10699 5.06487 1.76957 7.31456 1.66843 7.54733C1.55583 7.80652 1.47276 8.09914 1.50833 8.4366C1.5439 8.7741 1.68619 9.04311 1.85043 9.2733C1.99791 9.47998 3.45586 11.1019 3.45586 11.1019C3.45586 11.1019 3.71591 11.4151 3.71591 11.9998C3.71591 12.4993 3.45588 12.898 3.45588 12.898C3.24083 13.1363 1.99791 14.5199 1.85044 14.7266C1.68619 14.9568 1.5439 15.2258 1.50833 15.5633C1.47276 15.9008 1.55583 16.1934 1.66844 16.4526L3.10697 18.935C3.28601 19.1739 3.51341 19.4031 3.84761 19.5385C4.18191 19.674 4.50519 19.6681 4.80071 19.6213C5.06643 19.5792 6.94789 19.0495 6.94789 19.0495C7.22563 18.9894 7.65193 19.0495 8.07443 19.3266C8.28957 19.4678 8.55153 19.9112 8.55153 19.9112C8.55153 19.9112 9.0878 21.5117 9.19424 21.7402C9.31274 21.9946 9.47551 22.2495 9.74975 22.4469ZM12.0195 15.5C13.9525 15.5 15.5195 13.933 15.5195 12C15.5195 10.067 13.9525 8.5 12.0195 8.5C10.0865 8.5 8.51953 10.067 8.51953 12C8.51953 13.933 10.0865 15.5 12.0195 15.5Z" fill="currentColor" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
4
src/components/icons/ui/solid/trash.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" color="#000000" fill="none">
|
||||
<path d="M19.5825 15.6564C19.5058 16.9096 19.4449 17.9041 19.3202 18.6984C19.1922 19.5131 18.9874 20.1915 18.5777 20.7849C18.2029 21.3278 17.7204 21.786 17.1608 22.1303C16.5491 22.5067 15.8661 22.6713 15.0531 22.75L8.92739 22.7499C8.1135 22.671 7.42972 22.5061 6.8176 22.129C6.25763 21.7841 5.77494 21.3251 5.40028 20.7813C4.99073 20.1869 4.78656 19.5075 4.65957 18.6917C4.53574 17.8962 4.47623 16.9003 4.40122 15.6453L3.75 4.75H20.25L19.5825 15.6564Z" fill="currentColor" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.3473 1.28277C13.9124 1.33331 14.4435 1.50576 14.8996 1.84591C15.2369 2.09748 15.4712 2.40542 15.6714 2.73893C15.8569 3.04798 16.0437 3.4333 16.2555 3.8704L16.6823 4.7507H21C21.5523 4.7507 22 5.19842 22 5.7507C22 6.30299 21.5523 6.7507 21 6.7507C14.9998 6.7507 9.00019 6.7507 3 6.7507C2.44772 6.7507 2 6.30299 2 5.7507C2 5.19842 2.44772 4.7507 3 4.7507H7.40976L7.76556 3.97016C7.97212 3.51696 8.15403 3.11782 8.33676 2.79754C8.53387 2.45207 8.76721 2.13237 9.10861 1.87046C9.57032 1.51626 10.1121 1.33669 10.6899 1.28409C11.1249 1.24449 11.5634 1.24994 12 1.25064C12.5108 1.25146 12.97 1.24902 13.3473 1.28277ZM9.60776 4.7507H14.4597C14.233 4.28331 14.088 3.98707 13.9566 3.7682C13.7643 3.44787 13.5339 3.30745 13.1691 3.27482C12.9098 3.25163 12.5719 3.2507 12.0345 3.2507C11.4837 3.2507 11.137 3.25166 10.8712 3.27585C10.4971 3.30991 10.2639 3.45568 10.0739 3.78866C9.94941 4.00687 9.81387 4.29897 9.60776 4.7507Z" fill="currentColor" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
3
src/components/icons/ui/solid/warning.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" color="#000000" fill="none">
|
||||
<path d="M13.998 21.75C16.253 21.75 18.0331 21.75 19.3515 21.5537C20.6903 21.3543 21.7763 20.9217 22.3759 19.8633L22.4795 19.6641C22.9508 18.6611 22.7579 17.5714 22.2763 16.3945C21.8927 15.4569 21.277 14.3483 20.499 13.0195L19.6689 11.6162L17.7441 8.37109L17.6982 8.29297C16.5955 6.4338 15.7229 4.96328 14.9111 3.96484C14.0828 2.94615 13.1844 2.25 12 2.25C10.8155 2.25004 9.91711 2.94615 9.08883 3.96484C8.47155 4.72408 7.81868 5.7561 7.0566 7.02441L6.25582 8.37109L4.33102 11.6162L4.28317 11.6963C3.13452 13.6328 2.22816 15.1614 1.7236 16.3945C1.21 17.6498 1.02504 18.8058 1.62399 19.8633L1.74215 20.0547C2.36044 20.9749 3.39339 21.3668 4.6484 21.5537C5.96685 21.75 7.74695 21.75 10.0019 21.75L13.998 21.75ZM12 14.5C11.4477 14.4999 11 14.0522 11 13.5V9C11 8.44776 11.4477 8.00007 12 8C12.5522 8 13 8.44771 13 9V13.5C13 14.0523 12.5522 14.5 12 14.5ZM12 18.002C11.4477 18.0019 11 17.5542 11 17.002V16.9922C11 16.4399 11.4477 15.9923 12 15.9922C12.5522 15.9922 13 16.4399 13 16.9922V17.002C13 17.5542 12.5522 18.002 12 18.002Z" fill="currentColor" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 864 B After Width: | Height: | Size: 864 B |
4
src/components/icons/ui/stroke/arrow-up.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" color="#000000" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 5.5V19" />
|
||||
<path d="M18 11C18 11 13.5811 5.00001 12 5C10.4188 4.99999 6 11 6 11" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 311 B |
3
src/components/icons/ui/stroke/check.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" color="#000000" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M5 14L8.5 17.5L19 6.5" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 245 B |
3
src/components/icons/ui/stroke/close.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" color="#000000" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M18 6L6.00081 17.9992M17.9992 18L6 6.00085" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 266 B |
7
src/components/icons/ui/stroke/controller.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" color="#000000" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M2.00825 15.8092C2.23114 12.3161 2.88737 9.7599 3.44345 8.27511C3.72419 7.5255 4.32818 6.96728 5.10145 6.78021C9.40147 5.73993 14.5986 5.73993 18.8986 6.78021C19.6719 6.96728 20.2759 7.5255 20.5566 8.27511C21.1127 9.7599 21.7689 12.3161 21.9918 15.8092C22.1251 17.8989 20.6148 19.0503 18.9429 19.8925C17.878 20.4289 17.0591 18.8457 16.5155 17.6203C16.2185 16.9508 15.5667 16.5356 14.8281 16.5356H9.17196C8.43331 16.5356 7.78158 16.9508 7.48456 17.6203C6.94089 18.8457 6.122 20.4289 5.05711 19.8925C3.40215 19.0588 1.87384 17.9157 2.00825 15.8092Z" />
|
||||
<path d="M5 4.5L6.96285 4M19 4.5L17 4" />
|
||||
<path d="M9 13L7.5 11.5M7.5 11.5L6 10M7.5 11.5L6 13M7.5 11.5L9 10" />
|
||||
<path d="M15.9881 10H15.9971" />
|
||||
<path d="M17.9881 13H17.9971" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 964 B |
4
src/components/icons/ui/stroke/copy.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" color="#000000" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M16.9637 8.98209C16.9613 6.03194 16.9167 4.50384 16.0578 3.45753C15.892 3.25546 15.7067 3.07019 15.5047 2.90436C14.4008 1.99854 12.7609 1.99854 9.48087 1.99854C6.20089 1.99854 4.5609 1.99854 3.45708 2.90436C3.255 3.07018 3.06971 3.25546 2.90387 3.45753C1.99799 4.56128 1.99799 6.20116 1.99799 9.48091C1.99799 12.7607 1.99799 14.4005 2.90387 15.5043C3.0697 15.7063 3.255 15.8916 3.45708 16.0574C4.50346 16.9162 6.03167 16.9608 8.98201 16.9632" />
|
||||
<path d="M14.0283 9.02455L16.994 8.98193M14.0143 22.0013L16.9799 21.9586M21.9716 14.0221L21.9436 16.9818M9.01033 14.0357L8.98236 16.9953M11.4873 9.02455C10.6545 9.17371 9.31781 9.32713 9.01033 11.0488M19.4946 21.9586C20.3296 21.8223 21.6685 21.6894 22.0025 19.9726M19.4946 9.02455C20.3274 9.17371 21.6641 9.32713 21.9716 11.0488M11.5 21.9573C10.6672 21.8086 9.33039 21.6559 9.02197 19.9344" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
4
src/components/icons/ui/stroke/diamond.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" color="#000000" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<path d="M5.78223 4.18192C6.43007 3.68319 6.754 3.43383 7.12788 3.27323C7.29741 3.20041 7.47367 3.14158 7.65459 3.09741C8.0536 3 8.4767 3 9.32289 3H14.6771C15.5233 3 15.9464 3 16.3454 3.09741C16.5263 3.14158 16.7026 3.20041 16.8721 3.27323C17.246 3.43383 17.5699 3.68319 18.2178 4.18192C20.3644 5.83448 21.4378 6.66077 21.8057 7.73078C21.9694 8.20673 22.0305 8.70728 21.9858 9.20461C21.8852 10.3227 21.0379 11.346 19.3433 13.3925L15.3498 18.2153C13.8126 20.0718 13.044 21 12 21C10.956 21 10.1874 20.0718 8.65018 18.2153L4.65671 13.3925C2.96208 11.346 2.11476 10.3227 2.0142 9.20461C1.96947 8.70728 2.03064 8.20673 2.1943 7.73078C2.56224 6.66077 3.63557 5.83448 5.78223 4.18192Z" />
|
||||
<path d="M10 8.5H14" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 873 B |
5
src/components/icons/ui/stroke/download.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" color="#000000" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M14 21.5H10C6.71252 21.5 5.06878 21.5 3.96243 20.592C3.75989 20.4258 3.57418 20.2401 3.40796 20.0376C2.5 18.9312 2.5 17.2875 2.5 14C2.5 10.7125 2.5 9.06878 3.40796 7.96243C3.57418 7.75989 3.75989 7.57418 3.96243 7.40796C5.06878 6.5 6.71252 6.5 10 6.5H14C17.2875 6.5 18.9312 6.5 20.0376 7.40796C20.2401 7.57418 20.4258 7.75989 20.592 7.96243C21.5 9.06878 21.5 10.7125 21.5 14C21.5 17.2875 21.5 18.9312 20.592 20.0376C20.4258 20.2401 20.2401 20.4258 20.0376 20.592C18.9312 21.5 17.2875 21.5 14 21.5Z" />
|
||||
<path d="M2.5 14.5V10.5C2.5 6.72876 2.5 4.84315 3.67157 3.67157C4.84315 2.5 6.72876 2.5 10.5 2.5H13.5C17.2712 2.5 19.1569 2.5 20.3284 3.67157C21.5 4.84315 21.5 6.72876 21.5 10.5V14.5" />
|
||||
<path d="M15 14.5C15 14.5 12.7905 17.4999 12 17.4999C11.2094 17.5 9 14.4999 9 14.4999M12 17L12 10.5" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1020 B |
4
src/components/icons/ui/stroke/edit.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" color="#000000" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M3.78181 16.3092L3 21L7.69086 20.2182C8.50544 20.0825 9.25725 19.6956 9.84119 19.1116L20.4198 8.53288C21.1934 7.75922 21.1934 6.5049 20.4197 5.73126L18.2687 3.58024C17.495 2.80658 16.2406 2.80659 15.4669 3.58027L4.88841 14.159C4.30447 14.7429 3.91757 15.4947 3.78181 16.3092Z" />
|
||||
<path d="M14 6L18 10" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 528 B |
5
src/components/icons/ui/stroke/error.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" color="#000000" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<path d="M12 8V12.5" />
|
||||
<path d="M12 15.9883V15.9983" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 309 B |
@@ -1,19 +1,18 @@
|
||||
// Bold variant icons
|
||||
// Stroke variant icons
|
||||
export { ReactComponent as ArrowUp } from './arrow-up.svg'
|
||||
export { ReactComponent as Check } from './check.svg'
|
||||
export { ReactComponent as Close } from './close.svg'
|
||||
export { ReactComponent as Controller } from './controller.svg'
|
||||
export { ReactComponent as Copy } from './copy.svg'
|
||||
export { ReactComponent as Diamond } from './diamond.svg'
|
||||
export { ReactComponent as Download } from './download.svg'
|
||||
export { ReactComponent as Download1 } from './download1.svg'
|
||||
export { ReactComponent as Edit } from './edit.svg'
|
||||
export { ReactComponent as Error } from './error.svg'
|
||||
export { ReactComponent as Info } from './info.svg'
|
||||
export { ReactComponent as Layers } from './layers.svg'
|
||||
export { ReactComponent as Refresh } from './refresh.svg'
|
||||
export { ReactComponent as Search } from './search.svg'
|
||||
export { ReactComponent as Settings } from './settings.svg'
|
||||
export { ReactComponent as Trash } from './trash.svg'
|
||||
export { ReactComponent as Warning } from './warning.svg'
|
||||
export { ReactComponent as Wine } from './wine.svg'
|
||||
export { ReactComponent as Diamond } from './diamond.svg'
|
||||
export { ReactComponent as Settings } from './settings.svg'
|
||||
export { ReactComponent as Wine } from './wine.svg'
|
||||
5
src/components/icons/ui/stroke/info.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" color="#000000" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<path d="M12 16V11.5" />
|
||||
<path d="M12 8.01172V8.00172" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 310 B |
4
src/components/icons/ui/stroke/layers.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" color="#000000" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M9.60573 4.81298C10.7856 4.27099 11.3755 4 12 4C12.6245 4 13.2144 4.27099 14.3943 4.81298L19.2873 7.06064C21.0958 7.89137 22 8.30674 22 9C22 9.69326 21.0958 10.1086 19.2873 10.9394L14.3943 13.187C13.2144 13.729 12.6245 14 12 14C11.3755 14 10.7856 13.729 9.60573 13.187L4.7127 10.9394C2.90423 10.1086 2 9.69326 2 9C2 8.30674 2.90423 7.89137 4.7127 7.06064L9.60573 4.81298Z" />
|
||||
<path d="M20.2327 13.5C21.4109 14.062 22 14.4405 22 15.0001C22 15.6934 21.0958 16.1087 19.2873 16.9395L14.3943 19.1871C13.2144 19.7291 12.6245 20.0001 12 20.0001C11.3755 20.0001 10.7856 19.7291 9.60573 19.1871L4.7127 16.9395C2.90423 16.1087 2 15.6934 2 15.0001C2 14.4405 2.58909 14.062 3.76727 13.5" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 902 B |
4
src/components/icons/ui/stroke/refresh.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" color="#000000" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M20.4879 15C19.2524 18.4956 15.9187 21 12 21C7.02943 21 3 16.9706 3 12C3 7.02943 7.02943 3 12 3C15.7292 3 18.9286 5.26806 20.2941 8.5" />
|
||||
<path d="M15 9H18C19.4142 9 20.1213 9 20.5607 8.56066C21 8.12132 21 7.41421 21 6V3" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 448 B |