Compare commits

..

7 Commits

Author SHA1 Message Date
Gregory Schier
db6a7dcabb Bump version 2024-04-01 08:48:26 +02:00
Gregory Schier
00b1f90074 Separate floating sidebar hidden state 2024-03-22 10:43:10 -07:00
Gregory Schier
e292235792 Filtering for cmd palette 2024-03-22 10:42:45 -07:00
Gregory Schier
5f86802d88 Space between var placeholders and code fold cursor 2024-03-22 10:42:35 -07:00
Gregory Schier
acb7f2e49b Fix Postman variable import 2024-03-22 10:40:51 -07:00
Gregory Schier
e2a15609bf Adjust highlight color 2024-03-22 10:37:45 -07:00
Gregory Schier
aa3bfd78c4 Some scrolling tweaks 2024-03-20 17:27:47 -07:00
17 changed files with 105 additions and 74 deletions

View File

@@ -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);

View File

@@ -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) => {

View File

@@ -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")]

View File

@@ -8,7 +8,7 @@
},
"package": {
"productName": "Yaak",
"version": "2024.3.8"
"version": "2024.3.9"
},
"tauri": {
"windows": [],

View File

@@ -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

View File

@@ -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}

View File

@@ -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

View File

@@ -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>
);
});
}

View File

@@ -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 }}

View File

@@ -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}

View File

@@ -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}

View File

@@ -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 {

View File

@@ -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();

View 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;
}

View 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;
}

View File

@@ -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;
}

View File

@@ -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>)',