Files
upload-gltf/components/UploadZone.tsx
T
2026-05-13 17:50:26 +02:00

192 lines
6.4 KiB
TypeScript

'use client'
import type { FolderEntry } from '@/lib/client-types'
import { revokeEntryUrls } from '@/lib/client-object-urls'
import { useSecret } from '@/hooks/useSecret'
import { useFolderEntries } from '@/hooks/useFolderEntries'
import { useUploadOrchestrator } from '@/hooks/useUploadOrchestrator'
import SecretInput from './upload/SecretInput'
import FolderDropzone from './upload/FolderDropzone'
import FolderCard from './upload/FolderCard'
import ActionButtons from './upload/ActionButtons'
import OverwriteConfirmModal from './upload/OverwriteConfirmModal'
import NoChangesModal from './upload/NoChangesModal'
import DriveErrorModal from './upload/DriveErrorModal'
export default function UploadZone() {
const {
secret,
secretError,
secretVisible,
isSecretEmpty,
setSecretError,
handleSecretChange,
toggleSecretVisible,
} = useSecret()
const {
entries,
setEntries,
updateEntry,
removeEntry,
resetEntries,
allDone,
hasErrors,
} = useFolderEntries()
const {
isUploading,
isChecking,
isResolvingDriveError,
globalError,
setGlobalError,
overwriteConfirm,
noChangesFolder,
setNoChangesFolder,
driveError,
handleUpload,
handleOverwriteCancel,
handleDriveContinue,
handleDriveCancel,
handleCancel,
handleReset,
proceedUpload,
} = useUploadOrchestrator({
secret,
setSecretError,
entries,
updateEntry,
resetEntries,
})
const handleFolderSelected = (entry: FolderEntry) => {
entries.forEach(revokeEntryUrls)
setGlobalError(null)
setEntries([entry])
}
const handleToggleViewer = (index: number) => {
const entry = entries[index]
if (entry?.modelUrl) {
updateEntry(index, { viewerOpen: !entry.viewerOpen })
}
}
const hasPendingOrErrors = entries.some((f) => f.status === 'pending' || f.status === 'error')
return (
<div className="w-full max-w-2xl space-y-4">
{entries.length === 0 && (
<p className="rounded-2xl border border-white/20 bg-black-800 px-4 py-3 text-xs text-gray-400 leading-relaxed text-center mb-3">
Deposez un dossier complet contenant votre modele 3D nomme
{' '}<span className="font-mono text-gray-200">model.gltf</span>
{' '}ainsi que toutes les textures et fichiers binaires necessaires.
{' '}Les fichiers associes peuvent etre en
{' '}<span className="font-mono text-gray-200">.png</span>,
{' '}<span className="font-mono text-gray-200">.jpg</span>
{' '}<span className="font-mono text-gray-200">.webp</span>
{' '}ou <span className="font-mono text-gray-200">.bin</span>.
{' '}Utilisez un nom simple si la texture s&apos;applique au modele entier, et un nom detaille si elle correspond a une partie precise du modele,
{' '}par exemple <span className="font-mono text-gray-200">color_porte.jpg</span>,
{' '}<span className="font-mono text-gray-200">roughness_tuyaux.png</span>,
{' '}<span className="font-mono text-gray-200">normal_dashboard.webp</span>
{' '}ou <span className="font-mono text-gray-200">opacity_fenetre.png</span>.
{' '}Les exports classiques comme <span className="font-mono text-gray-200">porte_baseColor.png</span>
{' '}ou <span className="font-mono text-gray-200">porte_normal_opengl.png</span> sont normalises automatiquement pour Git.
</p>
)}
{entries.length > 0 && (
<p className="rounded-2xl border border-white/20 bg-black-800 px-4 py-3 text-xs text-gray-400 leading-relaxed text-center mb-3">
Plus votre dossier est <span className="font-semibold text-gray-200">volumineux</span> et contient de nombreux fichiers,
{' '}plus l&apos;<span className="font-semibold text-gray-200">upload</span> peut prendre du temps.
{' '}L&apos;envoi se fait en <span className="font-semibold text-gray-200">3 etapes</span> :
{' '}<span className="font-semibold text-gray-200">verification</span> du dossier existant,
{' '}<span className="font-semibold text-gray-200">archivage</span> sur le Drive,
{' '}puis <span className="font-semibold text-gray-200">envoi</span> sur Git.
{' '}Soyez patient pendant le traitement, surtout pour les <span className="font-semibold text-gray-200">gros assets</span> !
</p>
)}
<SecretInput
secret={secret}
secretVisible={secretVisible}
secretError={secretError}
disabled={isUploading || isChecking}
onChange={handleSecretChange}
onToggleVisible={toggleSecretVisible}
/>
{entries.length === 0 && (
<div>
<FolderDropzone
isUploading={isUploading || isChecking}
onFolderSelected={handleFolderSelected}
onError={setGlobalError}
/>
</div>
)}
{globalError && (
<p className="text-xs text-red-400 text-center">{globalError}</p>
)}
{entries.length > 0 && (
<div className="space-y-2">
{entries.map((entry, i) => (
<FolderCard
key={i}
entry={entry}
index={i}
onToggleViewer={handleToggleViewer}
onRemove={removeEntry}
/>
))}
</div>
)}
<ActionButtons
isUploading={isUploading}
isChecking={isChecking}
isSecretEmpty={isSecretEmpty}
hasPendingOrErrors={hasPendingOrErrors}
allDone={allDone}
hasErrors={hasErrors}
onUpload={handleUpload}
onCancel={handleCancel}
onReset={handleReset}
/>
{overwriteConfirm && (
<OverwriteConfirmModal
folderName={overwriteConfirm.folderName}
diffs={overwriteConfirm.diffs}
onCancel={handleOverwriteCancel}
onConfirm={proceedUpload}
disabled={isUploading || isChecking || isResolvingDriveError}
/>
)}
{noChangesFolder && (
<NoChangesModal
folderName={noChangesFolder}
onCancel={() => {
setNoChangesFolder(null)
handleReset()
}}
onModify={() => setNoChangesFolder(null)}
/>
)}
{driveError && (
<DriveErrorModal
error={driveError.error}
onCancel={handleDriveCancel}
onContinue={handleDriveContinue}
disabled={isResolvingDriveError}
/>
)}
</div>
)
}