Files
Firefly_Launcher/frontend/src/pages/diff/index.tsx

352 lines
16 KiB
TypeScript

import useSettingStore from "@/stores/settingStore"
import { Check, Folder, File, X, Settings } from "lucide-react"
import { useEffect } from "react"
import { toast } from "react-toastify"
import { DiffService} from "@bindings/firefly-launcher/internal/diff-service"
import { FSService } from "@bindings/firefly-launcher/internal/fs-service"
import { motion } from "motion/react"
import useDiffStore from "@/stores/diffStore"
export default function DiffPage() {
const { gameDir, setGameDir } = useSettingStore()
const {
isLoading,
setIsLoading,
folderCheckResult,
setFolderCheckResult,
diffDir,
setDiffDir,
diffCheckResult,
setDiffCheckResult,
isDiffLoading,
setIsDiffLoading,
progressUpdate,
setProgressUpdate,
maxProgressUpdate,
setMaxProgressUpdate,
stageType,
setStageType,
messageUpdate,
setMessageUpdate
} = useDiffStore()
useEffect(() => {
const getLanguage = async () => {
if (gameDir) {
const subPath = 'StarRail_Data/StreamingAssets/DesignData/Windows'
const fullPath = `${gameDir}/${subPath}`
const exists = await FSService.DirExists(fullPath)
if (exists) {
setFolderCheckResult('success')
} else {
setFolderCheckResult('error')
setGameDir('')
}
}
}
getLanguage()
}, [gameDir])
const handlePickGameFolder = async () => {
try {
setIsLoading({game: true, diff: false})
const basePath = await FSService.PickFolder()
if (basePath) {
setGameDir(basePath)
const subPath = 'StarRail_Data/StreamingAssets/DesignData/Windows'
const fullPath = `${basePath}/${subPath}`
const exists = await FSService.DirExists(fullPath)
setFolderCheckResult(exists ? 'success' : 'error')
setGameDir(exists ? basePath : '')
if (!exists) {
toast.error('Game directory not found. Please select the correct folder.')
}
} else {
toast.error('No folder path selected')
setFolderCheckResult('error')
setGameDir('')
}
} catch (err: any) {
toast.error('PickFolder error:', err)
setFolderCheckResult('error')
} finally {
setIsLoading({game: false, diff: false})
}
}
const handlePickDiffFile = async () => {
try {
setIsLoading({game: false, diff: true})
const basePath = await FSService.PickFile("")
if (basePath) {
if (!basePath.endsWith(".7z") && !basePath.endsWith(".zip") && !basePath.endsWith(".rar")) {
toast.error('Not valid file type')
setDiffCheckResult('error')
setDiffDir('')
return
}
setDiffDir(basePath)
setDiffCheckResult('success')
} else {
toast.error('No file path selected')
setDiffCheckResult('error')
setDiffDir('')
}
} catch (err: any) {
toast.error('PickFile error:', err)
setDiffCheckResult('error')
} finally {
setIsLoading({game: false, diff: false})
}
}
const handleUpdateGame = async () => {
const handleResult = (ok: boolean, error: string) => {
if (!ok) {
toast.error(error)
return false
}
return true
}
try {
setIsDiffLoading(true)
if (!gameDir || !diffDir) {
toast.error('Please select game directory and diff file')
return
}
setStageType('Check Type HDiff')
setProgressUpdate(0)
setMaxProgressUpdate(1)
const [isOk, validType, errorType] = await DiffService.CheckTypeHDiff(diffDir)
if (!handleResult(isOk, errorType)) return
setProgressUpdate(1)
if (['hdiffmap.json', 'hdifffiles.txt', 'hdifffiles.json'].includes(validType)) {
setStageType('Version Validate')
setProgressUpdate(0)
setMaxProgressUpdate(1)
const [validVersion, errorVersion] = await DiffService.VersionValidate(gameDir, diffDir)
if (!handleResult(validVersion, errorVersion)) return
setProgressUpdate(1)
}
setStageType('Data Extract')
const [validData, errorData] = await DiffService.DataExtract(gameDir, diffDir)
if (!handleResult(validData, errorData)) return
setStageType('Cut Data')
setMessageUpdate('')
const [validCut, errorCut] = await DiffService.CutData(gameDir)
if (!handleResult(validCut, errorCut)) return
switch (validType) {
case 'hdifffiles.txt':
case 'hdiffmap.json':
case 'hdifffiles.json': {
setStageType('Patch Data')
const [validPatch, errorPatch] = await DiffService.HDiffPatchData(gameDir)
if (!handleResult(validPatch, errorPatch)) return
setStageType('Delete old files')
const [validDelete, errorDelete] = await DiffService.DeleteFiles(gameDir)
if (!handleResult(validDelete, errorDelete)) return
break
}
case 'manifest': {
setStageType('Patch Data')
const [validPatch, errorPatch] = await DiffService.LDiffPatchData(gameDir)
if (!handleResult(validPatch, errorPatch)) return
break
}
}
toast.success('Update game completed')
} catch (err: any) {
console.error(err)
toast.error(`PickFile error: ${err}`)
} finally {
setIsDiffLoading(false)
}
}
return (
<div className="p-2 mx-4">
<div className="max-w-4xl mx-auto">
{/* Header */}
<div className="text-center mb-2">
<h1 className="text-4xl font-bold mb-2">
🎮 Game Update by Hdiffz
</h1>
<p className="">Help you update game with hdiffz</p>
</div>
{/* Main Content */}
<div className="rounded-2xl p-2 space-y-4">
{/* Folder Selection Section */}
<div className="pb-2">
<h2 className="text-2xl font-semibold mb-4 flex items-center gap-2">
<Folder className="text-primary" size={24} />
Game Directory
</h2>
<div className="space-y-1">
<div className='grid grid-cols-1 md:grid-cols-2 gap-2 items-center'>
<button
onClick={handlePickGameFolder}
disabled={isLoading.game}
className="btn btn-primary"
>
<Folder size={20} />
{isLoading.game ? 'Selecting...' : 'Select Game Folder'}
</button>
{gameDir && (
<div className="rounded-lg p-2">
<p className="font-mono text-sm px-3 py-2 rounded border truncate max-w-full overflow-hidden whitespace-nowrap">
{gameDir}
</p>
</div>
)}
</div>
{folderCheckResult && (
<div className={`flex items-center gap-2 p-3 rounded-lg ${folderCheckResult === 'success'
? 'bg-success/5 text-success border border-success'
: 'bg-error/5 text-error border border-error'
}`}>
{folderCheckResult === 'success' ? (
<>
<Check size={20} />
<span>Valid game directory found!</span>
</>
) : (
<>
<X size={20} />
<span>Game directory not found. Please select the correct folder.</span>
</>
)}
</div>
)}
</div>
</div>
{/* Folder Selection Section */}
<div className="pb-2">
<h2 className="text-2xl font-semibold mb-4 flex items-center gap-2">
<File className="text-primary" size={24} />
Diff file Directory
</h2>
<div className="space-y-1">
<div className='grid grid-cols-1 md:grid-cols-2 gap-2 items-center'>
<button
onClick={handlePickDiffFile}
disabled={isLoading.diff}
className="btn btn-primary"
>
<File size={20} />
{isLoading.diff ? 'Selecting...' : 'Select Diff file Folder'}
</button>
{diffDir && (
<div className="rounded-lg p-2">
<p className="font-mono text-sm px-3 py-2 rounded border truncate max-w-full overflow-hidden whitespace-nowrap">
{diffDir}
</p>
</div>
)}
</div>
{diffCheckResult && (
<div className={`flex items-center gap-2 p-3 mt-2 rounded-lg ${diffCheckResult === 'success'
? 'bg-success/5 text-success border border-success'
: 'bg-error/5 text-error border border-error'
}`}>
{diffCheckResult === 'success' ? (
<>
<Check size={20} />
<span>Valid diff file found!</span>
</>
) : (
<>
<X size={20} />
<span>Diff file not found. Please select the correct file.</span>
</>
)}
</div>
)}
</div>
{/* Apply Button */}
<div className="mt-6 flex justify-center">
<button
onClick={handleUpdateGame}
disabled={!diffDir || !gameDir || isLoading.game || isLoading.diff}
className="bg-gradient-to-r from-indigo-500 to-purple-600 hover:from-indigo-600 hover:to-purple-700 disabled:from-gray-400 disabled:to-gray-500 text-white px-8 py-3 rounded-lg font-medium transition-all duration-200 flex items-center gap-2 shadow-lg hover:shadow-xl disabled:cursor-not-allowed cursor-pointer"
>
<Settings size={20} />
{isDiffLoading ? 'Updating...' : 'Update Game'}
</button>
</div>
</div>
{isDiffLoading && (
<div className="fixed inset-0 z-50 h-full flex items-center justify-center bg-black/40 backdrop-blur-sm">
<div className="relative w-[90%] max-w-5xl bg-base-100 text-base-content rounded-xl border border-purple-500/50 shadow-lg shadow-purple-500/20">
<div className="border-b border-purple-500/30 px-6 py-4 mb-4 text-center">
<h3 className="font-bold text-2xl text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-cyan-400">
Update Game
</h3>
</div>
<div className="px-6 pb-6">
<div className="w-full p-4">
<div className="space-y-3">
<div className="flex justify-center items-center text-sm text-white/80">
<span className="font-bold text-lg text-accent">{stageType}:</span>
<div className="flex items-center gap-4 ml-2">
{stageType !== 'Cut Data' && <span className="text-white font-bold">{progressUpdate.toFixed(0)} / {maxProgressUpdate.toFixed(0)}</span>}
{stageType === 'Cut Data' && <span className="text-white font-bold truncate max-w-full overflow-hidden whitespace-nowrap">{messageUpdate}</span>}
</div>
</div>
<div className="w-full bg-white/20 rounded-full h-2 overflow-hidden">
<motion.div
className="h-full bg-gradient-to-r from-cyan-400 to-blue-500 rounded-full"
initial={{ width: 0 }}
animate={{ width: `${(progressUpdate/maxProgressUpdate)*100}%` }}
transition={{ duration: 0.3 }}
/>
</div>
<div className="text-center text-lg text-white/60">
Please wait...
</div>
</div>
</div>
</div>
</div>
</div>
)}
{/* Instructions */}
<div className="bg-info/5 rounded-lg p-4 border border-info/30 mt-6">
<h3 className="font-medium text-error mb-2">📋 Instructions:</h3>
<ol className="text-sm text-error space-y-1">
<li>1. Click "Select Game Folder" and choose your game's root directory</li>
<li>2. Wait for the system to validate the game directory</li>
<li>3. Click "Select Diff file Folder" and choose your diff file's root directory</li>
<li>4. Wait for the system to validate the diff file directory</li>
<li>5. Click "Update Game" to save your changes</li>
</ol>
</div>
</div>
</div>
</div>
)
}