feat: create local profile with selected file
This commit is contained in:
parent
7fe94076c7
commit
98fa4d5e65
@ -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))
|
||||||
|
@ -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())),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
61
src/components/profile/file-input.tsx
Normal file
61
src/components/profile/file-input.tsx
Normal 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;
|
@ -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 })}
|
||||||
/>
|
/>
|
||||||
|
@ -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) {
|
||||||
|
Loading…
Reference in New Issue
Block a user