feat: drag and drop func
This commit is contained in:
@@ -32,6 +32,7 @@ import { UploadIcon } from '@modrinth/assets'
|
||||
import { useNotifications } from '@/store/state'
|
||||
import { getCurrentWebview } from '@tauri-apps/api/webview'
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import { get_dragged_skin_data } from '@/helpers/skins'
|
||||
|
||||
const notifications = useNotifications()
|
||||
|
||||
@@ -106,22 +107,24 @@ async function setupDragDropListener() {
|
||||
return
|
||||
}
|
||||
|
||||
// const filePath = event.payload.paths[0]
|
||||
const filePath = event.payload.paths[0]
|
||||
|
||||
try {
|
||||
// TODO: Drag and drop support for local files
|
||||
// const data = await readFile(filePath)
|
||||
//
|
||||
// const fileName = filePath.split('/').pop() || filePath.split('\\').pop() || 'skin.png'
|
||||
// const fileBlob = new Blob([data], { type: 'image/png' })
|
||||
// const file = new File([fileBlob], fileName, { type: 'image/png' })
|
||||
//
|
||||
// await processFile(file)
|
||||
console.log(filePath);
|
||||
const data = await get_dragged_skin_data(filePath).catch((err) => {
|
||||
throw new Error(`Failed to read file: ${err.message || err}`)
|
||||
})
|
||||
|
||||
const fileName = filePath.split('/').pop() || filePath.split('\\').pop() || 'skin.png'
|
||||
const fileBlob = new Blob([data], { type: 'image/png' })
|
||||
const file = new File([fileBlob], fileName, { type: 'image/png' })
|
||||
|
||||
await processFile(file)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
notifications.addNotification({
|
||||
title: 'Error processing file',
|
||||
text: 'Failed to read the dropped file.',
|
||||
text: error.message || 'Failed to read the dropped file.',
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
@@ -140,25 +143,6 @@ async function cleanupDragDropListener() {
|
||||
}
|
||||
|
||||
async function processFile(file: File) {
|
||||
if (!file.name.toLowerCase().endsWith('.png') && file.type !== 'image/png') {
|
||||
notifications.addNotification({
|
||||
title: 'Invalid file type.',
|
||||
text: 'Only PNG files are accepted.',
|
||||
type: 'error',
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const isValidDimensions = await validateImageDimensions(file)
|
||||
if (!isValidDimensions) {
|
||||
notifications.addNotification({
|
||||
title: 'Invalid dimensions.',
|
||||
text: 'Only 64x64 and 64x32 PNG files are accepted.',
|
||||
type: 'error',
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
emit('uploaded', file)
|
||||
hide()
|
||||
}
|
||||
|
||||
@@ -156,3 +156,7 @@ export async function normalize_skin_texture(texture: Uint8Array | string): Prom
|
||||
export async function unequip_skin(): Promise<void> {
|
||||
await invoke('plugin:minecraft-skins|unequip_skin')
|
||||
}
|
||||
|
||||
export async function get_dragged_skin_data(path: string): Promise<Uint8Array> {
|
||||
return invoke('plugin:minecraft-skins|get_dragged_skin_data', { path })
|
||||
}
|
||||
|
||||
@@ -111,6 +111,7 @@ fn main() {
|
||||
"remove_custom_skin",
|
||||
"unequip_skin",
|
||||
"normalize_skin_texture",
|
||||
"get_dragged_skin_data",
|
||||
])
|
||||
.default_permission(
|
||||
DefaultPermissionRule::AllowAllCommands,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::api::Result;
|
||||
|
||||
use std::path::Path;
|
||||
use theseus::minecraft_skins::{
|
||||
self, Bytes, Cape, MinecraftSkinVariant, Skin, UrlOrBlob,
|
||||
};
|
||||
@@ -15,6 +16,7 @@ pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
remove_custom_skin,
|
||||
unequip_skin,
|
||||
normalize_skin_texture,
|
||||
get_dragged_skin_data,
|
||||
])
|
||||
.build()
|
||||
}
|
||||
@@ -91,3 +93,12 @@ pub async fn unequip_skin() -> Result<()> {
|
||||
pub async fn normalize_skin_texture(texture: UrlOrBlob) -> Result<Bytes> {
|
||||
Ok(minecraft_skins::normalize_skin_texture(&texture).await?)
|
||||
}
|
||||
|
||||
/// `invoke('plugin:minecraft-skins|get_dragged_skin_data', path)`
|
||||
///
|
||||
/// See also: [minecraft_skins::get_dragged_skin_data]
|
||||
#[tauri::command]
|
||||
pub async fn get_dragged_skin_data(path: String) -> Result<Bytes> {
|
||||
let path = Path::new(&path);
|
||||
Ok(minecraft_skins::get_dragged_skin_data(path).await?)
|
||||
}
|
||||
|
||||
@@ -455,6 +455,47 @@ pub async fn normalize_skin_texture(
|
||||
png_util::normalize_skin_texture(texture).await
|
||||
}
|
||||
|
||||
/// Reads and validates a skin texture file from the given path.
|
||||
/// Returns the file content as bytes if it's a valid skin texture (PNG with 64x64 or 64x32 dimensions).
|
||||
#[tracing::instrument]
|
||||
pub async fn get_dragged_skin_data(path: &std::path::Path) -> crate::Result<Bytes> {
|
||||
if let Some(extension) = path.extension() {
|
||||
if extension.to_string_lossy().to_lowercase() != "png" {
|
||||
return Err(ErrorKind::InvalidSkinTexture.into());
|
||||
}
|
||||
} else {
|
||||
return Err(ErrorKind::InvalidSkinTexture.into());
|
||||
}
|
||||
|
||||
tracing::debug!("Reading file: {:?}", path);
|
||||
|
||||
if !path.exists() {
|
||||
tracing::error!("File does not exist: {:?}", path);
|
||||
return Err(ErrorKind::InvalidSkinTexture.into());
|
||||
}
|
||||
|
||||
let data = match tokio::fs::read(path).await {
|
||||
Ok(data) => {
|
||||
tracing::debug!("File read successfully, size: {} bytes", data.len());
|
||||
data
|
||||
},
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to read file: {}", err);
|
||||
return Err(err.into());
|
||||
}
|
||||
};
|
||||
|
||||
let url_or_blob = UrlOrBlob::Blob(data.clone().into());
|
||||
|
||||
match normalize_skin_texture(&url_or_blob).await {
|
||||
Ok(_) => Ok(data.into()),
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to normalize skin texture: {}", err);
|
||||
Err(ErrorKind::InvalidSkinTexture.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Synchronizes the equipped cape with the selected cape if necessary, taking into
|
||||
/// account the currently equipped cape, the default cape for the player, and if a
|
||||
/// cape override is provided.
|
||||
|
||||
Reference in New Issue
Block a user