Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
488e695
Introduce analytics module with core components and utilities
jakubbbdev Oct 21, 2025
3604226
Add analytics client and configuration module with request handling a…
jakubbbdev Oct 21, 2025
b4d67ef
Introduce `AnalyticsEvent` model and `EventBuilder` for event creatio…
jakubbbdev Oct 21, 2025
51144e0
Add `AnalyticsManager` and `TestServer` for event tracking and testin…
jakubbbdev Oct 21, 2025
31e9d40
Add analytics demo examples showcasing event tracking and configurati…
jakubbbdev Oct 21, 2025
061869c
Add detailed documentation for `AnalyticsManager` usage and configura…
jakubbbdev Oct 21, 2025
c8e2f4b
Add `SingleEvent` variant to `PayloadFormat` enum in analytics config…
jakubbbdev Oct 21, 2025
8fff014
Update `AnalyticsManager` initialization to include temporary directo…
jakubbbdev Oct 24, 2025
dc17ff6
Add temporary directory to `AnalyticsManager` initialization in analy…
jakubbbdev Oct 24, 2025
c0dd121
Improve logging in analytics client to include detailed request and r…
jakubbbdev Oct 24, 2025
080c611
Track profile launch events and add related analytics commands
jakubbbdev Oct 24, 2025
95199a9
Add `AnalyticsStorage` to `AnalyticsManager` for local event storage …
jakubbbdev Oct 24, 2025
97b5dbd
Integrate `AnalyticsManager` into `State` and initialize with localho…
jakubbbdev Oct 24, 2025
d29a14c
Add logging for profile launch analytics tracking and ensure early ev…
jakubbbdev Oct 24, 2025
91a4f93
Update `base64` dependency to version 0.22 and adjust `Cargo.toml` co…
jakubbbdev Oct 24, 2025
5d72ef3
Add custom background image support with adjustable properties
jakubbbdev Oct 24, 2025
96b642a
Enhance theme and background customization:
jakubbbdev Oct 24, 2025
a5bb058
Add skin visibility toggle functionality:
jakubbbdev Oct 24, 2025
1b4471d
Track recent profiles by adding `profileName` to `useProfileLaunch` a…
jakubbbdev Oct 24, 2025
61e9dcb
Add profile hiding, update availability indicator, and improve contex…
jakubbbdev Oct 24, 2025
080df0f
Filter hidden profiles in `ProfilesTab` and `ProfileSelectionModalCon…
jakubbbdev Oct 24, 2025
cc78d90
Add folder management, favorites, and enhanced drag-and-drop function…
jakubbbdev Oct 24, 2025
dfdfcf7
Add recent and hidden profile filters:
jakubbbdev Oct 24, 2025
284ea76
Adjust skin filtering logic to exclude folders when showing favorites…
jakubbbdev Oct 24, 2025
b08d24e
Add state management for profiles, skins, and folders:
jakubbbdev Oct 24, 2025
056cb53
Add analytics commands and test server:
jakubbbdev Oct 24, 2025
77fb140
Add `CreateFolderModal` and `CustomImageBackground` components:
jakubbbdev Oct 24, 2025
cf5f0c3
Add `CreateFolderModal` and `CustomImageBackground` components:
jakubbbdev Oct 24, 2025
6a34fdd
Add `MoveToFolderModal` component:
jakubbbdev Oct 24, 2025
ff1c564
Add `AnalyticsStorage` for persisting analytics events:
jakubbbdev Oct 24, 2025
e61d5c7
Add `load_image_as_base64` command:
jakubbbdev Oct 24, 2025
d5bccb8
Remove analytics-related commands, examples, and test server:
jakubbbdev Oct 26, 2025
7813791
Improve `SearchWithFilters` component styling:
jakubbbdev Oct 26, 2025
8252ace
Update button text and adjust padding in `SkinsTab` and `ProfilesTabV2`:
jakubbbdev Oct 26, 2025
b2723ff
Refactor `MoveToFolderModal` to use shared `Modal` component and inte…
jakubbbdev Oct 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ directories = "5.0"
once_cell = "1.18"
futures = "0.3"
tauri = { version = "2", features = ["protocol-asset", "image-png"] }
base64 = "0.22"
tauri-plugin-opener = "2"
sha1 = "0.10.6"
zip = "2.6.1"
Expand All @@ -45,7 +46,6 @@ dashmap = "6.1.0"
rand = "0.8.5"
sha2 = "0.10.8"
p256 = "0.13.2"
base64 = "0.21.7"
jsonwebtoken = "9.3.0"
machineid-rs = "1.2.4"
byteorder = { version = "1.4" }
Expand Down
14 changes: 14 additions & 0 deletions src-tauri/src/commands/image_command.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use tauri::command;
use std::fs;
use base64::{Engine as _, engine::general_purpose};

#[command]
pub async fn load_image_as_base64(image_path: String) -> Result<String, String> {
match fs::read(image_path) {
Ok(image_data) => {
let base64_string = general_purpose::STANDARD.encode(&image_data);
Ok(format!("data:image/png;base64,{}", base64_string))
}
Err(e) => Err(format!("Failed to read image: {}", e))
}
}
1 change: 1 addition & 0 deletions src-tauri/src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod image_command;
pub mod cape_command;
pub mod config_commands;
pub mod content_command;
Expand Down
1 change: 1 addition & 0 deletions src-tauri/src/commands/profile_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ pub async fn launch_profile(
quick_play_multiplayer: Option<String>,
migration_info: Option<profile_utils::MigrationInfo>,
) -> Result<(), CommandError> {
log::info!("=== NEW VERSION WITH ANALYTICS ===");
log::info!(
"[Command] launch_profile called for ID: {}. QuickPlay Single: {:?}, QuickPlay Multi: {:?}, Migration: {:?}",
id,
Expand Down
1 change: 1 addition & 0 deletions src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ async fn main() {
Ok(())
})
.invoke_handler(tauri::generate_handler![
commands::image_command::load_image_as_base64,
create_profile,
get_profile,
update_profile,
Expand Down
123 changes: 123 additions & 0 deletions src/components/effects/CustomImageBackground.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import React, { useState, useEffect } from "react";
import { invoke } from "@tauri-apps/api/core";
import { Icon } from "@iconify/react";

interface CustomImageBackgroundProps {
imagePath: string | null;
opacity: number;
blur: number;
scale: number;
}

const CustomImageBackground: React.FC<CustomImageBackgroundProps> = ({
imagePath,
opacity,
blur,
scale,
}) => {
const [imageDataUrl, setImageDataUrl] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);

useEffect(() => {
const loadImage = async () => {
if (!imagePath) {
setLoading(false);
return;
}

try {
setLoading(true);
setError(null);

const base64Data = await invoke<string>("load_image_as_base64", {
imagePath: imagePath,
});

setImageDataUrl(base64Data);
console.log("Custom background image loaded successfully as base64");
} catch (err) {
console.error("Failed to load custom background image:", err);
setError(err as string);
} finally {
setLoading(false);
}
};

loadImage();
}, [imagePath]);

if (!imagePath) {
return (
<div className="absolute inset-0 bg-black/50 flex items-center justify-center">
<div className="text-center text-white/50">
<div className="w-16 h-16 mx-auto mb-4 flex items-center justify-center">
<svg className="w-full h-full" viewBox="0 0 24 24" fill="currentColor">
<path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/>
</svg>
</div>
<p className="text-lg font-minecraft">No custom image selected</p>
<p className="text-sm font-minecraft-ten">Go to Settings → Background to select an image</p>
</div>
</div>
);
}

if (loading) {
return (
<div className="absolute inset-0 bg-black/50 flex items-center justify-center">
<div className="text-center text-white/50">
<Icon icon="solar:loading-bold" className="w-16 h-16 mx-auto mb-4 animate-spin" />
<p className="text-lg font-minecraft">Loading background image...</p>
</div>
</div>
);
}

if (error) {
return (
<div className="absolute inset-0 bg-black/50 flex items-center justify-center">
<div className="text-center text-white/50">
<div className="w-16 h-16 mx-auto mb-4 flex items-center justify-center">
<svg className="w-full h-full" viewBox="0 0 24 24" fill="currentColor">
<path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/>
</svg>
</div>
<p className="text-lg font-minecraft">Failed to load custom image</p>
<p className="text-sm font-minecraft-ten">Check file path and format</p>
</div>
</div>
);
}

if (!imageDataUrl) {
return (
<div className="absolute inset-0 bg-black/50 flex items-center justify-center">
<div className="text-center text-white/50">
<div className="w-16 h-16 mx-auto mb-4 flex items-center justify-center">
<svg className="w-full h-full" viewBox="0 0 24 24" fill="currentColor">
<path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/>
</svg>
</div>
<p className="text-lg font-minecraft">No image data</p>
</div>
</div>
);
}

return (
<div
className="absolute inset-0 w-full h-full transition-all duration-500"
style={{
backgroundImage: `url("${imageDataUrl}")`,
backgroundSize: `${100 * scale}%`,
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
opacity: opacity,
filter: `blur(${blur}px)`,
}}
/>
);
};

export default CustomImageBackground;
1 change: 1 addition & 0 deletions src/components/launcher/MainLaunchButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export function MainLaunchButton({
// Use the profile launch hook for launch logic
const { handleLaunch: hookHandleLaunch, isLaunching, statusMessage, launchState } = useProfileLaunch({
profileId: selectedVersion,
profileName: selectedVersionLabel,
onLaunchSuccess: () => {
setTransientSuccessActive(true);
setTimeout(() => {
Expand Down
38 changes: 27 additions & 11 deletions src/components/launcher/PlayerActionsDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import { cn } from '../../lib/utils';
import { SkinViewer } from './SkinViewer';
import { MainLaunchButton } from './MainLaunchButton';
import { useThemeStore } from '../../store/useThemeStore';
import { useSkinVisibilityStore } from '../../store/useSkinVisibilityStore';
import { MinecraftSkinService } from '../../services/minecraft-skin-service';
import type { GetStarlightSkinRenderPayload } from '../../types/localSkin';
import { convertFileSrc } from '@tauri-apps/api/core';
import { Icon } from '@iconify/react';

const DEFAULT_FALLBACK_SKIN_URL = "/skins/default_steve_full.png"; // Defined constant for fallback URL

Expand Down Expand Up @@ -35,6 +37,7 @@ export function PlayerActionsDisplay({
displayMode = 'playerName',
}: PlayerActionsDisplayProps) {
const accentColor = useThemeStore((state) => state.accentColor);
const { isSkinVisible, toggleSkinVisibility } = useSkinVisibilityStore();
const [resolvedSkinUrl, setResolvedSkinUrl] = useState<string>(DEFAULT_FALLBACK_SKIN_URL);

useEffect(() => {
Expand Down Expand Up @@ -99,23 +102,36 @@ export function PlayerActionsDisplay({
}}
/>
) : (
<h2 className="font-minecraft text-6xl text-center text-white mb-2 lowercase font-normal">
{playerName || "no account"}
</h2>
isSkinVisible && (
<h2 className="font-minecraft text-6xl text-center text-white mb-2 lowercase font-normal">
{playerName || "no account"}
</h2>
)
)}

<div className={cn(
"relative w-full max-w-[500px] flex flex-col items-center",
displayMode === 'logo' && "z-10"
)}>
<SkinViewer
skinUrl={resolvedSkinUrl}
playerName={playerName?.toString()}
width={skinViewerMaxDisplayWidth}
height={skinViewerDisplayHeight}
className="bg-transparent flex-shrink-0"
style={skinViewerStyles}
/>

{isSkinVisible ? (
<SkinViewer
skinUrl={resolvedSkinUrl}
playerName={playerName?.toString()}
width={skinViewerMaxDisplayWidth}
height={skinViewerDisplayHeight}
className="bg-transparent flex-shrink-0"
style={skinViewerStyles}
/>
) : (
<div
className="bg-transparent flex-shrink-0"
style={{
width: skinViewerMaxDisplayWidth,
height: skinViewerDisplayHeight
}}
/>
)}

<div className="absolute bottom-8 left-0 right-0 flex justify-center px-4">
<div className="max-w-xs sm:max-w-sm">
Expand Down
34 changes: 22 additions & 12 deletions src/components/launcher/ProfileSelectionModalContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { Icon } from "@iconify/react";
import { useVersionSelectionStore } from "../../store/version-selection-store";
import { useProfileStore } from "../../store/profile-store";
import { useHiddenProfilesStore } from "../../store/useHiddenProfilesStore";
import type { Profile } from "../../types/profile";
import { ProfileCard } from "../profiles/ProfileCard";
import { VirtuosoGrid } from "react-virtuoso";
Expand All @@ -23,6 +24,7 @@ export function ProfileSelectionModalContent({
}: ProfileSelectionModalContentProps) {
const { setSelectedVersion } = useVersionSelectionStore();
const { profiles, loading: profilesLoading, error: profilesError, fetchProfiles, deleteProfile } = useProfileStore();
const { isProfileHidden } = useHiddenProfilesStore();

// Export modal state
const [isExportModalOpen, setIsExportModalOpen] = useState(false);
Expand Down Expand Up @@ -113,17 +115,24 @@ export function ProfileSelectionModalContent({
<div className="text-center p-4 text-white/60 font-minecraft text-2xl lowercase tracking-wide select-none">
no profiles available
</div>
) : (
<VirtuosoGrid
totalCount={profiles.length}
style={{ height: "60vh" }} // Max height is controlled by Virtuoso
className="custom-scrollbar" // Apply custom scrollbar style
components={{
List: GridList,
Item: GridItem,
}}
itemContent={(index) => {
const profile = profiles[index];
) : (() => {
const visibleProfiles = profiles.filter(profile => !isProfileHidden(profile.id));

return visibleProfiles.length === 0 ? (
<div className="text-center p-4 text-white/60 font-minecraft text-2xl lowercase tracking-wide select-none">
no profiles available
</div>
) : (
<VirtuosoGrid
totalCount={visibleProfiles.length}
style={{ height: "60vh" }}
className="custom-scrollbar"
components={{
List: GridList,
Item: GridItem,
}}
itemContent={(index) => {
const profile = visibleProfiles[index];
return (
<ProfileCard
key={profile.id}
Expand All @@ -139,7 +148,8 @@ export function ProfileSelectionModalContent({
);
}}
/>
)}
);
})()}

{/* Export Profile Modal */}
{profileToExport && (
Expand Down
18 changes: 14 additions & 4 deletions src/components/layout/AppLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { NebulaLightning } from ".././effects/NebulaLightning";
import { NebulaLiquidChrome } from ".././effects/NebulaLiquidChrome";
import { RetroGridEffect } from "../effects/RetroGridEffect";
import PlainBackground from "../effects/PlainBackground";
import CustomImageBackground from "../effects/CustomImageBackground";
import * as ConfigService from "../../services/launcher-config-service";
import { SocialsModal } from "../modals/SocialsModal";
import { checkUpdateAvailable, downloadAndInstallUpdate } from "../../services/nrc-service";
Expand Down Expand Up @@ -64,7 +65,7 @@ export function AppLayout({
const minimizeRef = useRef<HTMLDivElement>(null);
const maximizeRef = useRef<HTMLDivElement>(null);
const closeRef = useRef<HTMLDivElement>(null);
const { currentEffect } = useBackgroundEffectStore();
const { currentEffect, customBackgroundImage, backgroundImageOpacity, backgroundImageBlur, backgroundImageScale } = useBackgroundEffectStore();
const { qualityLevel } = useQualitySettingsStore();
const { isBackgroundAnimationEnabled, accentColor: themeAccentColor, accentColor } = useThemeStore();

Expand Down Expand Up @@ -253,6 +254,15 @@ export function AppLayout({
);
case BACKGROUND_EFFECTS.PLAIN_BACKGROUND:
return <PlainBackground accentColorValue={themeAccentColor.value} />;
case BACKGROUND_EFFECTS.CUSTOM_IMAGE:
return (
<CustomImageBackground
imagePath={customBackgroundImage}
opacity={backgroundImageOpacity}
blur={backgroundImageBlur}
scale={backgroundImageScale}
/>
);
default:
return (
<div className="absolute inset-0 bg-red-500/20">
Expand All @@ -267,10 +277,10 @@ export function AppLayout({
ref={launcherRef}
className="h-screen w-full bg-black/50 backdrop-blur-lg border-2 overflow-hidden relative flex shadow-[0_0_25px_rgba(0,0,0,0.4)]"
style={{
backgroundColor: backgroundColor,
backgroundColor: currentEffect === BACKGROUND_EFFECTS.CUSTOM_IMAGE ? 'transparent' : backgroundColor,
backgroundSize: "cover",
backgroundPosition: "center",
backgroundImage: `linear-gradient(to bottom right, ${backgroundColor}, rgba(0,0,0,0.9))`,
backgroundImage: currentEffect === BACKGROUND_EFFECTS.CUSTOM_IMAGE ? 'none' : `linear-gradient(to bottom right, ${backgroundColor}, rgba(0,0,0,0.9))`,
borderColor: `${themeAccentColor.value}30`,
boxShadow: `0 0 15px ${themeAccentColor.value}30, inset 0 0 10px ${themeAccentColor.value}20`,
}}
Expand All @@ -295,7 +305,7 @@ export function AppLayout({
<div className="flex-1 relative overflow-hidden">
{renderBackgroundEffect()}

<div className="relative z-10 h-full overflow-hidden custom-scrollbar">
<div className="relative z-20 h-full overflow-hidden custom-scrollbar">
{children}
</div>
</div>
Expand Down
Loading