Skip to content

Commit

Permalink
feat: ✨ Complex scene of FileSelector.
Browse files Browse the repository at this point in the history
  • Loading branch information
Jisu-Woniu committed Nov 19, 2023
1 parent 6262349 commit 5e4e183
Show file tree
Hide file tree
Showing 11 changed files with 187 additions and 26 deletions.
1 change: 1 addition & 0 deletions src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ tauri-build = { version = "1.5.0", features = [] }
anyhow = { version = "1.0.75", features = ["backtrace"] }
serde = { version = "1.0.192", features = ["derive"] }
serde_json = "1.0.108"
tokio = { version = "1.34.0", features = ["fs", "macros", "rt", "rt-multi-thread"] }

[dependencies.digital-signature-crypto]
version = "0.1.0"
Expand Down
46 changes: 46 additions & 0 deletions src-tauri/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use std::{
fmt::{self, Display, Formatter},
io,
};

use serde::Serialize;

#[derive(Debug)]
pub struct Error(anyhow::Error);

pub type Result<T> = std::result::Result<T, Error>;

impl From<anyhow::Error> for Error {
fn from(value: anyhow::Error) -> Self {
Self(value)
}
}

impl From<digital_signature_crypto::error::Error> for Error {
fn from(value: digital_signature_crypto::error::Error) -> Self {
Self(anyhow::Error::from(value))
}
}

impl From<io::Error> for Error {
fn from(value: io::Error) -> Self {
Self(anyhow::Error::from(value))
}
}

impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}

impl std::error::Error for Error {}

impl Serialize for Error {
fn serialize<S>(&self, serializer: S) -> std::prelude::v1::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.0.to_string())
}
}
55 changes: 55 additions & 0 deletions src-tauri/src/fs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use std::path::Path;

use serde::Serialize;
use tokio::fs;

use crate::error::Result;

#[repr(u8)]
#[derive(Debug, Clone, Copy)]
pub enum FileType {
File = 0,
Dir = 1,
Other = 2,
Inexist = 3,
}

impl Serialize for FileType {
fn serialize<S>(&self, serializer: S) -> std::prelude::v1::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_u8(*self as u8)
}
}

#[tauri::command]
pub async fn file_type(path: &Path) -> Result<FileType> {
Ok(match fs::metadata(path).await {
Ok(metadata) => {
if metadata.is_dir() {
FileType::Dir
} else if metadata.is_file() {
FileType::File
} else {
FileType::Other
}
}
Err(_) => FileType::Inexist,
})
}

#[cfg(test)]
mod tests {
use std::path::PathBuf;

use super::file_type;

#[tokio::test]
async fn test() {
println!("{:?}", file_type(&PathBuf::from("/home/")).await);
println!("{:?}", file_type(&PathBuf::from("/etc/fstab")).await);
println!("{:?}", file_type(&PathBuf::from("/home/inexist")).await);
println!("{:?}", file_type(&PathBuf::from("/bin/sh")).await);
}
}
13 changes: 13 additions & 0 deletions src-tauri/src/keygen.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//! Used for key generation.
use std::path::Path;

use digital_signature_crypto::keygen::write_key_pair;

use crate::error::Result;

#[tauri::command]
pub async fn generate_key_pair(name: &str, email: &str, path: &Path) -> Result<()> {
write_key_pair(name, email, path).await?;
Ok(())
}
8 changes: 7 additions & 1 deletion src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,15 @@

// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command

use crate::{fs::file_type, keygen::generate_key_pair};

mod error;
mod fs;
mod keygen;

