mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-23 17:18:32 +02:00
Better import flow
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
use chrono::{DateTime, Duration, Utc};
|
use chrono::{DateTime, Duration, Utc};
|
||||||
use log::{debug, info};
|
use log::debug;
|
||||||
use reqwest::Method;
|
use reqwest::Method;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tauri::{AppHandle, Manager};
|
use tauri::{AppHandle, Manager};
|
||||||
@@ -80,9 +80,7 @@ impl YaakNotifier {
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let age = notification
|
let age = notification.timestamp.signed_duration_since(Utc::now());
|
||||||
.timestamp
|
|
||||||
.signed_duration_since(Utc::now());
|
|
||||||
let seen = get_kv(app).await?;
|
let seen = get_kv(app).await?;
|
||||||
if seen.contains(¬ification.id) || (age > Duration::days(2)) {
|
if seen.contains(¬ification.id) || (age > Duration::days(2)) {
|
||||||
debug!("Already seen notification {}", notification.id);
|
debug!("Already seen notification {}", notification.id);
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { open } from '@tauri-apps/plugin-dialog';
|
|
||||||
import mime from 'mime';
|
import mime from 'mime';
|
||||||
import { useKeyValue } from '../hooks/useKeyValue';
|
import { useKeyValue } from '../hooks/useKeyValue';
|
||||||
import type { HttpRequest } from '../lib/models';
|
import type { HttpRequest } from '../lib/models';
|
||||||
@@ -6,6 +5,7 @@ import { Banner } from './core/Banner';
|
|||||||
import { Button } from './core/Button';
|
import { Button } from './core/Button';
|
||||||
import { InlineCode } from './core/InlineCode';
|
import { InlineCode } from './core/InlineCode';
|
||||||
import { HStack, VStack } from './core/Stacks';
|
import { HStack, VStack } from './core/Stacks';
|
||||||
|
import { SelectFile } from './SelectFile';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
requestId: string;
|
requestId: string;
|
||||||
@@ -28,33 +28,17 @@ export function BinaryFileEditor({
|
|||||||
fallback: false,
|
fallback: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleClick = async () => {
|
const handleChange = async (filePath: string | null) => {
|
||||||
await ignoreContentType.set(false);
|
await ignoreContentType.set(false);
|
||||||
const selected = await open({
|
onChange({ filePath: filePath ?? undefined });
|
||||||
title: 'Select File',
|
|
||||||
multiple: false,
|
|
||||||
});
|
|
||||||
if (selected == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onChange({ filePath: selected.path });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const filePath = typeof body.filePath === 'string' ? body.filePath : undefined;
|
const filePath = typeof body.filePath === 'string' ? body.filePath : null;
|
||||||
const mimeType = mime.getType(filePath ?? '') ?? 'application/octet-stream';
|
const mimeType = mime.getType(filePath ?? '') ?? 'application/octet-stream';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VStack space={2}>
|
<VStack space={2}>
|
||||||
<HStack space={2}>
|
<SelectFile onChange={handleChange} filePath={filePath} />
|
||||||
<Button variant="border" color="secondary" size="sm" onClick={handleClick}>
|
|
||||||
Choose File
|
|
||||||
</Button>
|
|
||||||
<div className="text-sm font-mono truncate rtl pr-3 text-fg">
|
|
||||||
{/* Special character to insert ltr text in rtl element without making things wonky */}
|
|
||||||
‎
|
|
||||||
{filePath ?? 'Select File'}
|
|
||||||
</div>
|
|
||||||
</HStack>
|
|
||||||
{filePath != null && mimeType !== contentType && !ignoreContentType.value && (
|
{filePath != null && mimeType !== contentType && !ignoreContentType.value && (
|
||||||
<Banner className="mt-3 !py-5">
|
<Banner className="mt-3 !py-5">
|
||||||
<div className="mb-4 text-center">
|
<div className="mb-4 text-center">
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { VStack } from './core/Stacks';
|
|
||||||
import { Button } from './core/Button';
|
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Banner } from './core/Banner';
|
import { Button } from './core/Button';
|
||||||
import { Icon } from './core/Icon';
|
import { VStack } from './core/Stacks';
|
||||||
|
import { SelectFile } from './SelectFile';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
importData: () => Promise<void>;
|
importData: (filePath: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ImportDataDialog({ importData }: Props) {
|
export function ImportDataDialog({ importData }: Props) {
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
|
const [filePath, setFilePath] = useState<string | null>(null);
|
||||||
return (
|
return (
|
||||||
<VStack space={5} className="pb-4">
|
<VStack space={5} className="pb-4">
|
||||||
<VStack space={1}>
|
<VStack space={1}>
|
||||||
@@ -18,27 +18,32 @@ export function ImportDataDialog({ importData }: Props) {
|
|||||||
<li>Postman Collection v2, v2.1</li>
|
<li>Postman Collection v2, v2.1</li>
|
||||||
<li>Insomnia v4+</li>
|
<li>Insomnia v4+</li>
|
||||||
<li>Swagger 2.0</li>
|
<li>Swagger 2.0</li>
|
||||||
<li>Curl commands</li>
|
<li>
|
||||||
|
Curl commands <em className="text-fg-subtle">(or paste into URL)</em>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<Banner className="mt-3 flex items-center gap-2">
|
|
||||||
<Icon icon="magicWand" />
|
|
||||||
Paste any Curl command into URL bar
|
|
||||||
</Banner>
|
|
||||||
</VStack>
|
</VStack>
|
||||||
<Button
|
<VStack space={2}>
|
||||||
color="primary"
|
<SelectFile filePath={filePath} onChange={setFilePath} />
|
||||||
isLoading={isLoading}
|
{filePath && (
|
||||||
onClick={async () => {
|
<Button
|
||||||
setIsLoading(true);
|
color="primary"
|
||||||
try {
|
disabled={!filePath || isLoading}
|
||||||
await importData();
|
isLoading={isLoading}
|
||||||
} finally {
|
size="sm"
|
||||||
setIsLoading(false);
|
onClick={async () => {
|
||||||
}
|
setIsLoading(true);
|
||||||
}}
|
try {
|
||||||
>
|
await importData(filePath);
|
||||||
{isLoading ? 'Importing' : 'Select File'}
|
} finally {
|
||||||
</Button>
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isLoading ? 'Importing' : 'Import'}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</VStack>
|
||||||
</VStack>
|
</VStack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
31
src-web/components/SelectFile.tsx
Normal file
31
src-web/components/SelectFile.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { open } from '@tauri-apps/plugin-dialog';
|
||||||
|
import { Button } from './core/Button';
|
||||||
|
import { HStack } from './core/Stacks';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onChange: (filePath: string | null) => void;
|
||||||
|
filePath: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SelectFile({ onChange, filePath }: Props) {
|
||||||
|
const handleClick = async () => {
|
||||||
|
const selected = await open({
|
||||||
|
title: 'Select File',
|
||||||
|
multiple: false,
|
||||||
|
});
|
||||||
|
if (selected == null) onChange(null);
|
||||||
|
else onChange(selected.path);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<HStack space={2}>
|
||||||
|
<Button variant="border" color="secondary" size="sm" onClick={handleClick}>
|
||||||
|
Choose File
|
||||||
|
</Button>
|
||||||
|
<div className="text-sm font-mono truncate rtl pr-3 text-fg">
|
||||||
|
{/* Special character to insert ltr text in rtl element without making things wonky */}
|
||||||
|
‎
|
||||||
|
{filePath ?? 'No file selected'}
|
||||||
|
</div>
|
||||||
|
</HStack>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,16 +1,15 @@
|
|||||||
import { useMutation } from '@tanstack/react-query';
|
import { useMutation } from '@tanstack/react-query';
|
||||||
import { open } from '@tauri-apps/plugin-dialog';
|
|
||||||
import { Button } from '../components/core/Button';
|
import { Button } from '../components/core/Button';
|
||||||
import { FormattedError } from '../components/core/FormattedError';
|
import { FormattedError } from '../components/core/FormattedError';
|
||||||
import { VStack } from '../components/core/Stacks';
|
import { VStack } from '../components/core/Stacks';
|
||||||
import { useDialog } from '../components/DialogContext';
|
import { useDialog } from '../components/DialogContext';
|
||||||
|
import { ImportDataDialog } from '../components/ImportDataDialog';
|
||||||
import type { Environment, Folder, GrpcRequest, HttpRequest, Workspace } from '../lib/models';
|
import type { Environment, Folder, GrpcRequest, HttpRequest, Workspace } from '../lib/models';
|
||||||
import { count } from '../lib/pluralize';
|
import { count } from '../lib/pluralize';
|
||||||
import { invokeCmd } from '../lib/tauri';
|
import { invokeCmd } from '../lib/tauri';
|
||||||
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
||||||
import { useAlert } from './useAlert';
|
import { useAlert } from './useAlert';
|
||||||
import { useAppRoutes } from './useAppRoutes';
|
import { useAppRoutes } from './useAppRoutes';
|
||||||
import { ImportDataDialog } from '../components/ImportDataDialog';
|
|
||||||
|
|
||||||
export function useImportData() {
|
export function useImportData() {
|
||||||
const routes = useAppRoutes();
|
const routes = useAppRoutes();
|
||||||
@@ -18,15 +17,7 @@ export function useImportData() {
|
|||||||
const alert = useAlert();
|
const alert = useAlert();
|
||||||
const activeWorkspaceId = useActiveWorkspaceId();
|
const activeWorkspaceId = useActiveWorkspaceId();
|
||||||
|
|
||||||
const importData = async (): Promise<boolean> => {
|
const importData = async (filePath: string): Promise<boolean> => {
|
||||||
const selected = await open({
|
|
||||||
filters: [{ name: 'Export File', extensions: ['json', 'yaml', 'sh', 'txt'] }],
|
|
||||||
multiple: false,
|
|
||||||
});
|
|
||||||
if (selected == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const imported: {
|
const imported: {
|
||||||
workspaces: Workspace[];
|
workspaces: Workspace[];
|
||||||
environments: Environment[];
|
environments: Environment[];
|
||||||
@@ -34,7 +25,7 @@ export function useImportData() {
|
|||||||
httpRequests: HttpRequest[];
|
httpRequests: HttpRequest[];
|
||||||
grpcRequests: GrpcRequest[];
|
grpcRequests: GrpcRequest[];
|
||||||
} = await invokeCmd('cmd_import_data', {
|
} = await invokeCmd('cmd_import_data', {
|
||||||
filePath: selected.path,
|
filePath,
|
||||||
workspaceId: activeWorkspaceId,
|
workspaceId: activeWorkspaceId,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -93,9 +84,9 @@ export function useImportData() {
|
|||||||
title: 'Import Data',
|
title: 'Import Data',
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
render: ({ hide }) => {
|
render: ({ hide }) => {
|
||||||
const importAndHide = async () => {
|
const importAndHide = async (filePath: string) => {
|
||||||
try {
|
try {
|
||||||
const didImport = await importData();
|
const didImport = await importData(filePath);
|
||||||
if (!didImport) {
|
if (!didImport) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user