feat: create local profile with selected file

This commit is contained in:
GyDi 2022-03-19 19:21:55 +08:00
parent 7fe94076c7
commit 98fa4d5e65
No known key found for this signature in database
GPG Key ID: 1C95E0D3467B3084
5 changed files with 92 additions and 12 deletions

View File

@ -43,9 +43,10 @@ pub async fn import_profile(
#[tauri::command] #[tauri::command]
pub async fn create_profile( pub async fn create_profile(
item: PrfItem, // partial item: PrfItem, // partial
file_data: Option<String>,
profiles_state: State<'_, ProfilesState>, profiles_state: State<'_, ProfilesState>,
) -> Result<(), String> { ) -> Result<(), String> {
let item = wrap_err!(PrfItem::from(item).await)?; let item = wrap_err!(PrfItem::from(item, file_data).await)?;
let mut profiles = profiles_state.0.lock().unwrap(); let mut profiles = profiles_state.0.lock().unwrap();
wrap_err!(profiles.append_item(item)) wrap_err!(profiles.append_item(item))

View File

@ -119,7 +119,7 @@ impl Default for PrfItem {
impl PrfItem { impl PrfItem {
/// From partial item /// From partial item
/// must contain `itype` /// must contain `itype`
pub async fn from(item: PrfItem) -> Result<PrfItem> { pub async fn from(item: PrfItem, file_data: Option<String>) -> Result<PrfItem> {
if item.itype.is_none() { if item.itype.is_none() {
bail!("type should not be null"); bail!("type should not be null");
} }
@ -137,7 +137,7 @@ impl PrfItem {
"local" => { "local" => {
let name = item.name.unwrap_or("Local File".into()); let name = item.name.unwrap_or("Local File".into());
let desc = item.desc.unwrap_or("".into()); let desc = item.desc.unwrap_or("".into());
PrfItem::from_local(name, desc) PrfItem::from_local(name, desc, file_data)
} }
"merge" => { "merge" => {
let name = item.name.unwrap_or("Merge".into()); let name = item.name.unwrap_or("Merge".into());
@ -155,7 +155,7 @@ impl PrfItem {
/// ## Local type /// ## Local type
/// create a new item from name/desc /// create a new item from name/desc
pub fn from_local(name: String, desc: String) -> Result<PrfItem> { pub fn from_local(name: String, desc: String, file_data: Option<String>) -> Result<PrfItem> {
let uid = help::get_uid("l"); let uid = help::get_uid("l");
let file = format!("{uid}.yaml"); let file = format!("{uid}.yaml");
@ -170,7 +170,7 @@ impl PrfItem {
extra: None, extra: None,
option: None, option: None,
updated: Some(help::get_now()), updated: Some(help::get_now()),
file_data: Some(tmpl::ITEM_LOCAL.into()), file_data: Some(file_data.unwrap_or(tmpl::ITEM_LOCAL.into())),
}) })
} }

View File

@ -0,0 +1,61 @@
import { useRef, useState } from "react";
import { useLockFn } from "ahooks";
import { Box, Button, Typography } from "@mui/material";
interface Props {
onChange: (value: string) => void;
}
const FileInput = (props: Props) => {
const { onChange } = props;
// file input
const inputRef = useRef<any>();
const [loading, setLoading] = useState(false);
const [fileName, setFileName] = useState("");
const onFileInput = useLockFn(async (e: any) => {
const file = e.target.files?.[0] as File;
if (!file) return;
setFileName(file.name);
setLoading(true);
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (event) => {
resolve(null);
onChange(event.target?.result as string);
};
reader.onerror = reject;
reader.readAsText(file);
}).finally(() => setLoading(false));
});
return (
<Box sx={{ mt: 2, mb: 1, display: "flex", alignItems: "center" }}>
<Button
variant="outlined"
sx={{ flex: "none" }}
onClick={() => inputRef.current?.click()}
>
Choose File
</Button>
<input
type="file"
accept=".yaml,.yml"
ref={inputRef}
style={{ display: "none" }}
onChange={onFileInput}
/>
<Typography noWrap sx={{ ml: 1 }}>
{loading ? "Loading..." : fileName}
</Typography>
</Box>
);
};
export default FileInput;

View File

@ -1,4 +1,4 @@
import { useState } from "react"; import { useRef, useState } from "react";
import { useSWRConfig } from "swr"; import { useSWRConfig } from "swr";
import { useLockFn, useSetState } from "ahooks"; import { useLockFn, useSetState } from "ahooks";
import { import {
@ -17,6 +17,7 @@ import {
import { Settings } from "@mui/icons-material"; import { Settings } from "@mui/icons-material";
import { createProfile } from "../../services/cmds"; import { createProfile } from "../../services/cmds";
import Notice from "../base/base-notice"; import Notice from "../base/base-notice";
import FileInput from "./file-input";
interface Props { interface Props {
open: boolean; open: boolean;
@ -37,9 +38,10 @@ const ProfileNew = (props: Props) => {
}); });
const [showOpt, setShowOpt] = useState(false); const [showOpt, setShowOpt] = useState(false);
const [option, setOption] = useSetState({ // can add more option
user_agent: "", const [option, setOption] = useSetState({ user_agent: "" });
}); // able to add more option // file input
const fileDataRef = useRef<string | null>(null);
const onCreate = useLockFn(async () => { const onCreate = useLockFn(async () => {
if (!form.type) { if (!form.type) {
@ -55,10 +57,15 @@ const ProfileNew = (props: Props) => {
} }
const option_ = form.type === "remote" ? option : undefined; const option_ = form.type === "remote" ? option : undefined;
await createProfile({ ...form, name, option: option_ }); const item = { ...form, name, option: option_ };
const fileData = form.type === "local" ? fileDataRef.current : null;
await createProfile(item, fileData);
setForm({ type: "remote", name: "", desc: "", url: "" }); setForm({ type: "remote", name: "", desc: "", url: "" });
setOption({ user_agent: "" }); setOption({ user_agent: "" });
setShowOpt(false); setShowOpt(false);
fileDataRef.current = null;
mutate("getProfiles"); mutate("getProfiles");
onClose(); onClose();
@ -97,6 +104,7 @@ const ProfileNew = (props: Props) => {
<TextField <TextField
{...textFieldProps} {...textFieldProps}
label="Name" label="Name"
autoComplete="off"
value={form.name} value={form.name}
onChange={(e) => setForm({ name: e.target.value })} onChange={(e) => setForm({ name: e.target.value })}
/> />
@ -104,6 +112,7 @@ const ProfileNew = (props: Props) => {
<TextField <TextField
{...textFieldProps} {...textFieldProps}
label="Descriptions" label="Descriptions"
autoComplete="off"
value={form.desc} value={form.desc}
onChange={(e) => setForm({ desc: e.target.value })} onChange={(e) => setForm({ desc: e.target.value })}
/> />
@ -112,15 +121,21 @@ const ProfileNew = (props: Props) => {
<TextField <TextField
{...textFieldProps} {...textFieldProps}
label="Subscription Url" label="Subscription Url"
autoComplete="off"
value={form.url} value={form.url}
onChange={(e) => setForm({ url: e.target.value })} onChange={(e) => setForm({ url: e.target.value })}
/> />
)} )}
{form.type === "local" && (
<FileInput onChange={(val) => (fileDataRef.current = val)} />
)}
{showOpt && ( {showOpt && (
<TextField <TextField
{...textFieldProps} {...textFieldProps}
label="User Agent" label="User Agent"
autoComplete="off"
value={option.user_agent} value={option.user_agent}
onChange={(e) => setOption({ user_agent: e.target.value })} onChange={(e) => setOption({ user_agent: e.target.value })}
/> />

View File

@ -14,8 +14,11 @@ export async function enhanceProfiles() {
return invoke<void>("enhance_profiles"); return invoke<void>("enhance_profiles");
} }
export async function createProfile(item: Partial<CmdType.ProfileItem>) { export async function createProfile(
return invoke<void>("create_profile", { item }); item: Partial<CmdType.ProfileItem>,
fileData?: string | null
) {
return invoke<void>("create_profile", { item, fileData });
} }
export async function viewProfile(index: string) { export async function viewProfile(index: string) {