fn main() -> anyhow::Result<()> {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![])
.invoke_handler(tauri::generate_handler![generate_key_pair, file_type])
.run(tauri::generate_context!())?;
Ok(())
}
14 changes: 7 additions & 7 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import {
VWindowItem,
} from "vuetify/components";
import { mdiFileKey, mdiFileCheck, mdiKeyChain } from "@mdi/js";
import SignView from "./views/SignView.vue";
import ValidateView from "./views/ValidateView.vue";
import KeygenView from "./views/KeygenView.vue";
import ColorSwitcher from "./components/ColorSwitcher.vue";
import SignView from "@/views/SignView.vue";
import ValidateView from "@/views/ValidateView.vue";
import KeygenView from "@/views/KeygenView.vue";
import ColorSwitcher from "@/components/ColorSwitcher.vue";
import { useSessionStorage } from "@vueuse/core";
const enum Tab {
Expand Down Expand Up @@ -40,13 +40,13 @@ const tab = useSessionStorage("tab", Tab.sign);

<VWindow v-model="tab">
<VWindowItem :value="Tab.sign">
<SignView />
<SignView :activated="tab === Tab.sign" />
</VWindowItem>
<VWindowItem :value="Tab.validate">
<ValidateView />
<ValidateView :activated="tab === Tab.validate" />
</VWindowItem>
<VWindowItem :value="Tab.keygen">
<KeygenView />
<KeygenView :activated="tab === Tab.keygen" />
</VWindowItem>
</VWindow>
</VApp>
Expand Down
47 changes: 33 additions & 14 deletions src/components/FileSelector.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,46 +7,63 @@ import { VBtn, VCard, VDialog, VIcon } from "vuetify/components";
import UploadFile from "~icons/ic/twotone-upload-file";
import FolderOpen from "~icons/ic/twotone-folder-open";
import Reject from "~icons/ic/twotone-highlight-off";
import { useVModel } from "@vueuse/core";
import { invoke } from "@tauri-apps/api/tauri";
import { onActivated } from "vue";
import { onDeactivated } from "vue";
const props = defineProps<{
modelValue: string | undefined;
directory: boolean;
directory?: boolean;
}>();
const emits = defineEmits<{
(event: "update:modelValue", value: string | undefined): void;
}>();
const files = useVModel(props, "modelValue", emits);
const file = useVModel(props, "modelValue", emits);
const SelectFile = async () => {
const selected = (await open({
multiple: false,
directory: props.directory,
})) as string | null;
files.value = selected ?? undefined;
file.value = selected ?? undefined;
};
const hover = ref(false);
const hover_accept = ref(false);
let listeners: UnlistenFn[];
const enum FileType {
file = 0,
dir = 1,
other = 2,
inexist = 3,
}
const checkFileType = async (path: string) => {
const fileType = await invoke<FileType>("file_type", { path });
return fileType === (props.directory ? FileType.dir : FileType.file);
};
onMounted(async () => {
listeners = await Promise.all([
listen<string[]>(TauriEvent.WINDOW_FILE_DROP_HOVER, () => {
listen<string[]>(TauriEvent.WINDOW_FILE_DROP_HOVER, async (e) => {
hover.value = true;
hover_accept.value =
e.payload.length == 1 && (await checkFileType(e.payload[0]));
}),
listen<string[]>(TauriEvent.WINDOW_FILE_DROP, async (e) => {
console.log(e.payload);
if (e.payload.length != 1) {
console.log("DROP!!");
if (e.payload.length != 1 || !(await checkFileType(e.payload[0]))) {
await message(
"You can only drop one file here.\n" +
"You have selected:\n" +
e.payload.join("\n"),
props.directory ? "请拖放一个文件夹到此处" : "请拖放一个文件到此处",
);
} else files.value = e.payload[0];
} else file.value = e.payload[0];
hover.value = false;
}),
listen<void>(TauriEvent.WINDOW_FILE_DROP_CANCELLED, () => {
Expand All @@ -58,19 +75,21 @@ onMounted(async () => {
]);
});
onUnmounted(() => {
listeners.forEach((unlisten) => unlisten());
});
onUnmounted(() => listeners.forEach((unlisten) => unlisten()));
</script>
<template>
<VBtn :prepend-icon="FolderOpen" @click="SelectFile"> 选择文件 </VBtn>
<VDialog v-model="hover" height="100%">
<VCard class="hover-indication-box">
<VCard v-if="hover_accept" class="hover-indication-box">
<VIcon :icon="UploadFile" class="hover-indication-icon" />
<div class="hover-indication-text text-blue-darken-3 mt-3">
拖拽至此处
</div>
</VCard>
<VCard v-else class="hover-indication-box">
<VIcon :icon="Reject" class="hover-indication-icon" />
<div class="hover-indication-text text-blue-darken-3 mt-3">请</div>
</VCard>
</VDialog>
</template>
<style scoped>
Expand Down
18 changes: 16 additions & 2 deletions src/views/KeygenView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import { ref } from "vue";
import FileSelector from "@/components/FileSelector.vue";
import { VBtn, VContainer, VTextField } from "vuetify/components";
import { mdiCheck } from "@mdi/js";
import { invoke } from "@tauri-apps/api/tauri";
defineProps<{ activated: boolean }>();
const name = ref("");
const email = ref("");
Expand All @@ -17,6 +21,14 @@ const rules = {
return regex.test(value) || "非法邮件地址";
},
};
const generateKeyPair = async () => {
await invoke("generate_key_pair", {
name: name.value,
email: email.value,
path: file.value,
});
};
</script>

<template>
Expand All @@ -28,10 +40,12 @@ const rules = {
label="邮箱"
:rules="[rules.required, rules.email]"
/>
<FileSelector v-model="file" directory />
<FileSelector v-if="activated" v-model="file" directory />
<div v-if="file">
{{ file }}
</div>
<VBtn :prepend-icon="mdiCheck" color="#4CAF50">提交</VBtn>
<VBtn :prepend-icon="mdiCheck" color="#4CAF50" @click="generateKeyPair"
>提交</VBtn
>
</VContainer>
</template>
5 changes: 4 additions & 1 deletion src/views/SignView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@
import { ref } from "vue";
import FileSelector from "@/components/FileSelector.vue";
import { VContainer } from "vuetify/components";
defineProps<{ activated: boolean }>();
const file = ref<string>();
</script>

<template>
<VContainer fluid>
<h1 class="pa-2">签名</h1>
<FileSelector v-model="file" />
<FileSelector v-if="activated" v-model="file" />
<div v-if="file">
{{ file }}
</div>
Expand Down
5 changes: 4 additions & 1 deletion src/views/ValidateView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@
import { ref } from "vue";
import FileSelector from "@/components/FileSelector.vue";
import { VContainer } from "vuetify/components";
defineProps<{ activated: boolean }>();
const file = ref<string>();
</script>

<template>
<VContainer fluid>
<h1 class="pa-2">校验</h1>
<FileSelector v-model="file" />
<FileSelector v-if="activated" v-model="file" />
<div v-if="file">
{{ file }}
</div>
Expand Down

0 comments on commit 5e4e183

Please sign in to comment.