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