mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-25 19:01:38 +01:00
Move split layout
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
import type { Environment, Workspace } from '@yaakapp-internal/models';
|
||||
import { duplicateModel, patchModel } from '@yaakapp-internal/models';
|
||||
import type { TreeHandle, TreeNode, TreeProps } from '@yaakapp-internal/ui';
|
||||
import { Icon, SplitLayout, Tree } from '@yaakapp-internal/ui';
|
||||
import { atom, useAtomValue } from 'jotai';
|
||||
import { atomFamily } from 'jotai/utils';
|
||||
import { useCallback, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { createSubEnvironmentAndActivate } from '../commands/createEnvironment';
|
||||
import { activeWorkspaceAtom, activeWorkspaceIdAtom } from '../hooks/useActiveWorkspace';
|
||||
@@ -9,6 +12,7 @@ import {
|
||||
useEnvironmentsBreakdown,
|
||||
} from '../hooks/useEnvironmentsBreakdown';
|
||||
import { useHotKey } from '../hooks/useHotKey';
|
||||
import { atomWithKVStorage } from '../lib/atoms/atomWithKVStorage';
|
||||
import { deleteModelWithConfirm } from '../lib/deleteModelWithConfirm';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
import { isBaseEnvironment, isSubEnvironment } from '../lib/model_util';
|
||||
@@ -17,15 +21,10 @@ import { showColorPicker } from '../lib/showColorPicker';
|
||||
import { Banner } from './core/Banner';
|
||||
import type { ContextMenuProps, DropdownItem } from './core/Dropdown';
|
||||
import { ContextMenu } from './core/Dropdown';
|
||||
import { Icon, Tree } from '@yaakapp-internal/ui';
|
||||
import type { TreeNode, TreeHandle, TreeProps } from '@yaakapp-internal/ui';
|
||||
import { IconButton } from './core/IconButton';
|
||||
import { IconTooltip } from './core/IconTooltip';
|
||||
import { InlineCode } from './core/InlineCode';
|
||||
import type { PairEditorHandle } from './core/PairEditor';
|
||||
import { SplitLayout } from './core/SplitLayout';
|
||||
import { atomFamily } from 'jotai/utils';
|
||||
import { atomWithKVStorage } from '../lib/atoms/atomWithKVStorage';
|
||||
import { EnvironmentColorIndicator } from './EnvironmentColorIndicator';
|
||||
import { EnvironmentEditor } from './EnvironmentEditor';
|
||||
import { EnvironmentSharableTooltip } from './EnvironmentSharableTooltip';
|
||||
@@ -55,7 +54,7 @@ export function EnvironmentEditDialog({ initialEnvironmentId, setRef }: Props) {
|
||||
|
||||
return (
|
||||
<SplitLayout
|
||||
name="env_editor"
|
||||
storageKey="env_editor"
|
||||
defaultRatio={0.75}
|
||||
layout="horizontal"
|
||||
className="gap-0"
|
||||
@@ -155,24 +154,39 @@ function EnvironmentEditDialogSidebar({
|
||||
[],
|
||||
);
|
||||
|
||||
const handleDuplicateSelected = useCallback(async (items: TreeModel[]) => {
|
||||
if (items.length === 1 && items[0]) {
|
||||
const newId = await duplicateModel(items[0]);
|
||||
setSelectedEnvironmentId(newId);
|
||||
} else {
|
||||
await Promise.all(items.map(duplicateModel));
|
||||
}
|
||||
}, [setSelectedEnvironmentId]);
|
||||
const handleDuplicateSelected = useCallback(
|
||||
async (items: TreeModel[]) => {
|
||||
if (items.length === 1 && items[0]) {
|
||||
const newId = await duplicateModel(items[0]);
|
||||
setSelectedEnvironmentId(newId);
|
||||
} else {
|
||||
await Promise.all(items.map(duplicateModel));
|
||||
}
|
||||
},
|
||||
[setSelectedEnvironmentId],
|
||||
);
|
||||
|
||||
useHotKey('sidebar.selected.rename', handleRenameSelected, { enable: treeHasFocus, allowDefault: true, priority: 100 });
|
||||
useHotKey('sidebar.selected.delete', useCallback(() => {
|
||||
const items = getSelectedTreeModels();
|
||||
if (items) handleDeleteSelected(items);
|
||||
}, [getSelectedTreeModels, handleDeleteSelected]), { enable: treeHasFocus, priority: 100 });
|
||||
useHotKey('sidebar.selected.duplicate', useCallback(async () => {
|
||||
const items = getSelectedTreeModels();
|
||||
if (items) await handleDuplicateSelected(items);
|
||||
}, [getSelectedTreeModels, handleDuplicateSelected]), { enable: treeHasFocus, priority: 100 });
|
||||
useHotKey('sidebar.selected.rename', handleRenameSelected, {
|
||||
enable: treeHasFocus,
|
||||
allowDefault: true,
|
||||
priority: 100,
|
||||
});
|
||||
useHotKey(
|
||||
'sidebar.selected.delete',
|
||||
useCallback(() => {
|
||||
const items = getSelectedTreeModels();
|
||||
if (items) handleDeleteSelected(items);
|
||||
}, [getSelectedTreeModels, handleDeleteSelected]),
|
||||
{ enable: treeHasFocus, priority: 100 },
|
||||
);
|
||||
useHotKey(
|
||||
'sidebar.selected.duplicate',
|
||||
useCallback(async () => {
|
||||
const items = getSelectedTreeModels();
|
||||
if (items) await handleDuplicateSelected(items);
|
||||
}, [getSelectedTreeModels, handleDuplicateSelected]),
|
||||
{ enable: treeHasFocus, priority: 100 },
|
||||
);
|
||||
|
||||
const getContextMenu = useCallback(
|
||||
(items: TreeModel[]): ContextMenuProps['items'] => {
|
||||
@@ -249,7 +263,12 @@ function EnvironmentEditDialogSidebar({
|
||||
|
||||
return menuItems;
|
||||
},
|
||||
[baseEnvironments.length, handleDeleteEnvironment, setSelectedEnvironmentId],
|
||||
[
|
||||
baseEnvironments.length,
|
||||
handleDeleteEnvironment,
|
||||
handleDuplicateSelected,
|
||||
handleRenameSelected,
|
||||
],
|
||||
);
|
||||
|
||||
const handleDragEnd = useCallback(async function handleDragEnd({
|
||||
|
||||
@@ -7,10 +7,11 @@ import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||
import { useGrpc } from '../hooks/useGrpc';
|
||||
import { useGrpcProtoFiles } from '../hooks/useGrpcProtoFiles';
|
||||
import { activeGrpcConnectionAtom, useGrpcEvents } from '../hooks/usePinnedGrpcConnection';
|
||||
import { SplitLayout } from '@yaakapp-internal/ui';
|
||||
import { activeWorkspaceAtom } from '../hooks/useActiveWorkspace';
|
||||
import { workspaceLayoutAtom } from '../lib/atoms';
|
||||
import { Banner } from './core/Banner';
|
||||
import { HotkeyList } from './core/HotkeyList';
|
||||
import { SplitLayout } from './core/SplitLayout';
|
||||
import { GrpcRequestPane } from './GrpcRequestPane';
|
||||
import { GrpcResponsePane } from './GrpcResponsePane';
|
||||
|
||||
@@ -22,6 +23,8 @@ const emptyArray: string[] = [];
|
||||
|
||||
export function GrpcConnectionLayout({ style }: Props) {
|
||||
const workspaceLayout = useAtomValue(workspaceLayoutAtom);
|
||||
const activeWorkspace = useAtomValue(activeWorkspaceAtom);
|
||||
const wsId = activeWorkspace?.id ?? 'n/a';
|
||||
const activeRequest = useActiveRequest('grpc_request');
|
||||
const activeConnection = useAtomValue(activeGrpcConnectionAtom);
|
||||
const grpcEvents = useGrpcEvents(activeConnection?.id ?? null);
|
||||
@@ -79,7 +82,7 @@ export function GrpcConnectionLayout({ style }: Props) {
|
||||
|
||||
return (
|
||||
<SplitLayout
|
||||
name="grpc_layout"
|
||||
storageKey={`grpc_layout::${wsId}`}
|
||||
className="p-3 gap-1.5"
|
||||
style={style}
|
||||
layout={workspaceLayout}
|
||||
|
||||
@@ -3,7 +3,7 @@ import classNames from 'classnames';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { useCallback, useMemo, useRef } from 'react';
|
||||
import { useAuthTab } from '../hooks/useAuthTab';
|
||||
import { useContainerSize } from '../hooks/useContainerQuery';
|
||||
import { useContainerSize } from '@yaakapp-internal/ui';
|
||||
import type { ReflectResponseService } from '../hooks/useGrpc';
|
||||
import { useHeadersTab } from '../hooks/useHeadersTab';
|
||||
import { useInheritedHeaders } from '../hooks/useInheritedHeaders';
|
||||
|
||||
@@ -92,7 +92,7 @@ export function GrpcResponsePane({ style, methodType, activeRequest }: Props) {
|
||||
getEventKey={(event) => event.id}
|
||||
error={activeConnection.error}
|
||||
header={header}
|
||||
splitLayoutName="grpc_events"
|
||||
splitLayoutStorageKey="grpc_events"
|
||||
defaultRatio={0.4}
|
||||
renderRow={({ event, isActive, onClick }) => (
|
||||
<GrpcEventRow event={event} isActive={isActive} onClick={onClick} />
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import type { HttpRequest } from '@yaakapp-internal/models';
|
||||
import type { SlotProps } from '@yaakapp-internal/ui';
|
||||
import { SplitLayout } from '@yaakapp-internal/ui';
|
||||
import classNames from 'classnames';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { useCurrentGraphQLSchema } from '../hooks/useIntrospectGraphQL';
|
||||
import { activeWorkspaceAtom } from '../hooks/useActiveWorkspace';
|
||||
import { workspaceLayoutAtom } from '../lib/atoms';
|
||||
import type { SlotProps } from './core/SplitLayout';
|
||||
import { SplitLayout } from './core/SplitLayout';
|
||||
import { GraphQLDocsExplorer } from './graphql/GraphQLDocsExplorer';
|
||||
import { showGraphQLDocExplorerAtom } from './graphql/graphqlAtoms';
|
||||
import { HttpRequestPane } from './HttpRequestPane';
|
||||
@@ -20,10 +21,12 @@ export function HttpRequestLayout({ activeRequest, style }: Props) {
|
||||
const showGraphQLDocExplorer = useAtomValue(showGraphQLDocExplorerAtom);
|
||||
const graphQLSchema = useCurrentGraphQLSchema(activeRequest);
|
||||
const workspaceLayout = useAtomValue(workspaceLayoutAtom);
|
||||
const activeWorkspace = useAtomValue(activeWorkspaceAtom);
|
||||
const wsId = activeWorkspace?.id ?? 'n/a';
|
||||
|
||||
const requestResponseSplit = ({ style }: Pick<SlotProps, 'style'>) => (
|
||||
<SplitLayout
|
||||
name="http_layout"
|
||||
storageKey={`http_layout::${wsId}`}
|
||||
className="p-3 gap-1.5"
|
||||
style={style}
|
||||
layout={workspaceLayout}
|
||||
@@ -47,7 +50,7 @@ export function HttpRequestLayout({ activeRequest, style }: Props) {
|
||||
) {
|
||||
return (
|
||||
<SplitLayout
|
||||
name="graphql_layout"
|
||||
storageKey={`graphql_layout::${wsId}`}
|
||||
defaultRatio={1 / 3}
|
||||
firstSlot={requestResponseSplit}
|
||||
secondSlot={({ style, orientation }) => (
|
||||
|
||||
@@ -55,7 +55,7 @@ function Inner({ response, viewMode }: Props) {
|
||||
isLoading={isLoading}
|
||||
loadingMessage="Loading events..."
|
||||
emptyMessage="No events recorded"
|
||||
splitLayoutName="http_response_events"
|
||||
splitLayoutStorageKey="http_response_events"
|
||||
defaultRatio={0.25}
|
||||
renderRow={({ event, isActive, onClick }) => {
|
||||
const display = getEventDisplay(event.event);
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
import classNames from 'classnames';
|
||||
import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
|
||||
const START_DISTANCE = 7;
|
||||
|
||||
export interface ResizeHandleEvent {
|
||||
x: number;
|
||||
y: number;
|
||||
xStart: number;
|
||||
yStart: number;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
style?: CSSProperties;
|
||||
className?: string;
|
||||
onResizeStart?: () => void;
|
||||
onResizeEnd?: () => void;
|
||||
onResizeMove?: (e: ResizeHandleEvent) => void;
|
||||
onReset?: () => void;
|
||||
side: 'left' | 'right' | 'top';
|
||||
justify: 'center' | 'end' | 'start';
|
||||
}
|
||||
|
||||
export function ResizeHandle({
|
||||
style,
|
||||
justify,
|
||||
className,
|
||||
onResizeStart,
|
||||
onResizeEnd,
|
||||
onResizeMove,
|
||||
onReset,
|
||||
side,
|
||||
}: Props) {
|
||||
const vertical = side === 'top';
|
||||
const [isResizing, setIsResizing] = useState<boolean>(false);
|
||||
const moveState = useRef<{
|
||||
move: (e: MouseEvent) => void;
|
||||
up: (e: MouseEvent) => void;
|
||||
calledStart: boolean;
|
||||
xStart: number;
|
||||
yStart: number;
|
||||
} | null>(null);
|
||||
|
||||
const handlePointerDown = useCallback(
|
||||
(e: ReactMouseEvent<HTMLDivElement>) => {
|
||||
function move(e: MouseEvent) {
|
||||
if (moveState.current == null) return;
|
||||
|
||||
const xDistance = moveState.current.xStart - e.clientX;
|
||||
const yDistance = moveState.current.yStart - e.clientY;
|
||||
const distance = Math.abs(vertical ? yDistance : xDistance);
|
||||
if (moveState.current.calledStart) {
|
||||
onResizeMove?.({
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
xStart: moveState.current.xStart,
|
||||
yStart: moveState.current.yStart,
|
||||
});
|
||||
} else if (distance > START_DISTANCE) {
|
||||
onResizeStart?.();
|
||||
moveState.current.calledStart = true;
|
||||
setIsResizing(true);
|
||||
}
|
||||
}
|
||||
|
||||
function up() {
|
||||
setIsResizing(false);
|
||||
moveState.current = null;
|
||||
document.documentElement.removeEventListener('mousemove', move);
|
||||
document.documentElement.removeEventListener('mouseup', up);
|
||||
onResizeEnd?.();
|
||||
}
|
||||
|
||||
moveState.current = { calledStart: false, xStart: e.clientX, yStart: e.clientY, move, up };
|
||||
|
||||
document.documentElement.addEventListener('mousemove', move);
|
||||
document.documentElement.addEventListener('mouseup', up);
|
||||
},
|
||||
[onResizeEnd, onResizeMove, onResizeStart, vertical],
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
aria-hidden
|
||||
style={style}
|
||||
onDoubleClick={onReset}
|
||||
onPointerDown={handlePointerDown}
|
||||
className={classNames(
|
||||
className,
|
||||
'group z-10 flex select-none transition-colors hover:bg-surface-active rounded-full',
|
||||
// 'bg-info', // For debugging
|
||||
vertical ? 'w-full h-1.5 cursor-row-resize' : 'h-full w-1.5 cursor-col-resize',
|
||||
justify === 'center' && 'justify-center',
|
||||
justify === 'end' && 'justify-end',
|
||||
justify === 'start' && 'justify-start',
|
||||
side === 'right' && 'right-0',
|
||||
side === 'left' && 'left-0',
|
||||
side === 'top' && 'top-0',
|
||||
)}
|
||||
>
|
||||
{/* Show global overlay with cursor style to ensure cursor remains the same when moving quickly */}
|
||||
{isResizing && (
|
||||
<div
|
||||
className={classNames(
|
||||
// 'bg-[rgba(255,0,0,0.1)]', // For debugging
|
||||
'fixed -left-[100vw] -right-[100vw] -top-[100vh] -bottom-[100vh]',
|
||||
vertical && 'cursor-row-resize',
|
||||
!vertical && 'cursor-col-resize',
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import { useAtomValue } from 'jotai';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { activeWorkspaceAtom } from '../../hooks/useActiveWorkspace';
|
||||
import { clamp } from '../../lib/clamp';
|
||||
import { clamp } from '@yaakapp-internal/ui';
|
||||
import { showConfirm } from '../../lib/confirm';
|
||||
import { invokeCmd } from '../../lib/tauri';
|
||||
import { CargoFeature } from '../CargoFeature';
|
||||
|
||||
@@ -3,8 +3,9 @@ import classNames from 'classnames';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import type { CSSProperties } from 'react';
|
||||
|
||||
import { SplitLayout } from '@yaakapp-internal/ui';
|
||||
import { activeWorkspaceAtom } from '../hooks/useActiveWorkspace';
|
||||
import { workspaceLayoutAtom } from '../lib/atoms';
|
||||
import { SplitLayout } from './core/SplitLayout';
|
||||
import { WebsocketRequestPane } from './WebsocketRequestPane';
|
||||
import { WebsocketResponsePane } from './WebsocketResponsePane';
|
||||
|
||||
@@ -15,9 +16,11 @@ interface Props {
|
||||
|
||||
export function WebsocketRequestLayout({ activeRequest, style }: Props) {
|
||||
const workspaceLayout = useAtomValue(workspaceLayoutAtom);
|
||||
const activeWorkspace = useAtomValue(activeWorkspaceAtom);
|
||||
const wsId = activeWorkspace?.id ?? 'n/a';
|
||||
return (
|
||||
<SplitLayout
|
||||
name="websocket_layout"
|
||||
storageKey={`websocket_layout::${wsId}`}
|
||||
className="p-3 gap-1.5"
|
||||
layout={workspaceLayout}
|
||||
style={style}
|
||||
|
||||
@@ -69,7 +69,7 @@ export function WebsocketResponsePane({ activeRequest }: Props) {
|
||||
getEventKey={(event) => event.id}
|
||||
error={activeConnection.error}
|
||||
header={header}
|
||||
splitLayoutName="websocket_events"
|
||||
splitLayoutStorageKey="websocket_events"
|
||||
defaultRatio={0.4}
|
||||
renderRow={({ event, isActive, onClick }) => (
|
||||
<WebsocketEventRow event={event} isActive={isActive} onClick={onClick} />
|
||||
|
||||
@@ -43,8 +43,7 @@ import { GrpcConnectionLayout } from './GrpcConnectionLayout';
|
||||
import { HeaderSize } from '@yaakapp-internal/ui';
|
||||
import { HttpRequestLayout } from './HttpRequestLayout';
|
||||
import { Overlay } from './Overlay';
|
||||
import type { ResizeHandleEvent } from './ResizeHandle';
|
||||
import { ResizeHandle } from './ResizeHandle';
|
||||
import { ResizeHandle, type ResizeHandleEvent } from '@yaakapp-internal/ui';
|
||||
import Sidebar from './Sidebar';
|
||||
import { SidebarActions } from './SidebarActions';
|
||||
import { WebsocketRequestLayout } from './WebsocketRequestLayout';
|
||||
|
||||
@@ -8,7 +8,7 @@ import { AutoScroller } from './AutoScroller';
|
||||
import { Banner } from './Banner';
|
||||
import { Button } from './Button';
|
||||
import { Separator } from './Separator';
|
||||
import { SplitLayout } from './SplitLayout';
|
||||
import { SplitLayout } from '@yaakapp-internal/ui';
|
||||
import { HStack } from './Stacks';
|
||||
import { IconButton } from './IconButton';
|
||||
import classNames from 'classnames';
|
||||
@@ -37,8 +37,8 @@ interface EventViewerProps<T> {
|
||||
/** Error message to display as a banner */
|
||||
error?: string | null;
|
||||
|
||||
/** Name for SplitLayout state persistence */
|
||||
splitLayoutName: string;
|
||||
/** Key for SplitLayout state persistence */
|
||||
splitLayoutStorageKey: string;
|
||||
|
||||
/** Default ratio for the split (0.0 - 1.0) */
|
||||
defaultRatio?: number;
|
||||
@@ -66,7 +66,7 @@ export function EventViewer<T>({
|
||||
renderDetail,
|
||||
header,
|
||||
error,
|
||||
splitLayoutName,
|
||||
splitLayoutStorageKey,
|
||||
defaultRatio = 0.4,
|
||||
enableKeyboardNav = true,
|
||||
isLoading = false,
|
||||
@@ -151,7 +151,7 @@ export function EventViewer<T>({
|
||||
<div ref={containerRef} className="h-full">
|
||||
<SplitLayout
|
||||
layout="vertical"
|
||||
name={splitLayoutName}
|
||||
storageKey={splitLayoutStorageKey}
|
||||
defaultRatio={defaultRatio}
|
||||
minHeightPx={10}
|
||||
firstSlot={({ style }) => (
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
import classNames from 'classnames';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import type { CSSProperties, ReactNode } from 'react';
|
||||
import { useCallback, useMemo, useRef } from 'react';
|
||||
import { useLocalStorage } from 'react-use';
|
||||
import { activeWorkspaceAtom } from '../../hooks/useActiveWorkspace';
|
||||
import { useContainerSize } from '../../hooks/useContainerQuery';
|
||||
import { clamp } from '../../lib/clamp';
|
||||
import type { ResizeHandleEvent } from '../ResizeHandle';
|
||||
import { ResizeHandle } from '../ResizeHandle';
|
||||
|
||||
export type SplitLayoutLayout = 'responsive' | 'horizontal' | 'vertical';
|
||||
|
||||
export interface SlotProps {
|
||||
orientation: 'horizontal' | 'vertical';
|
||||
style: CSSProperties;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
name: string;
|
||||
firstSlot: (props: SlotProps) => ReactNode;
|
||||
secondSlot: null | ((props: SlotProps) => ReactNode);
|
||||
style?: CSSProperties;
|
||||
className?: string;
|
||||
defaultRatio?: number;
|
||||
minHeightPx?: number;
|
||||
minWidthPx?: number;
|
||||
layout?: SplitLayoutLayout;
|
||||
resizeHandleClassName?: string;
|
||||
}
|
||||
|
||||
const baseProperties = { minWidth: 0 };
|
||||
const areaL = { ...baseProperties, gridArea: 'left' };
|
||||
const areaR = { ...baseProperties, gridArea: 'right' };
|
||||
const areaD = { ...baseProperties, gridArea: 'drag' };
|
||||
|
||||
const STACK_VERTICAL_WIDTH = 500;
|
||||
|
||||
export function SplitLayout({
|
||||
style,
|
||||
firstSlot,
|
||||
secondSlot,
|
||||
className,
|
||||
name,
|
||||
layout = 'responsive',
|
||||
resizeHandleClassName,
|
||||
defaultRatio = 0.5,
|
||||
minHeightPx = 10,
|
||||
minWidthPx = 10,
|
||||
}: Props) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const activeWorkspace = useAtomValue(activeWorkspaceAtom);
|
||||
const [widthRaw, setWidth] = useLocalStorage<number>(
|
||||
`${name}_width::${activeWorkspace?.id ?? 'n/a'}`,
|
||||
);
|
||||
const [heightRaw, setHeight] = useLocalStorage<number>(
|
||||
`${name}_height::${activeWorkspace?.id ?? 'n/a'}`,
|
||||
);
|
||||
const width = widthRaw ?? defaultRatio;
|
||||
let height = heightRaw ?? defaultRatio;
|
||||
|
||||
if (!secondSlot) {
|
||||
height = 0;
|
||||
minHeightPx = 0;
|
||||
}
|
||||
|
||||
const size = useContainerSize(containerRef);
|
||||
const verticalBasedOnSize = size.width !== 0 && size.width < STACK_VERTICAL_WIDTH;
|
||||
const vertical = layout !== 'horizontal' && (layout === 'vertical' || verticalBasedOnSize);
|
||||
|
||||
const styles = useMemo<CSSProperties>(() => {
|
||||
return {
|
||||
...style,
|
||||
gridTemplate: vertical
|
||||
? `
|
||||
' ${areaL.gridArea}' minmax(0,${1 - height}fr)
|
||||
' ${areaD.gridArea}' 0
|
||||
' ${areaR.gridArea}' minmax(${minHeightPx}px,${height}fr)
|
||||
/ 1fr
|
||||
`
|
||||
: `
|
||||
' ${areaL.gridArea} ${areaD.gridArea} ${areaR.gridArea}' minmax(0,1fr)
|
||||
/ ${1 - width}fr 0 ${width}fr
|
||||
`,
|
||||
};
|
||||
}, [style, vertical, height, minHeightPx, width]);
|
||||
|
||||
const handleReset = useCallback(() => {
|
||||
if (vertical) setHeight(defaultRatio);
|
||||
else setWidth(defaultRatio);
|
||||
}, [vertical, setHeight, defaultRatio, setWidth]);
|
||||
|
||||
const handleResizeMove = useCallback(
|
||||
(e: ResizeHandleEvent) => {
|
||||
if (containerRef.current === null) return;
|
||||
|
||||
// const containerRect = containerRef.current.getBoundingClientRect();
|
||||
const { paddingLeft, paddingRight, paddingTop, paddingBottom } = getComputedStyle(
|
||||
containerRef.current,
|
||||
);
|
||||
const $c = containerRef.current;
|
||||
const containerWidth =
|
||||
$c.clientWidth - Number.parseFloat(paddingLeft) - Number.parseFloat(paddingRight);
|
||||
const containerHeight =
|
||||
$c.clientHeight - Number.parseFloat(paddingTop) - Number.parseFloat(paddingBottom);
|
||||
|
||||
const mouseStartX = e.xStart;
|
||||
const mouseStartY = e.yStart;
|
||||
const startWidth = containerWidth * width;
|
||||
const startHeight = containerHeight * height;
|
||||
|
||||
if (vertical) {
|
||||
const maxHeightPx = containerHeight - minHeightPx;
|
||||
const newHeightPx = clamp(startHeight - (e.y - mouseStartY), minHeightPx, maxHeightPx);
|
||||
setHeight(newHeightPx / containerHeight);
|
||||
} else {
|
||||
const maxWidthPx = containerWidth - minWidthPx;
|
||||
const newWidthPx = clamp(startWidth - (e.x - mouseStartX), minWidthPx, maxWidthPx);
|
||||
setWidth(newWidthPx / containerWidth);
|
||||
}
|
||||
},
|
||||
[width, height, vertical, minHeightPx, setHeight, minWidthPx, setWidth],
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
style={styles}
|
||||
className={classNames(className, 'grid w-full h-full overflow-hidden')}
|
||||
>
|
||||
{firstSlot({ style: areaL, orientation: vertical ? 'vertical' : 'horizontal' })}
|
||||
{secondSlot && (
|
||||
<>
|
||||
<ResizeHandle
|
||||
style={areaD}
|
||||
className={classNames(
|
||||
resizeHandleClassName,
|
||||
vertical ? '-translate-y-1' : '-translate-x-1',
|
||||
)}
|
||||
onResizeMove={handleResizeMove}
|
||||
onReset={handleReset}
|
||||
side={vertical ? 'top' : 'left'}
|
||||
justify="center"
|
||||
/>
|
||||
{secondSlot({ style: areaR, orientation: vertical ? 'vertical' : 'horizontal' })}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -23,7 +23,7 @@ import { Icon } from '@yaakapp-internal/ui';
|
||||
import { InlineCode } from '../core/InlineCode';
|
||||
import { Input } from '../core/Input';
|
||||
import { Separator } from '../core/Separator';
|
||||
import { SplitLayout } from '../core/SplitLayout';
|
||||
import { SplitLayout } from '@yaakapp-internal/ui';
|
||||
import { HStack } from '../core/Stacks';
|
||||
import { EmptyStateText } from '../EmptyStateText';
|
||||
import { gitCallbacks } from './callbacks';
|
||||
@@ -185,13 +185,13 @@ export function GitCommitDialog({ syncDir, onDone, workspace }: Props) {
|
||||
return (
|
||||
<div className="h-full px-2 pb-4">
|
||||
<SplitLayout
|
||||
name="commit-horizontal"
|
||||
storageKey="commit-horizontal"
|
||||
layout="horizontal"
|
||||
defaultRatio={0.6}
|
||||
firstSlot={({ style }) => (
|
||||
<div style={style} className="h-full px-4">
|
||||
<SplitLayout
|
||||
name="commit-vertical"
|
||||
storageKey="commit-vertical"
|
||||
layout="vertical"
|
||||
defaultRatio={0.35}
|
||||
firstSlot={({ style: innerStyle }) => (
|
||||
|
||||
@@ -24,7 +24,7 @@ import { useAtomValue } from 'jotai';
|
||||
import type { CSSProperties, HTMLAttributes, KeyboardEvent, ReactNode } from 'react';
|
||||
import { Fragment, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useClickOutside } from '../../hooks/useClickOutside';
|
||||
import { useContainerSize } from '../../hooks/useContainerQuery';
|
||||
import { useContainerSize } from '@yaakapp-internal/ui';
|
||||
import { Icon, useDebouncedValue } from '@yaakapp-internal/ui';
|
||||
import { useStateWithDeps } from '../../hooks/useStateWithDeps';
|
||||
import { jotaiStore } from '../../lib/jotai';
|
||||
|
||||
@@ -38,7 +38,7 @@ function ActualEventStreamViewer({ response }: Props) {
|
||||
events={events.data ?? []}
|
||||
getEventKey={(_, index) => String(index)}
|
||||
error={events.error ? String(events.error) : null}
|
||||
splitLayoutName="sse_events"
|
||||
splitLayoutStorageKey="sse_events"
|
||||
defaultRatio={0.4}
|
||||
renderRow={({ event, index, isActive, onClick }) => (
|
||||
<EventViewerRow
|
||||
|
||||
@@ -5,7 +5,7 @@ import './PdfViewer.css';
|
||||
import type { PDFDocumentProxy } from 'pdfjs-dist';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { Document, Page } from 'react-pdf';
|
||||
import { useContainerSize } from '../../hooks/useContainerQuery';
|
||||
import { useContainerSize } from '@yaakapp-internal/ui';
|
||||
|
||||
import('react-pdf').then(({ pdfjs }) => {
|
||||
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
|
||||
|
||||
Reference in New Issue
Block a user