stort/src/components/HeaderBar/Settings/SettingsTabs/Background.tsx

263 lines
7.6 KiB
TypeScript

import { DeleteOutline, FileOpenOutlined } from "@mui/icons-material";
import { Box, Button, CircularProgress, LinearProgress, Stack, TextField, Typography } from "@mui/material";
import { open } from "@tauri-apps/api/dialog";
import { readBinaryFile } from "@tauri-apps/api/fs";
import { invoke } from "@tauri-apps/api/tauri";
import { useAtom } from "jotai";
import Image from "next/image";
import { FC, useEffect, useState } from "react";
import { useSettings } from "../../../../contexts/SettingsContext";
import { defaultSettings } from "../../../../lib/settings";
import { stagedSettingsAtom } from "../../../../lib/store/jotai/settings";
import { CategoryTitle } from "../CategoryTitle";
import { SettingsItem } from "../SettingsItem";
interface BackgroundProps {
sx?: any;
}
export const Background: FC<BackgroundProps> = ({ sx }) => {
// contexts
const { settings } = useSettings();
// atoms
const [stagedSettings, setStagedSettings] = useAtom(stagedSettingsAtom);
// states
const [oldWallpaperPath, setOldWallpaperPath] = useState<string | null>(null);
const [targetWallpaperPath, setTargetWallpaperPath] = useState<string | null>(null);
const [imageBlob, setImageBlob] = useState<string | null>(null);
const [noImageSelectedIcon, setNoImageSelectedIcon] = useState<string | null>(null);
const handleSettingsBackgroundValueChange = (
settingKey: string,
settingValue: boolean | number | string | number[],
) => {
const newSettings = {
...stagedSettings,
background: {
...stagedSettings.background,
[settingKey]: settingValue,
},
};
setStagedSettings(newSettings);
return newSettings;
};
const setImageSrc = async (filePath: string) => {
const imageBlobTemp = await readBinaryFile(filePath);
if (imageBlobTemp) setImageBlob(URL.createObjectURL(new Blob([imageBlobTemp])));
};
const selectImage = async () => {
const { appLocalDataDir, basename } = await import("@tauri-apps/api/path");
// clear the states first
setTargetWallpaperPath(null);
setImageBlob(null);
let selectedFilePath = await open({
multiple: false,
filters: [
{
name: "Images",
extensions: ["jpg", "png", "jpeg", "webp", "gif"],
},
],
});
// if the user somehow manages to select multiple files, take the first file
if (Array.isArray(selectedFilePath)) {
selectedFilePath = selectedFilePath[0];
}
if (selectedFilePath) {
setTargetWallpaperPath(selectedFilePath);
// construct the destination file path
const appLocalDataDirPath = await appLocalDataDir();
const filename = await basename(selectedFilePath);
const destinationFilePath = appLocalDataDirPath + "wallpaper/" + filename;
handleSettingsBackgroundValueChange("background_image_path", destinationFilePath);
}
};
const clearImage = async () => {
handleSettingsBackgroundValueChange("background_image_path", "");
};
// if settings.background.background_image_path changes, update the image
useEffect(() => {
const applyWallpaper = async () => {
// apply the new wallpaper image
try {
// if there is already a wallpaper file, delete it
if (settings.background.background_image_path && oldWallpaperPath) {
try {
await invoke("delete_old_wallpaper_images");
} catch (error) {
console.error("Failed to delete old wallpaper image", error);
}
}
if (targetWallpaperPath)
await invoke("process_wallpaper_image", {
filePathString: targetWallpaperPath,
});
} catch (error) {
console.error(error);
}
};
applyWallpaper();
setOldWallpaperPath(settings.background.background_image_path);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [settings.background.background_image_path]);
// update the preview image when stagedSettings.background.background_image_path changes
useEffect(() => {
if (targetWallpaperPath) {
setImageSrc(targetWallpaperPath);
} else {
setImageBlob(null);
}
}, [targetWallpaperPath]);
useEffect(() => {});
return (
<Box sx={{ sx }}>
<CategoryTitle title="Wallpaper" />
<Box
sx={{
position: "relative",
// dynamic width based on the parent container
width: "100%",
// 3:2 aspect ratio (2/3 = 66.67%) qwuik mafs
paddingBottom: "66.67%",
// Hide overflow to maintain aspect ratio
overflow: "hidden",
borderRadius: "8px", // Optional: rounded corners
}}
>
{imageBlob ? (
<Image
alt="Image not found"
// fill the box r/catsareliquid
layout="fill"
objectFit="cover"
src={imageBlob}
/>
) : (
<Box
sx={{
alignItems: "center",
backgroundColor: "rgba(0, 0, 0, 0.1)",
display: "flex",
flexDirection: "column",
height: "100%",
justifyContent: "center",
position: "absolute",
width: "100%",
}}
>
{targetWallpaperPath ? (
<CircularProgress color="primary" />
) : (
<>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
alt="Image not found"
src="/images/cry.webp"
style={{
height: "50%",
}}
/>
<Typography
color="text.disabled"
variant="h6"
>
No image selected
</Typography>
</>
)}
</Box>
)}
</Box>
<Box
sx={{
mb: 2,
mt: 1,
}}
>
<Stack
direction="row"
spacing={1}
>
<Button
color="primary"
onClick={() => {
selectImage();
}}
startIcon={<FileOpenOutlined />}
size="small"
variant="outlined"
>
Select
</Button>
<Button
color="warning"
onClick={clearImage}
startIcon={<DeleteOutline />}
size="small"
variant="outlined"
>
Clear
</Button>
</Stack>
</Box>
<CategoryTitle title="Colors" />
<SettingsItem
defaultText={defaultSettings.background.background_color}
description="Background color"
input={
<TextField
name="background_color"
onChange={(e) => {
handleSettingsBackgroundValueChange(e.target.name, e.target.value);
}}
sx={{
width: "100%",
}}
size="small"
type="color"
value={stagedSettings.background.background_color}
variant="standard"
/>
}
/>
<SettingsItem
defaultText={defaultSettings.background.background_color_popup}
description="Popup background color"
input={
<TextField
name="background_color_popup"
onChange={(e) => {
handleSettingsBackgroundValueChange(e.target.name, e.target.value);
}}
sx={{
width: "100%",
}}
size="small"
type="color"
value={stagedSettings.background.background_color_popup}
variant="standard"
/>
}
/>
</Box>
);
};