mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-11 03:26:58 +02:00
Better project selector, Fixes #2, and a bunch more
This commit is contained in:
@@ -58,8 +58,9 @@
|
|||||||
"uuid": "^9.0.0"
|
"uuid": "^9.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-react": "^3.1.0",
|
||||||
"@tailwindcss/nesting": "^0.0.0-insiders.565cd3e",
|
"@tailwindcss/nesting": "^0.0.0-insiders.565cd3e",
|
||||||
"@tauri-apps/cli": "^1.5.6",
|
"@tauri-apps/cli": "^1.5.4",
|
||||||
"@types/node": "^18.7.10",
|
"@types/node": "^18.7.10",
|
||||||
"@types/papaparse": "^5.3.7",
|
"@types/papaparse": "^5.3.7",
|
||||||
"@types/parse-color": "^1.0.1",
|
"@types/parse-color": "^1.0.1",
|
||||||
@@ -69,7 +70,6 @@
|
|||||||
"@types/uuid": "^9.0.1",
|
"@types/uuid": "^9.0.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.57.0",
|
"@typescript-eslint/eslint-plugin": "^5.57.0",
|
||||||
"@typescript-eslint/parser": "^5.57.0",
|
"@typescript-eslint/parser": "^5.57.0",
|
||||||
"@vitejs/plugin-react": "^3.1.0",
|
|
||||||
"autoprefixer": "^10.4.13",
|
"autoprefixer": "^10.4.13",
|
||||||
"eslint": "^8.34.0",
|
"eslint": "^8.34.0",
|
||||||
"eslint-config-prettier": "^8.6.0",
|
"eslint-config-prettier": "^8.6.0",
|
||||||
|
|||||||
@@ -498,17 +498,7 @@ async fn list_environments(
|
|||||||
.await
|
.await
|
||||||
.expect("Failed to find environments");
|
.expect("Failed to find environments");
|
||||||
|
|
||||||
println!("");
|
Ok(environments)
|
||||||
if environments.is_empty() {
|
|
||||||
println!("CREATING DEFAULT ENVIRONMENT");
|
|
||||||
let data: HashMap<String, JsonValue> = HashMap::new();
|
|
||||||
let environment = models::create_environment(workspace_id, "Default", data, pool)
|
|
||||||
.await
|
|
||||||
.expect("Failed to create default environment");
|
|
||||||
Ok(vec![environment])
|
|
||||||
} else {
|
|
||||||
Ok(environments)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { keyValueQueryKey } from '../hooks/useKeyValue';
|
|||||||
import { requestsQueryKey } from '../hooks/useRequests';
|
import { requestsQueryKey } from '../hooks/useRequests';
|
||||||
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
||||||
import { responsesQueryKey } from '../hooks/useResponses';
|
import { responsesQueryKey } from '../hooks/useResponses';
|
||||||
import { useTauriEvent } from '../hooks/useTauriEvent';
|
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
|
||||||
import { workspacesQueryKey } from '../hooks/useWorkspaces';
|
import { workspacesQueryKey } from '../hooks/useWorkspaces';
|
||||||
import { DEFAULT_FONT_SIZE } from '../lib/constants';
|
import { DEFAULT_FONT_SIZE } from '../lib/constants';
|
||||||
import { NAMESPACE_NO_SYNC } from '../lib/keyValueStore';
|
import { NAMESPACE_NO_SYNC } from '../lib/keyValueStore';
|
||||||
@@ -15,7 +15,7 @@ export function GlobalHooks() {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { wasUpdatedExternally } = useRequestUpdateKey(null);
|
const { wasUpdatedExternally } = useRequestUpdateKey(null);
|
||||||
|
|
||||||
useTauriEvent<Model>('created_model', ({ payload, windowLabel }) => {
|
useListenToTauriEvent<Model>('created_model', ({ payload, windowLabel }) => {
|
||||||
if (shouldIgnoreEvent(payload, windowLabel)) return;
|
if (shouldIgnoreEvent(payload, windowLabel)) return;
|
||||||
|
|
||||||
const queryKey =
|
const queryKey =
|
||||||
@@ -40,7 +40,7 @@ export function GlobalHooks() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
useTauriEvent<Model>('updated_model', ({ payload, windowLabel }) => {
|
useListenToTauriEvent<Model>('updated_model', ({ payload, windowLabel }) => {
|
||||||
if (shouldIgnoreEvent(payload, windowLabel)) return;
|
if (shouldIgnoreEvent(payload, windowLabel)) return;
|
||||||
|
|
||||||
const queryKey =
|
const queryKey =
|
||||||
@@ -70,7 +70,7 @@ export function GlobalHooks() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
useTauriEvent<Model>('deleted_model', ({ payload, windowLabel }) => {
|
useListenToTauriEvent<Model>('deleted_model', ({ payload, windowLabel }) => {
|
||||||
if (shouldIgnoreEvent(payload, windowLabel)) return;
|
if (shouldIgnoreEvent(payload, windowLabel)) return;
|
||||||
|
|
||||||
if (shouldIgnoreModel(payload)) return;
|
if (shouldIgnoreModel(payload)) return;
|
||||||
@@ -85,7 +85,7 @@ export function GlobalHooks() {
|
|||||||
queryClient.setQueryData(keyValueQueryKey(payload), undefined);
|
queryClient.setQueryData(keyValueQueryKey(payload), undefined);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
useTauriEvent<number>('zoom', ({ payload: zoomDelta, windowLabel }) => {
|
useListenToTauriEvent<number>('zoom', ({ payload: zoomDelta, windowLabel }) => {
|
||||||
if (windowLabel !== appWindow.label) return;
|
if (windowLabel !== appWindow.label) return;
|
||||||
const fontSize = parseFloat(window.getComputedStyle(document.documentElement).fontSize);
|
const fontSize = parseFloat(window.getComputedStyle(document.documentElement).fontSize);
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ import type { HTMLAttributes, ReactElement } from 'react';
|
|||||||
import React, { useRef } from 'react';
|
import React, { useRef } from 'react';
|
||||||
import { useDeleteRequest } from '../hooks/useDeleteRequest';
|
import { useDeleteRequest } from '../hooks/useDeleteRequest';
|
||||||
import { useDuplicateRequest } from '../hooks/useDuplicateRequest';
|
import { useDuplicateRequest } from '../hooks/useDuplicateRequest';
|
||||||
import { useTauriEvent } from '../hooks/useTauriEvent';
|
|
||||||
import { useTheme } from '../hooks/useTheme';
|
import { useTheme } from '../hooks/useTheme';
|
||||||
import type { DropdownRef } from './core/Dropdown';
|
import type { DropdownRef } from './core/Dropdown';
|
||||||
import { Dropdown } from './core/Dropdown';
|
import { Dropdown } from './core/Dropdown';
|
||||||
import { HotKey } from './core/HotKey';
|
import { HotKey } from './core/HotKey';
|
||||||
import { Icon } from './core/Icon';
|
import { Icon } from './core/Icon';
|
||||||
|
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
requestId: string;
|
requestId: string;
|
||||||
@@ -20,12 +20,12 @@ export function RequestActionsDropdown({ requestId, children }: Props) {
|
|||||||
const dropdownRef = useRef<DropdownRef>(null);
|
const dropdownRef = useRef<DropdownRef>(null);
|
||||||
const { appearance, toggleAppearance } = useTheme();
|
const { appearance, toggleAppearance } = useTheme();
|
||||||
|
|
||||||
useTauriEvent('toggle_settings', () => {
|
useListenToTauriEvent('toggle_settings', () => {
|
||||||
dropdownRef.current?.toggle();
|
dropdownRef.current?.toggle();
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Put this somewhere better
|
// TODO: Put this somewhere better
|
||||||
useTauriEvent('duplicate_request', () => {
|
useListenToTauriEvent('duplicate_request', () => {
|
||||||
duplicateRequest.mutate();
|
duplicateRequest.mutate();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { memo, useCallback, useMemo, useState } from 'react';
|
|||||||
import { createGlobalState } from 'react-use';
|
import { createGlobalState } from 'react-use';
|
||||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||||
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
||||||
import { useTauriEvent } from '../hooks/useTauriEvent';
|
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
|
||||||
import { useUpdateRequest } from '../hooks/useUpdateRequest';
|
import { useUpdateRequest } from '../hooks/useUpdateRequest';
|
||||||
import { tryFormatJson } from '../lib/formatters';
|
import { tryFormatJson } from '../lib/formatters';
|
||||||
import type { HttpHeader, HttpRequest } from '../lib/models';
|
import type { HttpHeader, HttpRequest } from '../lib/models';
|
||||||
@@ -140,7 +140,7 @@ export const RequestPane = memo(function RequestPane({ style, fullHeight, classN
|
|||||||
[updateRequest],
|
[updateRequest],
|
||||||
);
|
);
|
||||||
|
|
||||||
useTauriEvent(
|
useListenToTauriEvent(
|
||||||
'send_request',
|
'send_request',
|
||||||
async ({ windowLabel }) => {
|
async ({ windowLabel }) => {
|
||||||
if (windowLabel !== appWindow.label) return;
|
if (windowLabel !== appWindow.label) return;
|
||||||
|
|||||||
@@ -10,16 +10,18 @@ import { useDeleteAnyRequest } from '../hooks/useDeleteAnyRequest';
|
|||||||
import { useLatestResponse } from '../hooks/useLatestResponse';
|
import { useLatestResponse } from '../hooks/useLatestResponse';
|
||||||
import { useRequests } from '../hooks/useRequests';
|
import { useRequests } from '../hooks/useRequests';
|
||||||
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
||||||
import { useTauriEvent } from '../hooks/useTauriEvent';
|
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
|
||||||
import { useUpdateAnyRequest } from '../hooks/useUpdateAnyRequest';
|
import { useUpdateAnyRequest } from '../hooks/useUpdateAnyRequest';
|
||||||
import { useUpdateRequest } from '../hooks/useUpdateRequest';
|
import { useUpdateRequest } from '../hooks/useUpdateRequest';
|
||||||
import type { HttpRequest } from '../lib/models';
|
import type { HttpRequest } from '../lib/models';
|
||||||
import { isResponseLoading } from '../lib/models';
|
import { isResponseLoading } from '../lib/models';
|
||||||
import { Icon } from './core/Icon';
|
import { Icon } from './core/Icon';
|
||||||
import { VStack } from './core/Stacks';
|
import { HStack, VStack } from './core/Stacks';
|
||||||
import { StatusTag } from './core/StatusTag';
|
import { StatusTag } from './core/StatusTag';
|
||||||
import { DropMarker } from './DropMarker';
|
import { DropMarker } from './DropMarker';
|
||||||
import { useActiveEnvironmentId } from '../hooks/useActiveEnvironmentId';
|
import { useActiveEnvironmentId } from '../hooks/useActiveEnvironmentId';
|
||||||
|
import { WorkspaceActionsDropdown } from './WorkspaceActionsDropdown';
|
||||||
|
import { IconButton } from './core/IconButton';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
@@ -63,7 +65,7 @@ export const Sidebar = memo(function Sidebar({ className }: Props) {
|
|||||||
routes.navigate('request', {
|
routes.navigate('request', {
|
||||||
requestId,
|
requestId,
|
||||||
workspaceId: request.workspaceId,
|
workspaceId: request.workspaceId,
|
||||||
environmentId: activeEnvironmentId,
|
environmentId: activeEnvironmentId ?? undefined,
|
||||||
});
|
});
|
||||||
setSelectedIndex(index);
|
setSelectedIndex(index);
|
||||||
focusActiveRequest(index);
|
focusActiveRequest(index);
|
||||||
@@ -93,7 +95,7 @@ export const Sidebar = memo(function Sidebar({ className }: Props) {
|
|||||||
useKeyPressEvent('Backspace', handleDeleteKey);
|
useKeyPressEvent('Backspace', handleDeleteKey);
|
||||||
useKeyPressEvent('Delete', handleDeleteKey);
|
useKeyPressEvent('Delete', handleDeleteKey);
|
||||||
|
|
||||||
useTauriEvent(
|
useListenToTauriEvent(
|
||||||
'focus_sidebar',
|
'focus_sidebar',
|
||||||
() => {
|
() => {
|
||||||
if (hidden || hasFocus) return;
|
if (hidden || hasFocus) return;
|
||||||
@@ -149,11 +151,22 @@ export const Sidebar = memo(function Sidebar({ className }: Props) {
|
|||||||
onFocus={handleFocus}
|
onFocus={handleFocus}
|
||||||
onBlur={handleBlur}
|
onBlur={handleBlur}
|
||||||
tabIndex={hidden ? -1 : 0}
|
tabIndex={hidden ? -1 : 0}
|
||||||
className={classNames(className, 'h-full relative grid grid-rows-[minmax(0,1fr)_auto]')}
|
className={classNames(
|
||||||
|
className,
|
||||||
|
'h-full relative grid grid-rows-[auto_minmax(0,1fr)_auto]',
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
|
<HStack className="mt-1 mb-2 pt-1 mx-2" justifyContent="between" alignItems="center" space={1}>
|
||||||
|
<WorkspaceActionsDropdown
|
||||||
|
forDropdown={false}
|
||||||
|
className="text-left mb-0"
|
||||||
|
justify="start"
|
||||||
|
/>
|
||||||
|
<IconButton size="sm" icon="plusCircle" title="Create Request" />
|
||||||
|
</HStack>
|
||||||
<VStack
|
<VStack
|
||||||
as="ul"
|
as="ul"
|
||||||
className="relative py-3 overflow-y-auto overflow-x-visible"
|
className="relative pb-3 overflow-y-auto overflow-x-visible"
|
||||||
draggable={false}
|
draggable={false}
|
||||||
>
|
>
|
||||||
<SidebarItems
|
<SidebarItems
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useCreateRequest } from '../hooks/useCreateRequest';
|
import { useCreateRequest } from '../hooks/useCreateRequest';
|
||||||
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
||||||
import { useTauriEvent } from '../hooks/useTauriEvent';
|
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
|
||||||
import { IconButton } from './core/IconButton';
|
import { IconButton } from './core/IconButton';
|
||||||
|
|
||||||
export const SidebarActions = memo(function SidebarActions() {
|
export const SidebarActions = memo(function SidebarActions() {
|
||||||
@@ -12,7 +12,7 @@ export const SidebarActions = memo(function SidebarActions() {
|
|||||||
createRequest.mutate({});
|
createRequest.mutate({});
|
||||||
}, [createRequest]);
|
}, [createRequest]);
|
||||||
|
|
||||||
useTauriEvent('new_request', () => {
|
useListenToTauriEvent('new_request', () => {
|
||||||
createRequest.mutate({});
|
createRequest.mutate({});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { memo, useCallback, useRef } from 'react';
|
|||||||
import { useIsResponseLoading } from '../hooks/useIsResponseLoading';
|
import { useIsResponseLoading } from '../hooks/useIsResponseLoading';
|
||||||
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
||||||
import { useSendRequest } from '../hooks/useSendRequest';
|
import { useSendRequest } from '../hooks/useSendRequest';
|
||||||
import { useTauriEvent } from '../hooks/useTauriEvent';
|
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
|
||||||
import { useUpdateRequest } from '../hooks/useUpdateRequest';
|
import { useUpdateRequest } from '../hooks/useUpdateRequest';
|
||||||
import type { HttpRequest } from '../lib/models';
|
import type { HttpRequest } from '../lib/models';
|
||||||
import { IconButton } from './core/IconButton';
|
import { IconButton } from './core/IconButton';
|
||||||
@@ -39,7 +39,7 @@ export const UrlBar = memo(function UrlBar({ id: requestId, url, method, classNa
|
|||||||
[sendRequest],
|
[sendRequest],
|
||||||
);
|
);
|
||||||
|
|
||||||
useTauriEvent('focus_url', () => {
|
useListenToTauriEvent('focus_url', () => {
|
||||||
inputRef.current?.focus();
|
inputRef.current?.focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ import type {
|
|||||||
MouseEvent as ReactMouseEvent,
|
MouseEvent as ReactMouseEvent,
|
||||||
ReactNode,
|
ReactNode,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useWindowSize } from 'react-use';
|
import { useWindowSize } from 'react-use';
|
||||||
import { useOsInfo } from '../hooks/useOsInfo';
|
import { useOsInfo } from '../hooks/useOsInfo';
|
||||||
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
||||||
import { useSidebarWidth } from '../hooks/useSidebarWidth';
|
import { useSidebarWidth } from '../hooks/useSidebarWidth';
|
||||||
import { useTauriEvent } from '../hooks/useTauriEvent';
|
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
|
||||||
import { WINDOW_FLOATING_SIDEBAR_WIDTH } from '../lib/constants';
|
import { WINDOW_FLOATING_SIDEBAR_WIDTH } from '../lib/constants';
|
||||||
import { Button } from './core/Button';
|
import { Button } from './core/Button';
|
||||||
import { HStack } from './core/Stacks';
|
import { HStack } from './core/Stacks';
|
||||||
@@ -38,7 +38,7 @@ export default function Workspace() {
|
|||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
|
||||||
useTauriEvent('toggle_sidebar', toggle);
|
useListenToTauriEvent('toggle_sidebar', toggle);
|
||||||
|
|
||||||
// float/un-float sidebar on window resize
|
// float/un-float sidebar on window resize
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { usePrompt } from '../hooks/usePrompt';
|
|||||||
import { useUpdateWorkspace } from '../hooks/useUpdateWorkspace';
|
import { useUpdateWorkspace } from '../hooks/useUpdateWorkspace';
|
||||||
import { useWorkspaces } from '../hooks/useWorkspaces';
|
import { useWorkspaces } from '../hooks/useWorkspaces';
|
||||||
import { Button } from './core/Button';
|
import { Button } from './core/Button';
|
||||||
|
import type { ButtonProps } from './core/Button';
|
||||||
import type { DropdownItem } from './core/Dropdown';
|
import type { DropdownItem } from './core/Dropdown';
|
||||||
import { Dropdown } from './core/Dropdown';
|
import { Dropdown } from './core/Dropdown';
|
||||||
import { Icon } from './core/Icon';
|
import { Icon } from './core/Icon';
|
||||||
@@ -17,12 +18,11 @@ import { HStack } from './core/Stacks';
|
|||||||
import { useDialog } from './DialogContext';
|
import { useDialog } from './DialogContext';
|
||||||
import { useActiveEnvironmentId } from '../hooks/useActiveEnvironmentId';
|
import { useActiveEnvironmentId } from '../hooks/useActiveEnvironmentId';
|
||||||
|
|
||||||
type Props = {
|
type Props = Pick<ButtonProps, 'className' | 'justify' | 'forDropdown'>;
|
||||||
className?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||||
className,
|
className,
|
||||||
|
...buttonProps
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const workspaces = useWorkspaces();
|
const workspaces = useWorkspaces();
|
||||||
const activeWorkspace = useActiveWorkspace();
|
const activeWorkspace = useActiveWorkspace();
|
||||||
@@ -36,9 +36,10 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
|||||||
const routes = useAppRoutes();
|
const routes = useAppRoutes();
|
||||||
|
|
||||||
const items: DropdownItem[] = useMemo(() => {
|
const items: DropdownItem[] = useMemo(() => {
|
||||||
const workspaceItems = workspaces.map((w) => ({
|
const workspaceItems: DropdownItem[] = workspaces.map((w) => ({
|
||||||
key: w.id,
|
key: w.id,
|
||||||
label: w.name,
|
label: w.name,
|
||||||
|
rightSlot: w.id === activeWorkspaceId ? <Icon icon="check" /> : undefined,
|
||||||
onSelect: async () => {
|
onSelect: async () => {
|
||||||
dialog.show({
|
dialog.show({
|
||||||
id: 'open-workspace',
|
id: 'open-workspace',
|
||||||
@@ -147,6 +148,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
|||||||
];
|
];
|
||||||
}, [
|
}, [
|
||||||
activeWorkspace?.name,
|
activeWorkspace?.name,
|
||||||
|
activeWorkspaceId,
|
||||||
createWorkspace,
|
createWorkspace,
|
||||||
deleteWorkspace.mutate,
|
deleteWorkspace.mutate,
|
||||||
dialog,
|
dialog,
|
||||||
@@ -163,6 +165,8 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
|||||||
size="sm"
|
size="sm"
|
||||||
className={classNames(className, 'text-gray-800 !px-2 truncate')}
|
className={classNames(className, 'text-gray-800 !px-2 truncate')}
|
||||||
forDropdown
|
forDropdown
|
||||||
|
leftSlot={<img src="https://yaak.app/logo.svg" alt="Workspace logo" className="w-4 h-4 mr-1" />}
|
||||||
|
{...buttonProps}
|
||||||
>
|
>
|
||||||
{activeWorkspace?.name}
|
{activeWorkspace?.name}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { HStack } from './core/Stacks';
|
|||||||
import { RecentRequestsDropdown } from './RecentRequestsDropdown';
|
import { RecentRequestsDropdown } from './RecentRequestsDropdown';
|
||||||
import { RequestActionsDropdown } from './RequestActionsDropdown';
|
import { RequestActionsDropdown } from './RequestActionsDropdown';
|
||||||
import { SidebarActions } from './SidebarActions';
|
import { SidebarActions } from './SidebarActions';
|
||||||
import { WorkspaceActionsDropdown } from './WorkspaceActionsDropdown';
|
|
||||||
import { EnvironmentActionsDropdown } from './EnvironmentActionsDropdown';
|
import { EnvironmentActionsDropdown } from './EnvironmentActionsDropdown';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -24,7 +23,6 @@ export const WorkspaceHeader = memo(function WorkspaceHeader({ className }: Prop
|
|||||||
>
|
>
|
||||||
<HStack space={0.5} className="flex-1 pointer-events-none" alignItems="center">
|
<HStack space={0.5} className="flex-1 pointer-events-none" alignItems="center">
|
||||||
<SidebarActions />
|
<SidebarActions />
|
||||||
<WorkspaceActionsDropdown className="pointer-events-auto" />
|
|
||||||
<EnvironmentActionsDropdown className="pointer-events-auto" />
|
<EnvironmentActionsDropdown className="pointer-events-auto" />
|
||||||
</HStack>
|
</HStack>
|
||||||
<div className="pointer-events-none">
|
<div className="pointer-events-none">
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export type ButtonProps = HTMLAttributes<HTMLButtonElement> & {
|
|||||||
forDropdown?: boolean;
|
forDropdown?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
title?: string;
|
title?: string;
|
||||||
|
leftSlot?: ReactNode;
|
||||||
rightSlot?: ReactNode;
|
rightSlot?: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -37,6 +38,7 @@ const _Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
|||||||
type = 'button',
|
type = 'button',
|
||||||
justify = 'center',
|
justify = 'center',
|
||||||
size = 'md',
|
size = 'md',
|
||||||
|
leftSlot,
|
||||||
rightSlot,
|
rightSlot,
|
||||||
disabled,
|
disabled,
|
||||||
...props
|
...props
|
||||||
@@ -63,7 +65,11 @@ const _Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<button ref={ref} type={type} className={classes} disabled={disabled} {...props}>
|
<button ref={ref} type={type} className={classes} disabled={disabled} {...props}>
|
||||||
{isLoading && <Icon icon="update" size={size} className="animate-spin mr-1" />}
|
{isLoading ? (
|
||||||
|
<Icon icon="update" size={size} className="animate-spin mr-1" />
|
||||||
|
) : leftSlot ? (
|
||||||
|
<div className="mr-1">{leftSlot}</div>
|
||||||
|
) : null}
|
||||||
{children}
|
{children}
|
||||||
{rightSlot && <div className="ml-1">{rightSlot}</div>}
|
{rightSlot && <div className="ml-1">{rightSlot}</div>}
|
||||||
{forDropdown && <Icon icon="chevronDown" size={size} className="ml-1 -mr-1" />}
|
{forDropdown && <Icon icon="chevronDown" size={size} className="ml-1 -mr-1" />}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export interface EditorProps {
|
|||||||
onChange?: (value: string) => void;
|
onChange?: (value: string) => void;
|
||||||
onFocus?: () => void;
|
onFocus?: () => void;
|
||||||
onBlur?: () => void;
|
onBlur?: () => void;
|
||||||
|
onSubmit?: () => void;
|
||||||
singleLine?: boolean;
|
singleLine?: boolean;
|
||||||
wrapLines?: boolean;
|
wrapLines?: boolean;
|
||||||
format?: (v: string) => string;
|
format?: (v: string) => string;
|
||||||
@@ -56,6 +57,7 @@ const _Editor = forwardRef<EditorView | undefined, EditorProps>(function Editor(
|
|||||||
onChange,
|
onChange,
|
||||||
onFocus,
|
onFocus,
|
||||||
onBlur,
|
onBlur,
|
||||||
|
onSubmit,
|
||||||
className,
|
className,
|
||||||
singleLine,
|
singleLine,
|
||||||
format,
|
format,
|
||||||
@@ -77,6 +79,12 @@ const _Editor = forwardRef<EditorView | undefined, EditorProps>(function Editor(
|
|||||||
handleChange.current = onChange;
|
handleChange.current = onChange;
|
||||||
}, [onChange]);
|
}, [onChange]);
|
||||||
|
|
||||||
|
// Use ref so we can update the onChange handler without re-initializing the editor
|
||||||
|
const handleSubmit = useRef<EditorProps['onSubmit']>(onSubmit);
|
||||||
|
useEffect(() => {
|
||||||
|
handleSubmit.current = onSubmit;
|
||||||
|
}, [onSubmit]);
|
||||||
|
|
||||||
// Use ref so we can update the onChange handler without re-initializing the editor
|
// Use ref so we can update the onChange handler without re-initializing the editor
|
||||||
const handleFocus = useRef<EditorProps['onFocus']>(onFocus);
|
const handleFocus = useRef<EditorProps['onFocus']>(onFocus);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -113,6 +121,7 @@ const _Editor = forwardRef<EditorView | undefined, EditorProps>(function Editor(
|
|||||||
if (cm.current === null) return;
|
if (cm.current === null) return;
|
||||||
const { view, languageCompartment } = cm.current;
|
const { view, languageCompartment } = cm.current;
|
||||||
const ext = getLanguageExtension({ contentType, environment, useTemplating, autocomplete });
|
const ext = getLanguageExtension({ contentType, environment, useTemplating, autocomplete });
|
||||||
|
console.log("EXT", ext);
|
||||||
view.dispatch({ effects: languageCompartment.reconfigure(ext) });
|
view.dispatch({ effects: languageCompartment.reconfigure(ext) });
|
||||||
}, [contentType, autocomplete, useTemplating, environment]);
|
}, [contentType, autocomplete, useTemplating, environment]);
|
||||||
|
|
||||||
@@ -147,6 +156,7 @@ const _Editor = forwardRef<EditorView | undefined, EditorProps>(function Editor(
|
|||||||
container,
|
container,
|
||||||
readOnly,
|
readOnly,
|
||||||
singleLine,
|
singleLine,
|
||||||
|
onSubmit: handleSubmit,
|
||||||
onChange: handleChange,
|
onChange: handleChange,
|
||||||
onFocus: handleFocus,
|
onFocus: handleFocus,
|
||||||
onBlur: handleBlur,
|
onBlur: handleBlur,
|
||||||
@@ -219,11 +229,13 @@ function getExtensions({
|
|||||||
onChange,
|
onChange,
|
||||||
onFocus,
|
onFocus,
|
||||||
onBlur,
|
onBlur,
|
||||||
|
onSubmit,
|
||||||
}: Pick<EditorProps, 'singleLine' | 'readOnly'> & {
|
}: Pick<EditorProps, 'singleLine' | 'readOnly'> & {
|
||||||
container: HTMLDivElement | null;
|
container: HTMLDivElement | null;
|
||||||
onChange: MutableRefObject<EditorProps['onChange']>;
|
onChange: MutableRefObject<EditorProps['onChange']>;
|
||||||
onFocus: MutableRefObject<EditorProps['onFocus']>;
|
onFocus: MutableRefObject<EditorProps['onFocus']>;
|
||||||
onBlur: MutableRefObject<EditorProps['onBlur']>;
|
onBlur: MutableRefObject<EditorProps['onBlur']>;
|
||||||
|
onSubmit: MutableRefObject<EditorProps['onSubmit']>;
|
||||||
}) {
|
}) {
|
||||||
// TODO: Ensure tooltips render inside the dialog if we are in one.
|
// TODO: Ensure tooltips render inside the dialog if we are in one.
|
||||||
const parent =
|
const parent =
|
||||||
@@ -249,10 +261,8 @@ function getExtensions({
|
|||||||
},
|
},
|
||||||
keydown: (e) => {
|
keydown: (e) => {
|
||||||
// Submit nearest form on enter if there is one
|
// Submit nearest form on enter if there is one
|
||||||
if (e.key === 'Enter') {
|
if (onSubmit != null && e.key === 'Enter') {
|
||||||
const el = e.currentTarget as HTMLElement;
|
onSubmit.current?.();
|
||||||
const form = el.closest('form');
|
|
||||||
form?.dispatchEvent(new Event('submit', { cancelable: true, bubbles: true }));
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -99,10 +99,10 @@ export function getLanguageExtension({
|
|||||||
environment,
|
environment,
|
||||||
autocomplete,
|
autocomplete,
|
||||||
}: { environment: Environment | null } & Pick<EditorProps, 'contentType' | 'useTemplating' | 'autocomplete'>) {
|
}: { environment: Environment | null } & Pick<EditorProps, 'contentType' | 'useTemplating' | 'autocomplete'>) {
|
||||||
if (contentType === 'application/graphql') {
|
const justContentType = contentType?.split(';')[0] ?? contentType ?? '';
|
||||||
|
if (justContentType === 'application/graphql') {
|
||||||
return graphql();
|
return graphql();
|
||||||
}
|
}
|
||||||
const justContentType = contentType?.split(';')[0] ?? contentType ?? '';
|
|
||||||
const base = syntaxExtensions[justContentType] ?? text();
|
const base = syntaxExtensions[justContentType] ?? text();
|
||||||
if (!useTemplating) {
|
if (!useTemplating) {
|
||||||
return base ? base : [];
|
return base ? base : [];
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { EditorView } from 'codemirror';
|
import type { EditorView } from 'codemirror';
|
||||||
import type { HTMLAttributes, ReactNode } from 'react';
|
import type { HTMLAttributes, ReactNode } from 'react';
|
||||||
import { forwardRef, useCallback, useMemo, useState } from 'react';
|
import { forwardRef, useCallback, useMemo, useRef, useState } from 'react';
|
||||||
import type { EditorProps } from './Editor';
|
import type { EditorProps } from './Editor';
|
||||||
import { Editor } from './Editor';
|
import { Editor } from './Editor';
|
||||||
import { IconButton } from './IconButton';
|
import { IconButton } from './IconButton';
|
||||||
@@ -90,8 +90,17 @@ export const Input = forwardRef<EditorView | undefined, InputProps>(function Inp
|
|||||||
[onChange],
|
[onChange],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const handleSubmit = useCallback(() => {
|
||||||
|
const form = wrapperRef.current?.closest('form');
|
||||||
|
if (!isValid || form == null) return;
|
||||||
|
|
||||||
|
form?.dispatchEvent(new Event('submit', { cancelable: true, bubbles: true }));
|
||||||
|
}, [isValid]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VStack className="w-full">
|
<VStack ref={wrapperRef} className="w-full">
|
||||||
<label
|
<label
|
||||||
htmlFor={id}
|
htmlFor={id}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
@@ -119,6 +128,7 @@ export const Input = forwardRef<EditorView | undefined, InputProps>(function Inp
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
id={id}
|
id={id}
|
||||||
singleLine
|
singleLine
|
||||||
|
onSubmit={handleSubmit}
|
||||||
type={type === 'password' && !obscured ? 'text' : type}
|
type={type === 'password' && !obscured ? 'text' : type}
|
||||||
defaultValue={defaultValue}
|
defaultValue={defaultValue}
|
||||||
forceUpdateKey={forceUpdateKey}
|
forceUpdateKey={forceUpdateKey}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ type BaseStackProps = HTMLAttributes<HTMLElement> & {
|
|||||||
as?: ComponentType | 'ul' | 'form';
|
as?: ComponentType | 'ul' | 'form';
|
||||||
space?: keyof typeof gapClasses;
|
space?: keyof typeof gapClasses;
|
||||||
alignItems?: 'start' | 'center';
|
alignItems?: 'start' | 'center';
|
||||||
justifyContent?: 'start' | 'center' | 'end';
|
justifyContent?: 'start' | 'center' | 'end' | 'between';
|
||||||
};
|
};
|
||||||
|
|
||||||
const BaseStack = forwardRef(function BaseStack(
|
const BaseStack = forwardRef(function BaseStack(
|
||||||
@@ -77,6 +77,7 @@ const BaseStack = forwardRef(function BaseStack(
|
|||||||
justifyContent === 'start' && 'justify-start',
|
justifyContent === 'start' && 'justify-start',
|
||||||
justifyContent === 'center' && 'justify-center',
|
justifyContent === 'center' && 'justify-center',
|
||||||
justifyContent === 'end' && 'justify-end',
|
justifyContent === 'end' && 'justify-end',
|
||||||
|
justifyContent === 'between' && 'justify-between',
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export function Prompt({ onHide, label, name, defaultValue, onResult }: PromptPr
|
|||||||
<VStack space={6}>
|
<VStack space={6}>
|
||||||
<Input
|
<Input
|
||||||
hideLabel
|
hideLabel
|
||||||
|
require
|
||||||
label={label}
|
label={label}
|
||||||
name={name}
|
name={name}
|
||||||
defaultValue={defaultValue}
|
defaultValue={defaultValue}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useMemo } from 'react';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
||||||
import { useActiveRequestId } from './useActiveRequestId';
|
import { useActiveRequestId } from './useActiveRequestId';
|
||||||
import type { Environment } from '../lib/models';
|
import type { Environment } from '../lib/models';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
export type RouteParamsWorkspace = {
|
export type RouteParamsWorkspace = {
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
@@ -39,38 +39,42 @@ export const routePaths = {
|
|||||||
export function useAppRoutes() {
|
export function useAppRoutes() {
|
||||||
const workspaceId = useActiveWorkspaceId();
|
const workspaceId = useActiveWorkspaceId();
|
||||||
const requestId = useActiveRequestId();
|
const requestId = useActiveRequestId();
|
||||||
|
const nav = useNavigate();
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useCallback(<T extends keyof typeof routePaths>(
|
||||||
return useMemo(
|
path: T,
|
||||||
() => ({
|
...params: Parameters<(typeof routePaths)[T]>
|
||||||
setEnvironment({ id: environmentId }: Environment) {
|
) => {
|
||||||
if (workspaceId == null) {
|
// Not sure how to make TS work here, but it's good from the
|
||||||
this.navigate('workspaces');
|
// outside caller perspective.
|
||||||
} else if (requestId == null) {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
this.navigate('workspace', {
|
const resolvedPath = routePaths[path](...(params as any));
|
||||||
workspaceId: workspaceId,
|
nav(resolvedPath);
|
||||||
environmentId: environmentId ?? null,
|
}, [nav]);
|
||||||
});
|
|
||||||
} else {
|
const setEnvironment = useCallback(
|
||||||
this.navigate('request', {
|
({ id: environmentId }: Environment) => {
|
||||||
workspaceId,
|
if (workspaceId == null) {
|
||||||
environmentId: environmentId ?? null,
|
navigate('workspaces');
|
||||||
requestId,
|
} else if (requestId == null) {
|
||||||
});
|
navigate('workspace', {
|
||||||
}
|
workspaceId: workspaceId,
|
||||||
},
|
environmentId: environmentId ?? null,
|
||||||
navigate<T extends keyof typeof routePaths>(
|
});
|
||||||
path: T,
|
} else {
|
||||||
...params: Parameters<(typeof routePaths)[T]>
|
navigate('request', {
|
||||||
) {
|
workspaceId,
|
||||||
// Not sure how to make TS work here, but it's good from the
|
environmentId: environmentId ?? null,
|
||||||
// outside caller perspective.
|
requestId,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
});
|
||||||
const resolvedPath = routePaths[path](...(params as any));
|
}
|
||||||
navigate(resolvedPath);
|
},
|
||||||
},
|
[navigate, workspaceId, requestId],
|
||||||
paths: routePaths,
|
|
||||||
}),
|
|
||||||
[navigate, requestId, workspaceId],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
paths: routePaths,
|
||||||
|
navigate,
|
||||||
|
setEnvironment,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ import { listen as tauriListen } from '@tauri-apps/api/event';
|
|||||||
import type { DependencyList } from 'react';
|
import type { DependencyList } from 'react';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
export function useTauriEvent<T>(event: string, fn: EventCallback<T>, deps: DependencyList = []) {
|
/**
|
||||||
|
* React hook to listen to a Tauri event.
|
||||||
|
*/
|
||||||
|
export function useListenToTauriEvent<T>(event: string, fn: EventCallback<T>, deps: DependencyList = []) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let unMounted = false;
|
let unMounted = false;
|
||||||
let unsubFn: (() => void) | undefined = undefined;
|
let unsubFn: (() => void) | undefined = undefined;
|
||||||
Reference in New Issue
Block a user