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