mirror of
				https://github.com/Vomitblood/stort.git
				synced 2025-10-31 18:57:21 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			263 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			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>
 | |
|   );
 | |
| };
 |