Better import flow

This commit is contained in:
Gregory Schier
2024-07-23 08:29:09 -07:00
parent b7148d510b
commit ac8b1c018b
5 changed files with 72 additions and 63 deletions

View File

@@ -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(&notification.id) || (age > Duration::days(2)) { if seen.contains(&notification.id) || (age > Duration::days(2)) {
debug!("Already seen notification {}", notification.id); debug!("Already seen notification {}", notification.id);

View File

@@ -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 */}
&#x200E;
{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">

View File

@@ -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>
); );
} }

View 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 */}
&#x200E;
{filePath ?? 'No file selected'}
</div>
</HStack>
);
}

View File

@@ -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;
} }