mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-21 00:01:22 +02:00
Multi-line multi-part values
This commit is contained in:
20
packages/common-lib/formatSize.ts
Normal file
20
packages/common-lib/formatSize.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
export function formatSize(bytes: number): string {
|
||||||
|
let num;
|
||||||
|
let unit;
|
||||||
|
|
||||||
|
if (bytes > 1000 * 1000 * 1000) {
|
||||||
|
num = bytes / 1000 / 1000 / 1000;
|
||||||
|
unit = 'GB';
|
||||||
|
} else if (bytes > 1000 * 1000) {
|
||||||
|
num = bytes / 1000 / 1000;
|
||||||
|
unit = 'MB';
|
||||||
|
} else if (bytes > 1000) {
|
||||||
|
num = bytes / 1000;
|
||||||
|
unit = 'KB';
|
||||||
|
} else {
|
||||||
|
num = bytes;
|
||||||
|
unit = 'B';
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${Math.round(num * 10) / 10} ${unit}`;
|
||||||
|
}
|
||||||
@@ -326,14 +326,33 @@ pub async fn send_http_request<R: Runtime>(
|
|||||||
|
|
||||||
// Set or guess mimetype
|
// Set or guess mimetype
|
||||||
if !content_type.is_empty() {
|
if !content_type.is_empty() {
|
||||||
part = part.mime_str(content_type).map_err(|e| e.to_string())?;
|
part = match part.mime_str(content_type) {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(e) => {
|
||||||
|
return Ok(response_err(
|
||||||
|
&*response.lock().await,
|
||||||
|
format!("Invalid mime for multi-part entry {e:?}"),
|
||||||
|
window,
|
||||||
|
)
|
||||||
|
.await);
|
||||||
|
}
|
||||||
|
};
|
||||||
} else if !file_path.is_empty() {
|
} else if !file_path.is_empty() {
|
||||||
let default_mime =
|
let default_mime =
|
||||||
Mime::from_str("application/octet-stream").unwrap();
|
Mime::from_str("application/octet-stream").unwrap();
|
||||||
let mime =
|
let mime =
|
||||||
mime_guess::from_path(file_path.clone()).first_or(default_mime);
|
mime_guess::from_path(file_path.clone()).first_or(default_mime);
|
||||||
part =
|
part = match part.mime_str(mime.essence_str()) {
|
||||||
part.mime_str(mime.essence_str()).map_err(|e| e.to_string())?;
|
Ok(p) => p,
|
||||||
|
Err(e) => {
|
||||||
|
return Ok(response_err(
|
||||||
|
&*response.lock().await,
|
||||||
|
format!("Invalid mime for multi-part entry {e:?}"),
|
||||||
|
window,
|
||||||
|
)
|
||||||
|
.await);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set file path if not empty
|
// Set file path if not empty
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::{DEFAULT_WINDOW_HEIGHT, DEFAULT_WINDOW_WIDTH, MIN_WINDOW_HEIGHT, MIN_
|
|||||||
use log::{info, warn};
|
use log::{info, warn};
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use tauri::{
|
use tauri::{
|
||||||
AppHandle, Emitter, LogicalSize, Manager, Runtime, TitleBarStyle, WebviewUrl, WebviewWindow,
|
AppHandle, Emitter, LogicalSize, Manager, Runtime, WebviewUrl, WebviewWindow,
|
||||||
};
|
};
|
||||||
use tauri_plugin_opener::OpenerExt;
|
use tauri_plugin_opener::OpenerExt;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
@@ -67,6 +67,7 @@ pub(crate) fn create_window<R: Runtime>(
|
|||||||
if config.hide_titlebar {
|
if config.hide_titlebar {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
|
use tauri::TitleBarStyle;
|
||||||
win_builder = win_builder.hidden_title(true).title_bar_style(TitleBarStyle::Overlay);
|
win_builder = win_builder.hidden_title(true).title_bar_style(TitleBarStyle::Overlay);
|
||||||
}
|
}
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { useEffect, useMemo, useRef, useState } from 'react';
|
|||||||
import { useLocalStorage } from 'react-use';
|
import { useLocalStorage } from 'react-use';
|
||||||
import { useIntrospectGraphQL } from '../hooks/useIntrospectGraphQL';
|
import { useIntrospectGraphQL } from '../hooks/useIntrospectGraphQL';
|
||||||
import { showDialog } from '../lib/dialog';
|
import { showDialog } from '../lib/dialog';
|
||||||
import { tryFormatJson } from '../lib/formatters';
|
|
||||||
import { Button } from './core/Button';
|
import { Button } from './core/Button';
|
||||||
import { Dropdown } from './core/Dropdown';
|
import { Dropdown } from './core/Dropdown';
|
||||||
import type { EditorProps } from './core/Editor/Editor';
|
import type { EditorProps } from './core/Editor/Editor';
|
||||||
@@ -178,7 +177,6 @@ export function GraphQLEditor({ request, onChange, baseRequest, ...extraEditorPr
|
|||||||
Variables
|
Variables
|
||||||
</Separator>
|
</Separator>
|
||||||
<Editor
|
<Editor
|
||||||
format={tryFormatJson}
|
|
||||||
language="json"
|
language="json"
|
||||||
heightMode="auto"
|
heightMode="auto"
|
||||||
defaultValue={currentBody.variables}
|
defaultValue={currentBody.variables}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import { useEffect, useMemo, useRef } from 'react';
|
|||||||
import type { ReflectResponseService } from '../hooks/useGrpc';
|
import type { ReflectResponseService } from '../hooks/useGrpc';
|
||||||
import { showAlert } from '../lib/alert';
|
import { showAlert } from '../lib/alert';
|
||||||
import { showDialog } from '../lib/dialog';
|
import { showDialog } from '../lib/dialog';
|
||||||
import { tryFormatJson } from '../lib/formatters';
|
|
||||||
import { pluralizeCount } from '../lib/pluralize';
|
import { pluralizeCount } from '../lib/pluralize';
|
||||||
import { Button } from './core/Button';
|
import { Button } from './core/Button';
|
||||||
import type { EditorProps } from './core/Editor/Editor';
|
import type { EditorProps } from './core/Editor/Editor';
|
||||||
@@ -186,7 +185,6 @@ export function GrpcEditor({
|
|||||||
useTemplating
|
useTemplating
|
||||||
forceUpdateKey={request.id}
|
forceUpdateKey={request.id}
|
||||||
defaultValue={request.message}
|
defaultValue={request.message}
|
||||||
format={tryFormatJson}
|
|
||||||
heightMode="auto"
|
heightMode="auto"
|
||||||
placeholder="..."
|
placeholder="..."
|
||||||
ref={editorViewRef}
|
ref={editorViewRef}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { HttpRequest } from '@yaakapp-internal/models';
|
import type { HttpRequest } from '@yaakapp-internal/models';
|
||||||
import type {GenericCompletionOption} from "@yaakapp-internal/plugins";
|
import type { GenericCompletionOption } from '@yaakapp-internal/plugins';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { atom, useAtom, useAtomValue } from 'jotai';
|
import { atom, useAtom, useAtomValue } from 'jotai';
|
||||||
import { atomWithStorage } from 'jotai/utils';
|
import { atomWithStorage } from 'jotai/utils';
|
||||||
@@ -21,7 +21,6 @@ import { useUpdateAnyHttpRequest } from '../hooks/useUpdateAnyHttpRequest';
|
|||||||
import { deepEqualAtom } from '../lib/atoms';
|
import { deepEqualAtom } from '../lib/atoms';
|
||||||
import { languageFromContentType } from '../lib/contentType';
|
import { languageFromContentType } from '../lib/contentType';
|
||||||
import { fallbackRequestName } from '../lib/fallbackRequestName';
|
import { fallbackRequestName } from '../lib/fallbackRequestName';
|
||||||
import { tryFormatJson } from '../lib/formatters';
|
|
||||||
import { generateId } from '../lib/generateId';
|
import { generateId } from '../lib/generateId';
|
||||||
import {
|
import {
|
||||||
BODY_TYPE_BINARY,
|
BODY_TYPE_BINARY,
|
||||||
@@ -407,7 +406,6 @@ export const RequestPane = memo(function RequestPane({
|
|||||||
defaultValue={`${activeRequest.body?.text ?? ''}`}
|
defaultValue={`${activeRequest.body?.text ?? ''}`}
|
||||||
language="json"
|
language="json"
|
||||||
onChange={handleBodyTextChange}
|
onChange={handleBodyTextChange}
|
||||||
format={tryFormatJson}
|
|
||||||
stateKey={`json.${activeRequest.id}`}
|
stateKey={`json.${activeRequest.id}`}
|
||||||
/>
|
/>
|
||||||
) : activeRequest.bodyType === BODY_TYPE_XML ? (
|
) : activeRequest.bodyType === BODY_TYPE_XML ? (
|
||||||
|
|||||||
@@ -217,6 +217,7 @@ export const SidebarItem = memo(function SidebarItem({
|
|||||||
|
|
||||||
const itemPrefix = (item.model === 'http_request' || item.model === 'grpc_request') && (
|
const itemPrefix = (item.model === 'http_request' || item.model === 'grpc_request') && (
|
||||||
<HttpMethodTag
|
<HttpMethodTag
|
||||||
|
shortNames
|
||||||
request={item}
|
request={item}
|
||||||
className={classNames(!(active || selected) && 'text-text-subtlest')}
|
className={classNames(!(active || selected) && 'text-text-subtlest')}
|
||||||
/>
|
/>
|
||||||
@@ -271,7 +272,7 @@ export const SidebarItem = memo(function SidebarItem({
|
|||||||
onKeyDown={handleInputKeyDown}
|
onKeyDown={handleInputKeyDown}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<span className="truncate">{itemName}</span>
|
<span className="truncate w-full">{itemName}</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{latestGrpcConnection ? (
|
{latestGrpcConnection ? (
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import { useRequestEditor } from '../../../hooks/useRequestEditor';
|
|||||||
import { useSettings } from '../../../hooks/useSettings';
|
import { useSettings } from '../../../hooks/useSettings';
|
||||||
import { useTemplateFunctionCompletionOptions } from '../../../hooks/useTemplateFunctions';
|
import { useTemplateFunctionCompletionOptions } from '../../../hooks/useTemplateFunctions';
|
||||||
import { showDialog } from '../../../lib/dialog';
|
import { showDialog } from '../../../lib/dialog';
|
||||||
|
import { tryFormatJson, tryFormatXml } from '../../../lib/formatters';
|
||||||
import { TemplateFunctionDialog } from '../../TemplateFunctionDialog';
|
import { TemplateFunctionDialog } from '../../TemplateFunctionDialog';
|
||||||
import { TemplateVariableDialog } from '../../TemplateVariableDialog';
|
import { TemplateVariableDialog } from '../../TemplateVariableDialog';
|
||||||
import { IconButton } from '../IconButton';
|
import { IconButton } from '../IconButton';
|
||||||
@@ -134,7 +135,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
readOnly = true;
|
readOnly = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -147,6 +148,15 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
|||||||
disableTabIndent = true;
|
disableTabIndent = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (format == null) {
|
||||||
|
format =
|
||||||
|
language === 'json'
|
||||||
|
? tryFormatJson
|
||||||
|
: language === 'xml' || language === 'html'
|
||||||
|
? tryFormatXml
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
const cm = useRef<{ view: EditorView; languageCompartment: Compartment } | null>(null);
|
const cm = useRef<{ view: EditorView; languageCompartment: Compartment } | null>(null);
|
||||||
useImperativeHandle(ref, () => cm.current?.view, []);
|
useImperativeHandle(ref, () => cm.current?.view, []);
|
||||||
|
|
||||||
|
|||||||
@@ -15,18 +15,16 @@ const longMethodMap = {
|
|||||||
delete: 'DELETE',
|
delete: 'DELETE',
|
||||||
options: 'OPTIONS',
|
options: 'OPTIONS',
|
||||||
head: 'HEAD',
|
head: 'HEAD',
|
||||||
grpc: 'GRPC',
|
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const shortMethodMap: Record<keyof typeof longMethodMap, string> = {
|
const shortMethodMap: Record<keyof typeof longMethodMap, string> = {
|
||||||
get: 'GET',
|
get: 'GET',
|
||||||
put: 'PUT',
|
put: 'PUT',
|
||||||
post: 'POST',
|
post: 'PST',
|
||||||
patch: 'PTCH',
|
patch: 'PTC',
|
||||||
delete: 'DEL',
|
delete: 'DEL',
|
||||||
options: 'OPTS',
|
options: 'OPT',
|
||||||
head: 'HEAD',
|
head: 'HED',
|
||||||
grpc: 'GRPC',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function HttpMethodTag({ shortNames, request, className }: Props) {
|
export function HttpMethodTag({ shortNames, request, className }: Props) {
|
||||||
@@ -34,7 +32,7 @@ export function HttpMethodTag({ shortNames, request, className }: Props) {
|
|||||||
request.model === 'http_request' && request.bodyType === 'graphql'
|
request.model === 'http_request' && request.bodyType === 'graphql'
|
||||||
? 'GQL'
|
? 'GQL'
|
||||||
: request.model === 'grpc_request'
|
: request.model === 'grpc_request'
|
||||||
? 'GRPC'
|
? 'GRP'
|
||||||
: request.method;
|
: request.method;
|
||||||
|
|
||||||
const m = method.toLowerCase();
|
const m = method.toLowerCase();
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { formatSize } from '@yaakapp-internal/lib/formatSize';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { EditorView } from 'codemirror';
|
import type { EditorView } from 'codemirror';
|
||||||
import {
|
import {
|
||||||
@@ -13,6 +14,8 @@ import {
|
|||||||
import type { XYCoord } from 'react-dnd';
|
import type { XYCoord } from 'react-dnd';
|
||||||
import { useDrag, useDrop } from 'react-dnd';
|
import { useDrag, useDrop } from 'react-dnd';
|
||||||
import { useToggle } from '../../hooks/useToggle';
|
import { useToggle } from '../../hooks/useToggle';
|
||||||
|
import { languageFromContentType } from '../../lib/contentType';
|
||||||
|
import { showDialog } from '../../lib/dialog';
|
||||||
import { generateId } from '../../lib/generateId';
|
import { generateId } from '../../lib/generateId';
|
||||||
import { showPrompt } from '../../lib/prompt';
|
import { showPrompt } from '../../lib/prompt';
|
||||||
import { DropMarker } from '../DropMarker';
|
import { DropMarker } from '../DropMarker';
|
||||||
@@ -21,6 +24,8 @@ import { Button } from './Button';
|
|||||||
import { Checkbox } from './Checkbox';
|
import { Checkbox } from './Checkbox';
|
||||||
import type { DropdownItem } from './Dropdown';
|
import type { DropdownItem } from './Dropdown';
|
||||||
import { Dropdown } from './Dropdown';
|
import { Dropdown } from './Dropdown';
|
||||||
|
import type { EditorProps } from './Editor/Editor';
|
||||||
|
import { Editor } from './Editor/Editor';
|
||||||
import type { GenericCompletionConfig } from './Editor/genericCompletion';
|
import type { GenericCompletionConfig } from './Editor/genericCompletion';
|
||||||
import { Icon } from './Icon';
|
import { Icon } from './Icon';
|
||||||
import { IconButton } from './IconButton';
|
import { IconButton } from './IconButton';
|
||||||
@@ -326,6 +331,7 @@ function PairEditorRow({
|
|||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
const nameInputRef = useRef<EditorView>(null);
|
const nameInputRef = useRef<EditorView>(null);
|
||||||
const valueInputRef = useRef<EditorView>(null);
|
const valueInputRef = useRef<EditorView>(null);
|
||||||
|
const valueLanguage = languageFromContentType(pair.contentType ?? null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (forceFocusNamePairId === pair.id) {
|
if (forceFocusNamePairId === pair.id) {
|
||||||
@@ -380,6 +386,24 @@ function PairEditorRow({
|
|||||||
[onChange, pair],
|
[onChange, pair],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleEditMultiLineValue = useCallback(
|
||||||
|
() =>
|
||||||
|
showDialog({
|
||||||
|
id: 'pair-edit-multiline',
|
||||||
|
size: 'dynamic',
|
||||||
|
title: <>Edit {pair.name}</>,
|
||||||
|
render: ({ hide }) => (
|
||||||
|
<MultilineEditDialog
|
||||||
|
hide={hide}
|
||||||
|
onChange={handleChangeValueText}
|
||||||
|
defaultValue={pair.value}
|
||||||
|
language={valueLanguage}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
[handleChangeValueText, pair.name, pair.value, valueLanguage],
|
||||||
|
);
|
||||||
|
|
||||||
const [, connectDrop] = useDrop<Pair>(
|
const [, connectDrop] = useDrop<Pair>(
|
||||||
{
|
{
|
||||||
accept: ItemTypes.ROW,
|
accept: ItemTypes.ROW,
|
||||||
@@ -495,6 +519,15 @@ function PairEditorRow({
|
|||||||
onFocus={handleFocus}
|
onFocus={handleFocus}
|
||||||
placeholder={valuePlaceholder ?? 'value'}
|
placeholder={valuePlaceholder ?? 'value'}
|
||||||
/>
|
/>
|
||||||
|
) : pair.value.includes('\n') ? (
|
||||||
|
<Button
|
||||||
|
color="secondary"
|
||||||
|
size="sm"
|
||||||
|
onClick={handleEditMultiLineValue}
|
||||||
|
title={pair.value}
|
||||||
|
>
|
||||||
|
Edit {formatSize(pair.value.length)}
|
||||||
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Input
|
<Input
|
||||||
ref={valueInputRef}
|
ref={valueInputRef}
|
||||||
@@ -505,6 +538,7 @@ function PairEditorRow({
|
|||||||
size="sm"
|
size="sm"
|
||||||
containerClassName={classNames(isLast && 'border-dashed')}
|
containerClassName={classNames(isLast && 'border-dashed')}
|
||||||
validate={valueValidate}
|
validate={valueValidate}
|
||||||
|
language={valueLanguage}
|
||||||
forceUpdateKey={forceUpdateKey}
|
forceUpdateKey={forceUpdateKey}
|
||||||
defaultValue={pair.value}
|
defaultValue={pair.value}
|
||||||
label="Value"
|
label="Value"
|
||||||
@@ -526,6 +560,7 @@ function PairEditorRow({
|
|||||||
onChangeText={handleChangeValueText}
|
onChangeText={handleChangeValueText}
|
||||||
onChangeContentType={handleChangeValueContentType}
|
onChangeContentType={handleChangeValueContentType}
|
||||||
onDelete={handleDelete}
|
onDelete={handleDelete}
|
||||||
|
editMultiLine={handleEditMultiLineValue}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Dropdown items={deleteItems}>
|
<Dropdown items={deleteItems}>
|
||||||
@@ -552,12 +587,14 @@ function FileActionsDropdown({
|
|||||||
onChangeText,
|
onChangeText,
|
||||||
onChangeContentType,
|
onChangeContentType,
|
||||||
onDelete,
|
onDelete,
|
||||||
|
editMultiLine,
|
||||||
}: {
|
}: {
|
||||||
pair: Pair;
|
pair: Pair;
|
||||||
onChangeFile: ({ filePath }: { filePath: string | null }) => void;
|
onChangeFile: ({ filePath }: { filePath: string | null }) => void;
|
||||||
onChangeText: (text: string) => void;
|
onChangeText: (text: string) => void;
|
||||||
onChangeContentType: (contentType: string) => void;
|
onChangeContentType: (contentType: string) => void;
|
||||||
onDelete: () => void;
|
onDelete: () => void;
|
||||||
|
editMultiLine: () => void;
|
||||||
}) {
|
}) {
|
||||||
const onChange = useCallback(
|
const onChange = useCallback(
|
||||||
(v: string) => {
|
(v: string) => {
|
||||||
@@ -569,10 +606,15 @@ function FileActionsDropdown({
|
|||||||
|
|
||||||
const extraItems = useMemo<DropdownItem[]>(
|
const extraItems = useMemo<DropdownItem[]>(
|
||||||
() => [
|
() => [
|
||||||
|
{
|
||||||
|
label: 'Edit Multi-Line',
|
||||||
|
leftSlot: <Icon icon="file_code" />,
|
||||||
|
hidden: pair.isFile,
|
||||||
|
onSelect: editMultiLine,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Set Content-Type',
|
label: 'Set Content-Type',
|
||||||
leftSlot: <Icon icon="pencil" />,
|
leftSlot: <Icon icon="pencil" />,
|
||||||
hidden: !pair.isFile,
|
|
||||||
onSelect: async () => {
|
onSelect: async () => {
|
||||||
const contentType = await showPrompt({
|
const contentType = await showPrompt({
|
||||||
id: 'content-type',
|
id: 'content-type',
|
||||||
@@ -602,7 +644,7 @@ function FileActionsDropdown({
|
|||||||
leftSlot: <Icon icon="trash" />,
|
leftSlot: <Icon icon="trash" />,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[onChangeContentType, onChangeFile, onDelete, pair.contentType, pair.isFile],
|
[editMultiLine, onChangeContentType, onChangeFile, onDelete, pair.contentType, pair.isFile],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -629,3 +671,40 @@ function emptyPair(): PairWithId {
|
|||||||
function isPairEmpty(pair: Pair): boolean {
|
function isPairEmpty(pair: Pair): boolean {
|
||||||
return !pair.name && !pair.value;
|
return !pair.name && !pair.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function MultilineEditDialog({
|
||||||
|
defaultValue,
|
||||||
|
language,
|
||||||
|
onChange,
|
||||||
|
hide,
|
||||||
|
}: {
|
||||||
|
defaultValue: string;
|
||||||
|
language: EditorProps['language'];
|
||||||
|
onChange: (value: string) => void;
|
||||||
|
hide: () => void;
|
||||||
|
}) {
|
||||||
|
const [value, setValue] = useState<string>(defaultValue);
|
||||||
|
return (
|
||||||
|
<div className="w-[100vw] max-w-[40rem] h-[50vh] max-h-full grid grid-rows-[minmax(0,1fr)_auto]">
|
||||||
|
<Editor
|
||||||
|
heightMode="auto"
|
||||||
|
defaultValue={defaultValue}
|
||||||
|
language={language}
|
||||||
|
onChange={setValue}
|
||||||
|
stateKey={null}
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
className="ml-auto my-2"
|
||||||
|
onClick={() => {
|
||||||
|
onChange(value);
|
||||||
|
hide();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Done
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,28 +1,13 @@
|
|||||||
|
import { formatSize } from '@yaakapp-internal/lib/formatSize';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
contentLength: number;
|
contentLength: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SizeTag({ contentLength }: Props) {
|
export function SizeTag({ contentLength }: Props) {
|
||||||
let num;
|
|
||||||
let unit;
|
|
||||||
|
|
||||||
if (contentLength > 1000 * 1000 * 1000) {
|
|
||||||
num = contentLength / 1000 / 1000 / 1000;
|
|
||||||
unit = 'GB';
|
|
||||||
} else if (contentLength > 1000 * 1000) {
|
|
||||||
num = contentLength / 1000 / 1000;
|
|
||||||
unit = 'MB';
|
|
||||||
} else if (contentLength > 1000) {
|
|
||||||
num = contentLength / 1000;
|
|
||||||
unit = 'KB';
|
|
||||||
} else {
|
|
||||||
num = contentLength;
|
|
||||||
unit = 'B';
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className="font-mono" title={`${contentLength} bytes`}>
|
<span className="font-mono" title={`${contentLength} bytes`}>
|
||||||
{Math.round(num * 10) / 10} {unit}
|
{formatSize(contentLength)}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user