mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-23 09:08:32 +02:00
Use SelectFile component in more places
This commit is contained in:
@@ -28,7 +28,7 @@ export function BinaryFileEditor({
|
|||||||
fallback: false,
|
fallback: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleChange = async (filePath: string | null) => {
|
const handleChange = async ({ filePath }: { filePath: string | null }) => {
|
||||||
await ignoreContentType.set(false);
|
await ignoreContentType.set(false);
|
||||||
onChange({ filePath: filePath ?? undefined });
|
onChange({ filePath: filePath ?? undefined });
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export function ImportDataDialog({ importData }: Props) {
|
|||||||
</ul>
|
</ul>
|
||||||
</VStack>
|
</VStack>
|
||||||
<VStack space={2}>
|
<VStack space={2}>
|
||||||
<SelectFile filePath={filePath} onChange={setFilePath} />
|
<SelectFile filePath={filePath} onChange={({ filePath }) => setFilePath(filePath)} />
|
||||||
{filePath && (
|
{filePath && (
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
|
|||||||
@@ -1,31 +1,65 @@
|
|||||||
import { open } from '@tauri-apps/plugin-dialog';
|
import { open } from '@tauri-apps/plugin-dialog';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import mime from 'mime';
|
||||||
|
import type { ButtonProps } from './core/Button';
|
||||||
import { Button } from './core/Button';
|
import { Button } from './core/Button';
|
||||||
|
import { IconButton } from './core/IconButton';
|
||||||
import { HStack } from './core/Stacks';
|
import { HStack } from './core/Stacks';
|
||||||
|
|
||||||
interface Props {
|
type Props = ButtonProps & {
|
||||||
onChange: (filePath: string | null) => void;
|
onChange: (value: { filePath: string | null; contentType: string | null }) => void;
|
||||||
filePath: string | null;
|
filePath: string | null;
|
||||||
}
|
inline?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export function SelectFile({ onChange, filePath }: Props) {
|
// Special character to insert ltr text in rtl element
|
||||||
|
const rtlEscapeChar = <>‎</>;
|
||||||
|
|
||||||
|
export function SelectFile({ onChange, filePath, inline, className }: Props) {
|
||||||
const handleClick = async () => {
|
const handleClick = async () => {
|
||||||
const selected = await open({
|
const selected = await open({
|
||||||
title: 'Select File',
|
title: 'Select File',
|
||||||
multiple: false,
|
multiple: false,
|
||||||
});
|
});
|
||||||
if (selected == null) onChange(null);
|
if (selected == null) return;
|
||||||
else onChange(selected.path);
|
|
||||||
|
const filePath = selected.path;
|
||||||
|
const contentType = typeof filePath === 'string' && filePath ? mime.getType(filePath) : null;
|
||||||
|
onChange({ filePath, contentType });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleClear = async () => {
|
||||||
|
onChange({ filePath: null, contentType: null });
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HStack space={2}>
|
<HStack space={1.5} className="group relative justify-stretch">
|
||||||
<Button variant="border" color="secondary" size="sm" onClick={handleClick}>
|
<Button
|
||||||
Choose File
|
className={classNames(className, 'font-mono text-xs rtl', inline && 'w-full')}
|
||||||
|
color="secondary"
|
||||||
|
size="sm"
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
{rtlEscapeChar}
|
||||||
|
{inline ? <>{filePath || 'Select File'}</> : <>Select File</>}
|
||||||
</Button>
|
</Button>
|
||||||
<div className="text-sm font-mono truncate rtl pr-3 text-fg">
|
{!inline && (
|
||||||
{/* Special character to insert ltr text in rtl element without making things wonky */}
|
<>
|
||||||
‎
|
{filePath && (
|
||||||
{filePath ?? 'No file selected'}
|
<IconButton
|
||||||
</div>
|
size="sm"
|
||||||
|
variant="border"
|
||||||
|
icon="x"
|
||||||
|
title="Unset File"
|
||||||
|
onClick={handleClear}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className="text-sm font-mono truncate rtl pr-3 text-fg">
|
||||||
|
{rtlEscapeChar}
|
||||||
|
{filePath ?? 'No file selected'}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</HStack>
|
</HStack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import type { HotkeyAction } from '../../hooks/useHotKey';
|
|||||||
import { useFormattedHotkey, useHotKey } from '../../hooks/useHotKey';
|
import { useFormattedHotkey, useHotKey } from '../../hooks/useHotKey';
|
||||||
import { Icon } from './Icon';
|
import { Icon } from './Icon';
|
||||||
|
|
||||||
export type ButtonProps = Omit<HTMLAttributes<HTMLButtonElement>, 'color'> & {
|
export type ButtonProps = Omit<HTMLAttributes<HTMLButtonElement>, 'color' | 'onChange'> & {
|
||||||
innerClassName?: string;
|
innerClassName?: string;
|
||||||
color?:
|
color?:
|
||||||
| 'custom'
|
| 'custom'
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { open } from '@tauri-apps/plugin-dialog';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { EditorView } from 'codemirror';
|
import type { EditorView } from 'codemirror';
|
||||||
import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
@@ -7,7 +6,7 @@ import { useDrag, useDrop } from 'react-dnd';
|
|||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import { usePrompt } from '../../hooks/usePrompt';
|
import { usePrompt } from '../../hooks/usePrompt';
|
||||||
import { DropMarker } from '../DropMarker';
|
import { DropMarker } from '../DropMarker';
|
||||||
import { Button } from './Button';
|
import { SelectFile } from '../SelectFile';
|
||||||
import { Checkbox } from './Checkbox';
|
import { Checkbox } from './Checkbox';
|
||||||
import { Dropdown } from './Dropdown';
|
import { Dropdown } from './Dropdown';
|
||||||
import type { GenericCompletionConfig } from './Editor/genericCompletion';
|
import type { GenericCompletionConfig } from './Editor/genericCompletion';
|
||||||
@@ -286,7 +285,12 @@ function PairEditorRow({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleChangeValueFile = useMemo(
|
const handleChangeValueFile = useMemo(
|
||||||
() => (value: string) => onChange({ id, pair: { ...pairContainer.pair, value, isFile: true } }),
|
() =>
|
||||||
|
({ filePath }: { filePath: string | null }) =>
|
||||||
|
onChange({
|
||||||
|
id,
|
||||||
|
pair: { ...pairContainer.pair, value: filePath ?? '', isFile: true },
|
||||||
|
}),
|
||||||
[onChange, id, pairContainer.pair],
|
[onChange, id, pairContainer.pair],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -386,27 +390,12 @@ function PairEditorRow({
|
|||||||
/>
|
/>
|
||||||
<div className="w-full grid grid-cols-[minmax(0,1fr)_auto] gap-1 items-center">
|
<div className="w-full grid grid-cols-[minmax(0,1fr)_auto] gap-1 items-center">
|
||||||
{pairContainer.pair.isFile ? (
|
{pairContainer.pair.isFile ? (
|
||||||
<Button
|
<SelectFile
|
||||||
|
inline
|
||||||
size="xs"
|
size="xs"
|
||||||
color="secondary"
|
filePath={pairContainer.pair.value}
|
||||||
className="font-mono text-2xs rtl"
|
onChange={handleChangeValueFile}
|
||||||
onClick={async (e) => {
|
/>
|
||||||
e.preventDefault();
|
|
||||||
const selected = await open({
|
|
||||||
title: 'Select file',
|
|
||||||
multiple: false,
|
|
||||||
});
|
|
||||||
if (selected == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleChangeValueFile(selected.path);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* Special character to insert ltr text in rtl element without making things wonky */}
|
|
||||||
‎
|
|
||||||
{pairContainer.pair.value || 'Select File'}
|
|
||||||
</Button>
|
|
||||||
) : (
|
) : (
|
||||||
<Input
|
<Input
|
||||||
hideLabel
|
hideLabel
|
||||||
@@ -432,7 +421,7 @@ function PairEditorRow({
|
|||||||
<RadioDropdown
|
<RadioDropdown
|
||||||
value={pairContainer.pair.isFile ? 'file' : 'text'}
|
value={pairContainer.pair.isFile ? 'file' : 'text'}
|
||||||
onChange={(v) => {
|
onChange={(v) => {
|
||||||
if (v === 'file') handleChangeValueFile('');
|
if (v === 'file') handleChangeValueFile({ filePath: '' });
|
||||||
else handleChangeValueText('');
|
else handleChangeValueText('');
|
||||||
}}
|
}}
|
||||||
items={[
|
items={[
|
||||||
@@ -444,6 +433,7 @@ function PairEditorRow({
|
|||||||
key: 'mime',
|
key: 'mime',
|
||||||
label: 'Set Content-Type',
|
label: 'Set Content-Type',
|
||||||
leftSlot: <Icon icon="pencil" />,
|
leftSlot: <Icon icon="pencil" />,
|
||||||
|
hidden: !pairContainer.pair.isFile,
|
||||||
onSelect: async () => {
|
onSelect: async () => {
|
||||||
const v = await prompt({
|
const v = await prompt({
|
||||||
id: 'content-type',
|
id: 'content-type',
|
||||||
@@ -459,6 +449,15 @@ function PairEditorRow({
|
|||||||
handleChangeValueContentType(v);
|
handleChangeValueContentType(v);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'clear-file',
|
||||||
|
label: 'Unset File',
|
||||||
|
leftSlot: <Icon icon="x" />,
|
||||||
|
hidden: !pairContainer.pair.isFile,
|
||||||
|
onSelect: async () => {
|
||||||
|
handleChangeValueFile({ filePath: null });
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'delete',
|
key: 'delete',
|
||||||
label: 'Delete',
|
label: 'Delete',
|
||||||
|
|||||||
@@ -60,10 +60,11 @@ type BaseStackProps = HTMLAttributes<HTMLElement> & {
|
|||||||
space?: keyof typeof gapClasses;
|
space?: keyof typeof gapClasses;
|
||||||
alignItems?: 'start' | 'center' | 'stretch' | 'end';
|
alignItems?: 'start' | 'center' | 'stretch' | 'end';
|
||||||
justifyContent?: 'start' | 'center' | 'end' | 'between';
|
justifyContent?: 'start' | 'center' | 'end' | 'between';
|
||||||
|
wrap?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const BaseStack = forwardRef(function BaseStack(
|
const BaseStack = forwardRef(function BaseStack(
|
||||||
{ className, alignItems, justifyContent, children, as, ...props }: BaseStackProps,
|
{ className, alignItems, justifyContent, wrap, children, as, ...props }: BaseStackProps,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
ref: ForwardedRef<any>,
|
ref: ForwardedRef<any>,
|
||||||
) {
|
) {
|
||||||
@@ -74,6 +75,7 @@ const BaseStack = forwardRef(function BaseStack(
|
|||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'flex',
|
'flex',
|
||||||
|
wrap && 'flex-wrap',
|
||||||
alignItems === 'center' && 'items-center',
|
alignItems === 'center' && 'items-center',
|
||||||
alignItems === 'start' && 'items-start',
|
alignItems === 'start' && 'items-start',
|
||||||
alignItems === 'stretch' && 'items-stretch',
|
alignItems === 'stretch' && 'items-stretch',
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ export function TextViewer({ response, pretty, className }: Props) {
|
|||||||
return <BinaryViewer response={response} />;
|
return <BinaryViewer response={response} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!showLargeResponse && (response.contentLength ?? 0) > LARGE_RESPONSE_BYTES / 1000) {
|
if (!showLargeResponse && (response.contentLength ?? 0) > LARGE_RESPONSE_BYTES) {
|
||||||
return (
|
return (
|
||||||
<Banner color="primary" className="h-full flex flex-col gap-3">
|
<Banner color="primary" className="h-full flex flex-col gap-3">
|
||||||
<p>
|
<p>
|
||||||
@@ -137,7 +137,7 @@ export function TextViewer({ response, pretty, className }: Props) {
|
|||||||
</InlineCode>{' '}
|
</InlineCode>{' '}
|
||||||
may impact performance
|
may impact performance
|
||||||
</p>
|
</p>
|
||||||
<HStack space={2}>
|
<HStack wrap space={2}>
|
||||||
<Button color="primary" size="xs" onClick={toggleShowLargeResponse}>
|
<Button color="primary" size="xs" onClick={toggleShowLargeResponse}>
|
||||||
Reveal Response
|
Reveal Response
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
Reference in New Issue
Block a user