mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-02-15 12:17:43 +01:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db6a7dcabb | ||
|
|
00b1f90074 | ||
|
|
e292235792 | ||
|
|
5f86802d88 | ||
|
|
acb7f2e49b | ||
|
|
e2a15609bf | ||
|
|
aa3bfd78c4 |
@@ -37,10 +37,11 @@ export function pluginHookImport(contents: string): { resources: ExportResources
|
||||
id: generateId('wk'),
|
||||
name: info.name || 'Postman Import',
|
||||
description: info.description || '',
|
||||
variables: root.variable?.map((v: any) => ({
|
||||
name: v.key,
|
||||
value: v.value,
|
||||
})),
|
||||
variables:
|
||||
root.variable?.map((v: any) => ({
|
||||
name: v.key,
|
||||
value: v.value,
|
||||
})) ?? [],
|
||||
};
|
||||
exportResources.workspaces.push(workspace);
|
||||
|
||||
|
||||
@@ -17,10 +17,10 @@ function v(t) {
|
||||
id: m("wk"),
|
||||
name: n.name || "Postman Import",
|
||||
description: n.description || "",
|
||||
variables: (b = e.variable) == null ? void 0 : b.map((r) => ({
|
||||
variables: ((b = e.variable) == null ? void 0 : b.map((r) => ({
|
||||
name: r.key,
|
||||
value: r.value
|
||||
}))
|
||||
}))) ?? []
|
||||
};
|
||||
i.workspaces.push(c);
|
||||
const f = (r, u = null) => {
|
||||
|
||||
@@ -23,7 +23,7 @@ use log::{debug, error, info, warn};
|
||||
use rand::random;
|
||||
use serde_json::{json, Value};
|
||||
use sqlx::migrate::Migrator;
|
||||
use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode};
|
||||
use sqlx::sqlite::{SqliteConnectOptions};
|
||||
use sqlx::types::Json;
|
||||
use sqlx::{Pool, Sqlite, SqlitePool};
|
||||
#[cfg(target_os = "macos")]
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
},
|
||||
"package": {
|
||||
"productName": "Yaak",
|
||||
"version": "2024.3.8"
|
||||
"version": "2024.3.9"
|
||||
},
|
||||
"tauri": {
|
||||
"windows": [],
|
||||
|
||||
@@ -13,7 +13,7 @@ export function BasicAuth<T extends HttpRequest | GrpcRequest>({ request }: Prop
|
||||
const updateGrpcRequest = useUpdateGrpcRequest(request.id);
|
||||
|
||||
return (
|
||||
<VStack className="my-2" space={2}>
|
||||
<VStack className="py-2 overflow-y-auto h-full" space={2}>
|
||||
<Input
|
||||
useTemplating
|
||||
autocompleteVariables
|
||||
|
||||
@@ -15,6 +15,7 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
|
||||
const activeEnvironmentId = useActiveEnvironmentId();
|
||||
const workspaces = useWorkspaces();
|
||||
const requests = useRequests();
|
||||
const [command, setCommand] = useState<string>('');
|
||||
|
||||
const items = useMemo<{ label: string; onSelect: () => void; key: string }[]>(() => {
|
||||
const items = [];
|
||||
@@ -47,10 +48,17 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
|
||||
return items;
|
||||
}, [activeEnvironmentId, requests, routes, workspaces]);
|
||||
|
||||
const handleSelectAndClose = (cb: () => void) => {
|
||||
onClose();
|
||||
cb();
|
||||
};
|
||||
const filteredItems = useMemo(() => {
|
||||
return items.filter((v) => v.label.toLowerCase().includes(command.toLowerCase()));
|
||||
}, [command, items]);
|
||||
|
||||
const handleSelectAndClose = useCallback(
|
||||
(cb: () => void) => {
|
||||
onClose();
|
||||
cb();
|
||||
},
|
||||
[onClose],
|
||||
);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: KeyboardEvent) => {
|
||||
@@ -59,13 +67,13 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
setSelectedIndex((prev) => prev - 1);
|
||||
} else if (e.key === 'Enter') {
|
||||
const item = items[selectedIndex];
|
||||
const item = filteredItems[selectedIndex];
|
||||
if (item) {
|
||||
handleSelectAndClose(item.onSelect);
|
||||
}
|
||||
}
|
||||
},
|
||||
[items, onClose, selectedIndex],
|
||||
[filteredItems, handleSelectAndClose, selectedIndex],
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -76,11 +84,13 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
|
||||
name="command"
|
||||
label="Command"
|
||||
placeholder="Type a command"
|
||||
defaultValue=""
|
||||
onChange={setCommand}
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
</div>
|
||||
<div className="h-full px-1.5 overflow-y-auto">
|
||||
{items.map((v, i) => (
|
||||
{filteredItems.map((v, i) => (
|
||||
<CommandPaletteItem
|
||||
active={i === selectedIndex}
|
||||
key={v.key}
|
||||
|
||||
@@ -56,7 +56,7 @@ interface TreeNode {
|
||||
}
|
||||
|
||||
export function Sidebar({ className }: Props) {
|
||||
const { hidden, show, hide } = useSidebarHidden();
|
||||
const [hidden, setHidden] = useSidebarHidden();
|
||||
const sidebarRef = useRef<HTMLLIElement>(null);
|
||||
const activeRequest = useActiveRequest();
|
||||
const activeEnvironmentId = useActiveEnvironmentId();
|
||||
@@ -241,16 +241,15 @@ export function Sidebar({ className }: Props) {
|
||||
useKeyPressEvent('Delete', handleDeleteKey);
|
||||
|
||||
useHotKey('sidebar.focus', async () => {
|
||||
console.log('sidebar.focus', { hidden, hasFocus });
|
||||
// Hide the sidebar if it's already focused
|
||||
if (!hidden && hasFocus) {
|
||||
await hide();
|
||||
await setHidden(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Show the sidebar if it's hidden
|
||||
if (hidden) {
|
||||
await show();
|
||||
await setHidden(false);
|
||||
}
|
||||
|
||||
// Select 0 index on focus if none selected
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
import { memo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { useFloatingSidebarHidden } from '../hooks/useFloatingSidebarHidden';
|
||||
import { useShouldFloatSidebar } from '../hooks/useShouldFloatSidebar';
|
||||
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
||||
import { trackEvent } from '../lib/analytics';
|
||||
import { IconButton } from './core/IconButton';
|
||||
import { HStack } from './core/Stacks';
|
||||
import { CreateDropdown } from './CreateDropdown';
|
||||
|
||||
export const SidebarActions = memo(function SidebarActions() {
|
||||
const { hidden, show, hide } = useSidebarHidden();
|
||||
export function SidebarActions() {
|
||||
const floating = useShouldFloatSidebar();
|
||||
const [normalHidden, setNormalHidden] = useSidebarHidden();
|
||||
const [floatingHidden, setFloatingHidden] = useFloatingSidebarHidden();
|
||||
|
||||
const hidden = floating ? floatingHidden : normalHidden;
|
||||
const setHidden = useMemo(
|
||||
() => (floating ? setFloatingHidden : setNormalHidden),
|
||||
[floating, setFloatingHidden, setNormalHidden],
|
||||
);
|
||||
|
||||
return (
|
||||
<HStack className="h-full" alignItems="center">
|
||||
@@ -14,10 +24,9 @@ export const SidebarActions = memo(function SidebarActions() {
|
||||
onClick={async () => {
|
||||
trackEvent('sidebar', 'toggle');
|
||||
|
||||
// NOTE: We're not using `toggle` because it may be out of sync
|
||||
// from changes in other windows
|
||||
if (hidden) await show();
|
||||
else await hide();
|
||||
// NOTE: We're not using the (h) => !h pattern here because the data
|
||||
// might be different if another window changed it (out of sync)
|
||||
await setHidden(!hidden);
|
||||
}}
|
||||
className="pointer-events-auto"
|
||||
size="sm"
|
||||
@@ -35,4 +44,4 @@ export const SidebarActions = memo(function SidebarActions() {
|
||||
</CreateDropdown>
|
||||
</HStack>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,14 +6,16 @@ import type {
|
||||
MouseEvent as ReactMouseEvent,
|
||||
ReactNode,
|
||||
} from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { useWindowSize } from 'react-use';
|
||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
||||
import { useActiveWorkspaceId } from '../hooks/useActiveWorkspaceId';
|
||||
import { useFloatingSidebarHidden } from '../hooks/useFloatingSidebarHidden';
|
||||
import { useImportData } from '../hooks/useImportData';
|
||||
import { useIsFullscreen } from '../hooks/useIsFullscreen';
|
||||
import { useOsInfo } from '../hooks/useOsInfo';
|
||||
import { useShouldFloatSidebar } from '../hooks/useShouldFloatSidebar';
|
||||
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
||||
import { useSidebarWidth } from '../hooks/useSidebarWidth';
|
||||
import { useWorkspaces } from '../hooks/useWorkspaces';
|
||||
@@ -37,34 +39,22 @@ const head = { gridArea: 'head' };
|
||||
const body = { gridArea: 'body' };
|
||||
const drag = { gridArea: 'drag' };
|
||||
|
||||
const WINDOW_FLOATING_SIDEBAR_WIDTH = 600;
|
||||
|
||||
export default function Workspace() {
|
||||
const workspaces = useWorkspaces();
|
||||
const activeWorkspace = useActiveWorkspace();
|
||||
const activeWorkspaceId = useActiveWorkspaceId();
|
||||
const { setWidth, width, resetWidth } = useSidebarWidth();
|
||||
const { hide, show, hidden } = useSidebarHidden();
|
||||
const [sidebarHidden, setSidebarHidden] = useSidebarHidden();
|
||||
const [floatingSidebarHidden, setFloatingSidebarHidden] = useFloatingSidebarHidden();
|
||||
const activeRequest = useActiveRequest();
|
||||
const windowSize = useWindowSize();
|
||||
const importData = useImportData();
|
||||
const [floating, setFloating] = useState<boolean>(false);
|
||||
const floating = useShouldFloatSidebar();
|
||||
const [isResizing, setIsResizing] = useState<boolean>(false);
|
||||
const moveState = useRef<{ move: (e: MouseEvent) => void; up: (e: MouseEvent) => void } | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
// float/un-float sidebar on window resize
|
||||
useEffect(() => {
|
||||
const shouldHide = windowSize.width <= WINDOW_FLOATING_SIDEBAR_WIDTH;
|
||||
if (shouldHide && !floating) {
|
||||
setFloating(true);
|
||||
hide().catch(console.error);
|
||||
} else if (!shouldHide && floating) {
|
||||
setFloating(false);
|
||||
}
|
||||
}, [floating, hide, windowSize.width]);
|
||||
|
||||
const unsub = () => {
|
||||
if (moveState.current !== null) {
|
||||
document.documentElement.removeEventListener('mousemove', moveState.current.move);
|
||||
@@ -84,10 +74,10 @@ export default function Workspace() {
|
||||
e.preventDefault(); // Prevent text selection and things
|
||||
const newWidth = startWidth + (e.clientX - mouseStartX);
|
||||
if (newWidth < 50) {
|
||||
await hide();
|
||||
await setSidebarHidden(true);
|
||||
resetWidth();
|
||||
} else {
|
||||
await show();
|
||||
await setSidebarHidden(false);
|
||||
setWidth(newWidth);
|
||||
}
|
||||
},
|
||||
@@ -101,10 +91,10 @@ export default function Workspace() {
|
||||
document.documentElement.addEventListener('mouseup', moveState.current.up);
|
||||
setIsResizing(true);
|
||||
},
|
||||
[setWidth, resetWidth, width, hide, show],
|
||||
[width, setSidebarHidden, resetWidth, setWidth],
|
||||
);
|
||||
|
||||
const sideWidth = hidden ? 0 : width;
|
||||
const sideWidth = sidebarHidden ? 0 : width;
|
||||
const styles = useMemo<CSSProperties>(
|
||||
() => ({
|
||||
gridTemplate: floating
|
||||
@@ -144,7 +134,11 @@ export default function Workspace() {
|
||||
)}
|
||||
>
|
||||
{floating ? (
|
||||
<Overlay open={!hidden} portalName="sidebar" onClose={hide}>
|
||||
<Overlay
|
||||
open={!floatingSidebarHidden}
|
||||
portalName="sidebar"
|
||||
onClose={() => setFloatingSidebarHidden(true)}
|
||||
>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
|
||||
@@ -16,7 +16,7 @@ export function Banner({ children, className, color = 'gray' }: Props) {
|
||||
color === 'gray' && 'border-gray-500/60 bg-gray-300/10 text-gray-800',
|
||||
color === 'warning' && 'border-orange-500/60 bg-orange-300/10 text-orange-800',
|
||||
color === 'danger' && 'border-red-500/60 bg-red-300/10 text-red-800',
|
||||
color === 'success' && 'border-green-500/60 bg-green-300/10 text-green-800',
|
||||
color === 'success' && 'border-violet-500/60 bg-violet-300/10 text-violet-800',
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -34,8 +34,8 @@ export function Checkbox({
|
||||
<input
|
||||
aria-hidden
|
||||
className={classNames(
|
||||
'opacity-50 appearance-none w-4 h-4 flex-shrink-0 border border-[currentColor]',
|
||||
'rounded hocus:border-focus hocus:bg-focus/[5%] hocus:opacity-100 outline-none ring-0',
|
||||
'appearance-none w-4 h-4 flex-shrink-0 border border-highlight',
|
||||
'rounded hocus:border-focus hocus:bg-focus/[5%] outline-none ring-0',
|
||||
)}
|
||||
type="checkbox"
|
||||
disabled={disabled}
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
}
|
||||
|
||||
.placeholder-widget {
|
||||
@apply text-xs text-violet-700 dark:text-violet-700 px-1 rounded cursor-default dark:shadow;
|
||||
@apply text-xs text-violet-700 dark:text-violet-700 px-1 mx-[0.5px] rounded cursor-default dark:shadow;
|
||||
|
||||
/* NOTE: Background and border are translucent so we can see text selection through it */
|
||||
@apply bg-violet-500/20 border border-violet-500/20 border-opacity-40;
|
||||
@@ -131,7 +131,8 @@
|
||||
}
|
||||
|
||||
.cm-editor .fold-gutter-icon {
|
||||
@apply pt-[0.25em] pl-[0.4em] px-[0.4em] h-4 cursor-pointer rounded;
|
||||
@apply pt-[0.25em] pl-[0.4em] px-[0.4em] h-4 rounded;
|
||||
@apply cursor-default !important;
|
||||
}
|
||||
|
||||
.cm-editor .fold-gutter-icon::after {
|
||||
@@ -152,8 +153,9 @@
|
||||
}
|
||||
|
||||
.cm-editor .cm-foldPlaceholder {
|
||||
@apply px-2 border border-gray-400/50 bg-gray-300/50 cursor-default;
|
||||
@apply px-2 border border-gray-400/50 bg-gray-300/50;
|
||||
@apply hover:text-gray-800 hover:border-gray-400;
|
||||
@apply cursor-default !important;
|
||||
}
|
||||
|
||||
.cm-editor .cm-activeLineGutter {
|
||||
|
||||
@@ -32,6 +32,7 @@ export type PairEditorProps = {
|
||||
allowFileValues?: boolean;
|
||||
nameValidate?: InputProps['validate'];
|
||||
valueValidate?: InputProps['validate'];
|
||||
noScroll?: boolean;
|
||||
};
|
||||
|
||||
export type Pair = {
|
||||
@@ -57,6 +58,7 @@ export const PairEditor = memo(function PairEditor({
|
||||
nameValidate,
|
||||
valueType,
|
||||
onChange,
|
||||
noScroll,
|
||||
pairs: originalPairs,
|
||||
valueAutocomplete,
|
||||
valueAutocompleteVariables,
|
||||
@@ -95,7 +97,7 @@ export const PairEditor = memo(function PairEditor({
|
||||
[onChange],
|
||||
);
|
||||
|
||||
const handleMove = useCallback<FormRowProps['onMove']>(
|
||||
const handleMove = useCallback<PairEditorRowProps['onMove']>(
|
||||
(id, side) => {
|
||||
const dragIndex = pairs.findIndex((r) => r.id === id);
|
||||
setHoveredIndex(side === 'above' ? dragIndex : dragIndex + 1);
|
||||
@@ -103,7 +105,7 @@ export const PairEditor = memo(function PairEditor({
|
||||
[pairs],
|
||||
);
|
||||
|
||||
const handleEnd = useCallback<FormRowProps['onEnd']>(
|
||||
const handleEnd = useCallback<PairEditorRowProps['onEnd']>(
|
||||
(id: string) => {
|
||||
if (hoveredIndex === null) return;
|
||||
setHoveredIndex(null);
|
||||
@@ -162,7 +164,8 @@ export const PairEditor = memo(function PairEditor({
|
||||
className={classNames(
|
||||
className,
|
||||
'@container',
|
||||
'pb-2 grid overflow-auto max-h-full',
|
||||
'pb-2 mb-auto',
|
||||
!noScroll && 'overflow-y-auto max-h-full',
|
||||
// Move over the width of the drag handle
|
||||
'-ml-3',
|
||||
// Pad to make room for the drag divider
|
||||
@@ -174,7 +177,7 @@ export const PairEditor = memo(function PairEditor({
|
||||
return (
|
||||
<Fragment key={p.id}>
|
||||
{hoveredIndex === i && <DropMarker />}
|
||||
<FormRow
|
||||
<PairEditorRow
|
||||
pairContainer={p}
|
||||
className="py-1"
|
||||
isLast={isLast}
|
||||
@@ -207,7 +210,7 @@ enum ItemTypes {
|
||||
ROW = 'pair-row',
|
||||
}
|
||||
|
||||
type FormRowProps = {
|
||||
type PairEditorRowProps = {
|
||||
className?: string;
|
||||
pairContainer: PairContainer;
|
||||
forceFocusPairId?: string | null;
|
||||
@@ -233,7 +236,7 @@ type FormRowProps = {
|
||||
| 'allowFileValues'
|
||||
>;
|
||||
|
||||
const FormRow = memo(function FormRow({
|
||||
function PairEditorRow({
|
||||
allowFileValues,
|
||||
className,
|
||||
forceFocusPairId,
|
||||
@@ -254,7 +257,7 @@ const FormRow = memo(function FormRow({
|
||||
valuePlaceholder,
|
||||
valueValidate,
|
||||
valueType,
|
||||
}: FormRowProps) {
|
||||
}: PairEditorRowProps) {
|
||||
const { id } = pairContainer;
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const prompt = usePrompt();
|
||||
@@ -480,7 +483,7 @@ const FormRow = memo(function FormRow({
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const newPairContainer = (initialPair?: Pair): PairContainer => {
|
||||
const id = initialPair?.id ?? uuid();
|
||||
|
||||
13
src-web/hooks/useFloatingSidebarHidden.ts
Normal file
13
src-web/hooks/useFloatingSidebarHidden.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
||||
import { useKeyValue } from './useKeyValue';
|
||||
|
||||
export function useFloatingSidebarHidden() {
|
||||
const activeWorkspaceId = useActiveWorkspaceId();
|
||||
const { set, value } = useKeyValue<boolean>({
|
||||
namespace: 'no_sync',
|
||||
key: ['floating_sidebar_hidden', activeWorkspaceId ?? 'n/a'],
|
||||
fallback: false,
|
||||
});
|
||||
|
||||
return [value, set] as const;
|
||||
}
|
||||
8
src-web/hooks/useShouldFloatSidebar.ts
Normal file
8
src-web/hooks/useShouldFloatSidebar.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { useWindowSize } from 'react-use';
|
||||
|
||||
const WINDOW_FLOATING_SIDEBAR_WIDTH = 600;
|
||||
|
||||
export function useShouldFloatSidebar() {
|
||||
const windowSize = useWindowSize();
|
||||
return windowSize.width <= WINDOW_FLOATING_SIDEBAR_WIDTH;
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
||||
import { useKeyValue } from './useKeyValue';
|
||||
|
||||
@@ -10,12 +9,5 @@ export function useSidebarHidden() {
|
||||
fallback: false,
|
||||
});
|
||||
|
||||
return useMemo(() => {
|
||||
return {
|
||||
show: () => set(false),
|
||||
hide: () => set(true),
|
||||
toggle: () => set((h) => !h),
|
||||
hidden: value,
|
||||
};
|
||||
}, [set, value]);
|
||||
return [value, set] as const;
|
||||
}
|
||||
|
||||
@@ -63,8 +63,8 @@ module.exports = {
|
||||
selection: 'hsl(var(--color-violet-500) / 0.3)',
|
||||
focus: 'hsl(var(--color-blue-500) / 0.7)',
|
||||
invalid: 'hsl(var(--color-red-500))',
|
||||
highlight: 'hsl(var(--color-gray-300) / 0.35)',
|
||||
highlightSecondary: 'hsl(var(--color-gray-300) / 0.2)',
|
||||
highlight: 'hsl(var(--color-gray-500) / 0.3)',
|
||||
highlightSecondary: 'hsl(var(--color-gray-500) / 0.15)',
|
||||
transparent: 'transparent',
|
||||
white: 'hsl(0 100% 100% / <alpha-value>)',
|
||||
black: 'hsl(0 100% 0% / <alpha-value>)',
|
||||
|
||||
Reference in New Issue
Block a user