mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-06-06 03:34:40 +00:00
separate backgroundselector file
This commit is contained in:
@@ -3,6 +3,11 @@ import { downloadPresetBackground, openDB, readAllData, writeData } from "../hoo
|
||||
import presetBackgrounds from "../assets/presetBackgrounds";
|
||||
import "./BackgroundSelector.css";
|
||||
import { disableTheme } from "../hooks/ThemeManagment";
|
||||
import { DownloadProgressCircle } from "./backgroundSelector/DownloadProgressCircle";
|
||||
import { BackgroundSwatch } from "./backgroundSelector/BackgroundSwatch";
|
||||
import { BackgroundList } from "./backgroundSelector/BackgroundList";
|
||||
import { FileUploader } from "./backgroundSelector/FileUploader";
|
||||
import { RemoveButton } from "./backgroundSelector/RemoveButton";
|
||||
|
||||
// Custom Types and Interfaces
|
||||
export interface Background {
|
||||
@@ -99,8 +104,6 @@ export default function BackgroundSelector({ selectedType, setSelectedType, isEd
|
||||
setSelectedBackground(null);
|
||||
localStorage.removeItem('selectedBackground');
|
||||
};
|
||||
|
||||
const calcCircumference = (radius: number) => 2 * Math.PI * radius;
|
||||
|
||||
useEffect(() => {
|
||||
loadBackgrounds();
|
||||
@@ -108,101 +111,67 @@ export default function BackgroundSelector({ selectedType, setSelectedType, isEd
|
||||
|
||||
return (
|
||||
<>
|
||||
<button disabled={selectedBackground == null ? true : false} className={`w-full px-4 py-2 mb-4 dark:text-white transition ${selectedBackground == null ? 'dark:bg-zinc-900 bg-zinc-100' : 'bg-blue-500 text-white'} rounded`} onClick={() => selectNoBackground()}>
|
||||
{selectedBackground == null ? 'No Background' : 'Remove Background'}
|
||||
</button>
|
||||
<div className="relative">
|
||||
<h2 className="pb-2 text-lg font-bold">Images</h2>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
{/* Image uploader swatch */}
|
||||
<div className="relative w-16 h-16 overflow-hidden transition rounded-xl bg-zinc-100 dark:bg-zinc-900">
|
||||
<div className="flex items-center justify-center w-full h-full text-3xl font-bold text-gray-400 transition font-IconFamily hover:text-gray-500">
|
||||
{/* Plus icon */}
|
||||
|
||||
</div>
|
||||
<input type="file" accept='image/*, video/*' onChange={handleFileChange} className="absolute inset-0 w-full h-full opacity-0 cursor-pointer" />
|
||||
</div>
|
||||
{backgrounds.filter(bg => bg.type === 'image').map(bg => (
|
||||
<div key={bg.id}
|
||||
onClick={() => selectBackground(bg.id)}
|
||||
className={`relative w-16 h-16 cursor-pointer rounded-xl transition ring dark:ring-white ring-zinc-300 ${isEditMode ? 'animate-shake' : ''} ${selectedBackground === bg.id && selectedType === "background" ? 'dark:ring-2 ring-4' : 'ring-0'}`}>
|
||||
{isEditMode && (
|
||||
<div className="absolute top-0 right-0 z-10 flex w-6 h-6 p-2 text-white translate-x-1/2 -translate-y-1/2 bg-red-600 rounded-full place-items-center"
|
||||
onClick={() => deleteBackground(bg.id)}>
|
||||
<div className="w-4 h-0.5 bg-white"></div>
|
||||
</div>
|
||||
)}
|
||||
<img className="object-cover w-full h-full rounded-xl" src={bg.url} alt="swatch" />
|
||||
</div>
|
||||
))}
|
||||
{backgrounds.concat(presetBackgrounds as Background[]).filter(bg => bg.type === 'image' && bg.isPreset && !bg.isDownloaded && !downloadedPresetIds.includes(bg.id)).map(bg => (
|
||||
<div key={bg.id}
|
||||
onClick={() => handlePresetClick(bg)}
|
||||
className={`relative w-16 h-16 transition cursor-pointer rounded-xl duration-300 ${ isEditMode ? 'opacity-0 pointer-events-none hidden' : 'opacity-100'}`}>
|
||||
{bg.isPreset && downloadProgress[bg.id] !== undefined && (
|
||||
<div className="absolute top-0 left-0 z-20 flex items-center justify-center w-full h-full">
|
||||
<svg className="w-full h-full text-zinc-100 dark:text-zinc-700" viewBox="0 0 36 36">
|
||||
<circle stroke="currentColor" fill="none" strokeWidth="4" strokeLinecap="round" cx="18" cy="18" r="10" strokeDasharray={`${calcCircumference(14)} ${calcCircumference(14)}`} strokeDashoffset="0" transform="rotate(-90 18 18)"></circle>
|
||||
<circle stroke="#3B82F6" fill="none" strokeWidth="4" strokeLinecap="round" cx="18" cy="18" r="10" strokeDasharray={`${calcCircumference(14)} ${calcCircumference(14)}`} strokeDashoffset={`${calcCircumference(14) * (1 - (downloadProgress[bg.id] / 100))}`} transform="rotate(-90 18 18)"></circle>
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
<div className={`relative transition top-0 z-10 flex justify-center w-full h-full text-white rounded-xl group place-items-center ${downloadProgress[bg.id] === undefined ? 'hover:bg-black/20' : ''}`}>
|
||||
<span className="absolute z-10 text-3xl transition opacity-0 font-IconFamily group-hover:opacity-100">
|
||||
{downloadProgress[bg.id] === undefined ? '' : ''}
|
||||
</span>
|
||||
</div>
|
||||
<img
|
||||
className="absolute top-0 object-cover w-full h-full rounded-xl"
|
||||
src={bg.isPreset ? bg.previewUrl : bg.url} // Use preview for preset backgrounds
|
||||
alt="swatch" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<RemoveButton selectedBackground={selectedBackground} selectNoBackground={selectNoBackground} />
|
||||
|
||||
<h2 className="py-2 text-lg font-bold">Videos</h2>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
{/* Video uploader swatch */}
|
||||
<div className="relative w-16 h-16 overflow-hidden transition rounded-xl bg-zinc-100 dark:bg-zinc-900">
|
||||
<div className="flex items-center justify-center w-full h-full text-3xl font-bold text-gray-400 transition font-IconFamily hover:text-gray-500">
|
||||
{/* Plus icon */}
|
||||
|
||||
</div>
|
||||
<input type="file" accept='image/*, video/*' onChange={handleFileChange} className="absolute inset-0 w-full h-full opacity-0 cursor-pointer" />
|
||||
<div className="relative">
|
||||
<h2 className="pb-2 text-lg font-bold">Images</h2>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<FileUploader handleFileChange={handleFileChange} />
|
||||
<BackgroundList
|
||||
backgrounds={backgrounds.filter(bg => bg.type === 'image')}
|
||||
selectBackground={selectBackground}
|
||||
isEditMode={isEditMode}
|
||||
deleteBackground={deleteBackground}
|
||||
selectedBackground={selectedBackground}
|
||||
selectedType={selectedType}
|
||||
/>
|
||||
{/* Preset backgrounds handling (images) */}
|
||||
{presetBackgrounds
|
||||
.filter(bg => bg.type === 'image' && bg.isPreset && !downloadedPresetIds.includes(bg.id))
|
||||
.map(bg => (
|
||||
<div key={bg.id} onClick={() => handlePresetClick(bg)} className="relative w-16 h-16">
|
||||
{downloadProgress[bg.id] !== undefined && <DownloadProgressCircle progress={downloadProgress[bg.id]} />}
|
||||
<BackgroundSwatch
|
||||
background={bg}
|
||||
selectBackground={selectBackground}
|
||||
isEditMode={isEditMode}
|
||||
deleteBackground={deleteBackground}
|
||||
selectedBackground={selectedBackground}
|
||||
selectedType={selectedType}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{backgrounds.filter(bg => bg.type === 'video').map(bg => (
|
||||
<div key={bg.id} onClick={() => selectBackground(bg.id)} className={`relative w-16 h-16 cursor-pointer rounded-xl transition ring dark:ring-white ring-zinc-300 ${isEditMode ? 'animate-shake' : ''} ${selectedBackground === bg.id && selectedType === "background" ? 'dark:ring-2 ring-4' : 'ring-0'}`}>
|
||||
{isEditMode && (
|
||||
<div className="absolute top-0 right-0 z-10 flex w-6 h-6 p-2 text-white translate-x-1/2 -translate-y-1/2 bg-red-600 rounded-full place-items-center"
|
||||
onClick={() => deleteBackground(bg.id)}>
|
||||
<div className="w-4 h-0.5 bg-white"></div>
|
||||
|
||||
<h2 className="py-2 text-lg font-bold">Videos</h2>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<FileUploader handleFileChange={handleFileChange} />
|
||||
<BackgroundList
|
||||
backgrounds={backgrounds.filter(bg => bg.type === 'video')}
|
||||
selectBackground={selectBackground}
|
||||
isEditMode={isEditMode}
|
||||
deleteBackground={deleteBackground}
|
||||
selectedBackground={selectedBackground}
|
||||
selectedType={selectedType}
|
||||
/>
|
||||
{/* Preset backgrounds handling (videos) */}
|
||||
{presetBackgrounds
|
||||
.filter(bg => bg.type === 'video' && bg.isPreset && !downloadedPresetIds.includes(bg.id))
|
||||
.map(bg => (
|
||||
<div key={bg.id} onClick={() => handlePresetClick(bg)} className="relative w-16 h-16">
|
||||
{downloadProgress[bg.id] !== undefined && <DownloadProgressCircle progress={downloadProgress[bg.id]} />}
|
||||
<BackgroundSwatch
|
||||
background={bg}
|
||||
selectBackground={selectBackground}
|
||||
isEditMode={isEditMode}
|
||||
deleteBackground={deleteBackground}
|
||||
selectedBackground={selectedBackground}
|
||||
selectedType={selectedType}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<video muted loop autoPlay src={bg.url} className="object-cover w-full h-full rounded-xl" />
|
||||
</div>
|
||||
))}
|
||||
{backgrounds.concat(presetBackgrounds as Background[]).filter(bg => bg.type === 'video' && bg.isPreset && !bg.isDownloaded && !downloadedPresetIds.includes(bg.id)).map(bg => (
|
||||
<div key={bg.id}
|
||||
onClick={() => handlePresetClick(bg)}
|
||||
className={`relative w-16 h-16 transition cursor-pointer rounded-xl duration-300 ${ isEditMode ? 'opacity-0 pointer-events-none hidden' : 'opacity-100'}`}>
|
||||
{bg.isPreset && downloadProgress[bg.id] !== undefined && (
|
||||
<div className="absolute top-0 left-0 z-20 flex items-center justify-center w-full h-full">
|
||||
<svg className="w-full h-full text-zinc-100 dark:text-zinc-700" viewBox="0 0 36 36">
|
||||
<circle stroke="currentColor" fill="none" strokeWidth="4" strokeLinecap="round" cx="18" cy="18" r="10" strokeDasharray={`${calcCircumference(14)} ${calcCircumference(14)}`} strokeDashoffset="0" transform="rotate(-90 18 18)"></circle>
|
||||
<circle stroke="#3B82F6" fill="none" strokeWidth="4" strokeLinecap="round" cx="18" cy="18" r="10" strokeDasharray={`${calcCircumference(14)} ${calcCircumference(14)}`} strokeDashoffset={`${calcCircumference(14) * (1 - (downloadProgress[bg.id] / 100))}`} transform="rotate(-90 18 18)"></circle>
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
<div className={`relative transition top-0 z-10 flex justify-center w-full h-full text-white rounded-xl group place-items-center ${downloadProgress[bg.id] === undefined ? 'hover:bg-black/20' : ''}`}>
|
||||
<span className="absolute z-10 text-3xl transition opacity-0 font-IconFamily group-hover:opacity-100">
|
||||
{downloadProgress[bg.id] === undefined ? '' : ''}
|
||||
</span>
|
||||
</div>
|
||||
<video muted loop autoPlay src={bg.isPreset ? bg.previewUrl : bg.url} className="absolute top-0 object-cover w-full h-full rounded-xl" />
|
||||
</div>
|
||||
))}
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import { BackgroundSwatch } from './BackgroundSwatch';
|
||||
import { Background } from '../BackgroundSelector'; // Import the Background interface
|
||||
|
||||
interface BackgroundListProps {
|
||||
backgrounds: Background[];
|
||||
selectBackground: (fileId: string) => void;
|
||||
isEditMode: boolean;
|
||||
deleteBackground: (fileId: string) => Promise<void>;
|
||||
selectedBackground: string | null;
|
||||
selectedType: 'background' | 'theme';
|
||||
}
|
||||
|
||||
export const BackgroundList: React.FC<BackgroundListProps> = ({ backgrounds, selectBackground, isEditMode, deleteBackground, selectedBackground, selectedType }) => {
|
||||
return (
|
||||
<>
|
||||
{backgrounds.map(bg => (
|
||||
<BackgroundSwatch
|
||||
key={bg.id}
|
||||
background={bg}
|
||||
selectBackground={selectBackground}
|
||||
isEditMode={isEditMode}
|
||||
deleteBackground={deleteBackground}
|
||||
selectedBackground={selectedBackground}
|
||||
selectedType={selectedType}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
|
||||
interface BackgroundSwatchProps {
|
||||
background: {
|
||||
id: string;
|
||||
type: string;
|
||||
url: string;
|
||||
previewUrl: string;
|
||||
isPreset: boolean;
|
||||
};
|
||||
selectBackground: (fileId: string) => void;
|
||||
isEditMode: boolean;
|
||||
deleteBackground: (fileId: string) => Promise<void>;
|
||||
selectedBackground: string | null;
|
||||
selectedType: 'background' | 'theme';
|
||||
}
|
||||
|
||||
export const BackgroundSwatch: React.FC<BackgroundSwatchProps> = ({ background, selectBackground, isEditMode, deleteBackground, selectedBackground, selectedType }) => {
|
||||
const { id, url, type } = background;
|
||||
const isSelected = selectedBackground === id && selectedType === "background";
|
||||
|
||||
return (
|
||||
<div key={id} onClick={() => selectBackground(id)} className={`relative w-16 h-16 cursor-pointer rounded-xl transition ring dark:ring-white ring-zinc-300 ${isEditMode ? 'animate-shake' : ''} ${isSelected ? 'dark:ring-2 ring-4' : 'ring-0'}`}>
|
||||
{isEditMode && (
|
||||
<div className="absolute top-0 right-0 z-10 flex w-6 h-6 p-2 text-white translate-x-1/2 -translate-y-1/2 bg-red-600 rounded-full place-items-center"
|
||||
onClick={() => deleteBackground(id)}>
|
||||
<div className="w-4 h-0.5 bg-white"></div>
|
||||
</div>
|
||||
)}
|
||||
{type === 'image' ?
|
||||
<img className="object-cover w-full h-full rounded-xl" src={url} alt="swatch" /> :
|
||||
<video muted loop autoPlay src={url} className="object-cover w-full h-full rounded-xl" />
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
|
||||
interface DownloadProgressCircleProps {
|
||||
progress: number;
|
||||
}
|
||||
|
||||
export const DownloadProgressCircle: React.FC<DownloadProgressCircleProps> = ({ progress }) => {
|
||||
const calcCircumference = (radius: number) => 2 * Math.PI * radius;
|
||||
|
||||
return (
|
||||
<svg className="w-full h-full text-zinc-100 dark:text-zinc-700" viewBox="0 0 36 36">
|
||||
<circle stroke="currentColor" fill="none" strokeWidth="4" strokeLinecap="round" cx="18" cy="18" r="10" strokeDasharray={`${calcCircumference(14)} ${calcCircumference(14)}`} strokeDashoffset="0" transform="rotate(-90 18 18)"></circle>
|
||||
<circle stroke="#3B82F6" fill="none" strokeWidth="4" strokeLinecap="round" cx="18" cy="18" r="10" strokeDasharray={`${calcCircumference(14)} ${calcCircumference(14)}`} strokeDashoffset={`${calcCircumference(14) * (1 - (progress / 100))}`} transform="rotate(-90 18 18)"></circle>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
import React, { ChangeEvent } from 'react';
|
||||
|
||||
interface FileUploaderProps {
|
||||
handleFileChange: (e: ChangeEvent<HTMLInputElement>) => Promise<void>;
|
||||
}
|
||||
|
||||
export const FileUploader: React.FC<FileUploaderProps> = ({ handleFileChange }) => {
|
||||
return (
|
||||
<div className="relative w-16 h-16 overflow-hidden transition rounded-xl bg-zinc-100 dark:bg-zinc-900">
|
||||
<div className="flex items-center justify-center w-full h-full text-3xl font-bold text-gray-400 transition font-IconFamily hover:text-gray-500">
|
||||
{/* Plus icon */}
|
||||
|
||||
</div>
|
||||
<input type="file" accept='image/*, video/*' onChange={handleFileChange} className="absolute inset-0 w-full h-full opacity-0 cursor-pointer" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
|
||||
interface RemoveButtonProps {
|
||||
selectedBackground: string | null;
|
||||
selectNoBackground: () => void;
|
||||
}
|
||||
|
||||
export const RemoveButton: React.FC<RemoveButtonProps> = ({ selectedBackground, selectNoBackground }) => {
|
||||
return (
|
||||
<button disabled={selectedBackground == null} className={`w-full px-4 py-2 mb-4 dark:text-white transition ${selectedBackground == null ? 'dark:bg-zinc-900 bg-zinc-100' : 'bg-blue-500 text-white'} rounded`} onClick={selectNoBackground}>
|
||||
{selectedBackground == null ? 'No Background' : 'Remove Background'}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user