Fixed the circular imports and things

This commit is contained in:
Gregory Schier
2024-12-20 23:49:15 -08:00
parent 51a11b6495
commit ec999015ab
83 changed files with 511 additions and 551 deletions

View File

@@ -1,3 +1,4 @@
import { useNavigate } from '@tanstack/react-router';
import classNames from 'classnames';
import { fuzzyFilter } from 'fuzzbunny';
import type { KeyboardEvent, ReactNode } from 'react';
@@ -11,6 +12,7 @@ import { useCreateHttpRequest } from '../hooks/useCreateHttpRequest';
import { useCreateWorkspace } from '../hooks/useCreateWorkspace';
import { useDebouncedState } from '../hooks/useDebouncedState';
import { useDeleteRequest } from '../hooks/useDeleteRequest';
import { useDialog } from '../hooks/useDialog';
import { useEnvironments } from '../hooks/useEnvironments';
import type { HotkeyAction } from '../hooks/useHotKey';
import { useHotKey } from '../hooks/useHotKey';
@@ -27,8 +29,6 @@ import { useSendAnyHttpRequest } from '../hooks/useSendAnyHttpRequest';
import { useSidebarHidden } from '../hooks/useSidebarHidden';
import { useWorkspaces } from '../hooks/useWorkspaces';
import { fallbackRequestName } from '../lib/fallbackRequestName';
import { router } from '../main';
import { Route } from '../routes/workspaces/$workspaceId/requests/$requestId';
import { CookieDialog } from './CookieDialog';
import { Button } from './core/Button';
import { Heading } from './core/Heading';
@@ -37,7 +37,6 @@ import { HttpMethodTag } from './core/HttpMethodTag';
import { Icon } from './core/Icon';
import { PlainInput } from './core/PlainInput';
import { HStack } from './core/Stacks';
import { useDialog } from './DialogContext';
import { EnvironmentEditDialog } from './EnvironmentEditDialog';
interface CommandPaletteGroup {
@@ -78,6 +77,7 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
const deleteRequest = useDeleteRequest(activeRequest?.id ?? null);
const [, setSidebarHidden] = useSidebarHidden();
const openSettings = useOpenSettings();
const navigate = useNavigate();
const workspaceCommands = useMemo<CommandPaletteItem[]>(() => {
const commands: CommandPaletteItem[] = [
@@ -267,9 +267,9 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
<div className="truncate">{fallbackRequestName(r)}</div>
</HStack>
),
onSelect: () => {
router.navigate({
to: Route.fullPath,
onSelect: async () => {
await navigate({
to: '/workspaces/$workspaceId/requests/$requestId',
params: {
workspaceId: r.workspaceId,
requestId: r.id,
@@ -315,8 +315,9 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
}, [
workspaceCommands,
sortedRequests,
activeEnvironment?.id,
navigate,
sortedEnvironments,
activeEnvironment?.id,
setActiveEnvironmentId,
sortedWorkspaces,
openWorkspace,

View File

@@ -9,7 +9,7 @@ import { Dropdown, type DropdownItem } from './core/Dropdown';
import { Icon } from './core/Icon';
import { IconButton } from './core/IconButton';
import { InlineCode } from './core/InlineCode';
import { useDialog } from './DialogContext';
import { useDialog } from '../hooks/useDialog';
export function CookieDropdown() {
const cookieJars = useCookieJars() ?? [];

View File

@@ -1,80 +1,5 @@
import React, { createContext, useContext, useMemo, useState } from 'react';
import { trackEvent } from '../lib/analytics';
import type { DialogProps } from './core/Dialog';
import { Dialog } from './core/Dialog';
type DialogEntry = {
id: string;
render: ({ hide }: { hide: () => void }) => React.ReactNode;
} & Omit<DialogProps, 'open' | 'children'>;
interface State {
dialogs: DialogEntry[];
actions: Actions;
}
interface Actions {
show: (d: DialogEntry) => void;
toggle: (d: DialogEntry) => void;
hide: (id: string) => void;
}
import { createContext } from 'react';
import type { DialogState } from './Dialogs';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const DialogContext = createContext<State>({} as State);
export const DialogProvider = ({ children }: { children: React.ReactNode }) => {
const [dialogs, setDialogs] = useState<State['dialogs']>([]);
const actions = useMemo<Actions>(
() => ({
show({ id, ...props }: DialogEntry) {
trackEvent('dialog', 'show', { id });
setDialogs((a) => [...a.filter((d) => d.id !== id), { id, ...props }]);
},
toggle({ id, ...props }: DialogEntry) {
if (dialogs.some((d) => d.id === id)) this.hide(id);
else this.show({ id, ...props });
},
hide: (id: string) => {
setDialogs((a) => a.filter((d) => d.id !== id));
},
}),
[dialogs],
);
const state: State = {
dialogs,
actions,
};
return <DialogContext.Provider value={state}>{children}</DialogContext.Provider>;
};
function DialogInstance({ id, render, onClose, ...props }: DialogEntry) {
const { actions } = useContext(DialogContext);
const children = render({ hide: () => actions.hide(id) });
return (
<Dialog
open
onClose={() => {
onClose?.();
actions.hide(id);
}}
{...props}
>
{children}
</Dialog>
);
}
export const useDialog = () => useContext(DialogContext).actions;
export function Dialogs() {
const { dialogs } = useContext(DialogContext);
return (
<>
{dialogs.map((props: DialogEntry) => (
<DialogInstance key={props.id} {...props} />
))}
</>
);
}
export const DialogContext = createContext<DialogState>({} as DialogState);

View File

@@ -0,0 +1,75 @@
import React, { useContext, useMemo, useState } from 'react';
import { trackEvent } from '../lib/analytics';
import { Dialog, type DialogProps } from './core/Dialog';
import { DialogContext } from './DialogContext';
type DialogEntry = {
id: string;
render: ({ hide }: { hide: () => void }) => React.ReactNode;
} & Omit<DialogProps, 'open' | 'children'>;
export interface DialogState {
dialogs: DialogEntry[];
actions: Actions;
}
interface Actions {
show: (d: DialogEntry) => void;
toggle: (d: DialogEntry) => void;
hide: (id: string) => void;
}
export const DialogProvider = ({ children }: { children: React.ReactNode }) => {
const [dialogs, setDialogs] = useState<DialogState['dialogs']>([]);
const actions = useMemo<Actions>(
() => ({
show({ id, ...props }: DialogEntry) {
trackEvent('dialog', 'show', { id });
setDialogs((a) => [...a.filter((d) => d.id !== id), { id, ...props }]);
},
toggle({ id, ...props }: DialogEntry) {
if (dialogs.some((d) => d.id === id)) this.hide(id);
else this.show({ id, ...props });
},
hide: (id: string) => {
setDialogs((a) => a.filter((d) => d.id !== id));
},
}),
[dialogs],
);
const state: DialogState = {
dialogs,
actions,
};
return <DialogContext.Provider value={state}>{children}</DialogContext.Provider>;
};
function DialogInstance({ id, render, onClose, ...props }: DialogEntry) {
const { actions } = useContext(DialogContext);
const children = render({ hide: () => actions.hide(id) });
return (
<Dialog
open
onClose={() => {
onClose?.();
actions.hide(id);
}}
{...props}
>
{children}
</Dialog>
);
}
export function Dialogs() {
const { dialogs } = useContext(DialogContext);
return (
<>
{dialogs.map((props: DialogEntry) => (
<DialogInstance key={props.id} {...props} />
))}
</>
);
}

View File

@@ -8,7 +8,7 @@ import { Button } from './core/Button';
import type { DropdownItem } from './core/Dropdown';
import { Dropdown } from './core/Dropdown';
import { Icon } from './core/Icon';
import { useDialog } from './DialogContext';
import { useDialog } from '../hooks/useDialog';
import { EnvironmentEditDialog } from './EnvironmentEditDialog';
type Props = {

View File

@@ -1,18 +1,20 @@
import type { HttpRequest } from '@yaakapp-internal/models';
import { updateSchema } from 'cm6-graphql';
import type { EditorView } from 'codemirror';
import { formatSdl } from 'format-graphql';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useLocalStorage } from 'react-use';
import { useIntrospectGraphQL } from '../hooks/useIntrospectGraphQL';
import { tryFormatJson } from '../lib/formatters';
import { Button } from './core/Button';
import { Dropdown } from './core/Dropdown';
import type { EditorProps } from './core/Editor';
import { Editor, formatGraphQL } from './core/Editor';
import type { EditorProps } from './core/Editor/Editor';
import { Editor } from './core/Editor/Editor';
import { FormattedError } from './core/FormattedError';
import { Icon } from './core/Icon';
import { Separator } from './core/Separator';
import { useDialog } from './DialogContext';
import { useDialog } from '../hooks/useDialog';
type Props = Pick<EditorProps, 'heightMode' | 'className' | 'forceUpdateKey'> & {
baseRequest: HttpRequest;
@@ -168,7 +170,7 @@ export function GraphQLEditor({ body, onChange, baseRequest, ...extraEditorProps
<Editor
language="graphql"
heightMode="auto"
format={formatGraphQL}
format={formatSdl}
defaultValue={currentBody.query}
onChange={handleChangeQuery}
placeholder="..."

View File

@@ -16,13 +16,13 @@ import { tryFormatJson } from '../lib/formatters';
import type { GrpcRequest } from '@yaakapp-internal/models';
import { count } from '../lib/pluralize';
import { Button } from './core/Button';
import type { EditorProps } from './core/Editor';
import { Editor } from './core/Editor';
import { FormattedError } from './core/FormattedError';
import { InlineCode } from './core/InlineCode';
import { VStack } from './core/Stacks';
import { useDialog } from './DialogContext';
import { useDialog } from '../hooks/useDialog';
import { GrpcProtoSelection } from './GrpcProtoSelection';
import type { EditorProps} from './core/Editor/Editor';
import {Editor} from './core/Editor/Editor';
type Props = Pick<EditorProps, 'heightMode' | 'onChange' | 'className'> & {
services: ReflectResponseService[] | null;

View File

@@ -1,10 +1,11 @@
import classNames from 'classnames';
import type { HTMLAttributes, ReactNode } from 'react';
import React from 'react';
import { useSettings } from '../hooks/useSettings';
import { useOsInfo } from '../hooks/useOsInfo';
import { useSettings } from '../hooks/useSettings';
import { useStoplightsVisible } from '../hooks/useStoplightsVisible';
import { WINDOW_CONTROLS_WIDTH, WindowControls } from './WindowControls';
import { HEADER_SIZE_LG, HEADER_SIZE_MD, WINDOW_CONTROLS_WIDTH } from '../lib/constants';
import { WindowControls } from './WindowControls';
interface HeaderSizeProps extends HTMLAttributes<HTMLDivElement> {
children?: ReactNode;
@@ -13,9 +14,6 @@ interface HeaderSizeProps extends HTMLAttributes<HTMLDivElement> {
onlyXWindowControl?: boolean;
}
export const HEADER_SIZE_MD = '27px';
export const HEADER_SIZE_LG = '38px';
export function HeaderSize({
className,
style,
@@ -33,7 +31,8 @@ export function HeaderSize({
style={{
...style,
// Add padding for macOS stoplights, but keep it the same width (account for the interface scale)
paddingLeft: (stoplightsVisible && !ignoreControlsSpacing) ? 72 / settings.interfaceScale : undefined,
paddingLeft:
stoplightsVisible && !ignoreControlsSpacing ? 72 / settings.interfaceScale : undefined,
...(size === 'md' ? { height: HEADER_SIZE_MD } : {}),
...(size === 'lg' ? { height: HEADER_SIZE_LG } : {}),
...(osInfo.osType === 'macos' || ignoreControlsSpacing

View File

@@ -3,7 +3,7 @@ import { useLicense } from '@yaakapp-internal/license';
import { useOpenSettings } from '../hooks/useOpenSettings';
import type { ButtonProps } from './core/Button';
import { Button } from './core/Button';
import { SettingsTab } from './Settings/Settings';
import {SettingsTab} from "./Settings/SettingsTab";
const details: Record<
LicenseCheckStatus['type'],

View File

@@ -4,7 +4,7 @@ import { useRef } from 'react';
import Markdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import { useKeyValue } from '../hooks/useKeyValue';
import { Editor } from './core/Editor';
import {Editor} from "./core/Editor/Editor";
import { IconButton } from './core/IconButton';
import { SplitLayout } from './core/SplitLayout';
import { VStack } from './core/Stacks';

View File

@@ -1,16 +1,15 @@
import { useNavigate } from '@tanstack/react-router';
import type { GrpcRequest, HttpRequest } from '@yaakapp-internal/models';
import React, { useState } from 'react';
import { useToast } from '../hooks/useToast';
import { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest';
import { useUpdateAnyHttpRequest } from '../hooks/useUpdateAnyHttpRequest';
import { useWorkspaces } from '../hooks/useWorkspaces';
import { fallbackRequestName } from '../lib/fallbackRequestName';
import { router } from '../main';
import { Route } from '../routes/workspaces/$workspaceId/index';
import { Button } from './core/Button';
import { InlineCode } from './core/InlineCode';
import { Select } from './core/Select';
import { VStack } from './core/Stacks';
import { useToast } from './ToastContext';
interface Props {
activeWorkspaceId: string;
@@ -23,6 +22,7 @@ export function MoveToWorkspaceDialog({ onDone, request, activeWorkspaceId }: Pr
const updateHttpRequest = useUpdateAnyHttpRequest();
const updateGrpcRequest = useUpdateAnyGrpcRequest();
const toast = useToast();
const navigate = useNavigate();
const [selectedWorkspaceId, setSelectedWorkspaceId] = useState<string>(activeWorkspaceId);
return (
@@ -69,10 +69,10 @@ export function MoveToWorkspaceDialog({ onDone, request, activeWorkspaceId }: Pr
size="xs"
color="secondary"
className="mr-auto min-w-[5rem]"
onClick={() => {
onClick={async () => {
toast.hide('workspace-moved');
router.navigate({
to: Route.fullPath,
await navigate({
to: '/workspaces/$workspaceId',
params: { workspaceId: selectedWorkspaceId },
});
}}

View File

@@ -1,3 +1,4 @@
import { useNavigate } from '@tanstack/react-router';
import classNames from 'classnames';
import { useMemo, useRef } from 'react';
import { useKeyPressEvent } from 'react-use';
@@ -7,8 +8,6 @@ import { useHotKey } from '../hooks/useHotKey';
import { useRecentRequests } from '../hooks/useRecentRequests';
import { useRequests } from '../hooks/useRequests';
import { fallbackRequestName } from '../lib/fallbackRequestName';
import { router } from '../main';
import { Route } from '../routes/workspaces/$workspaceId/requests/$requestId';
import type { ButtonProps } from './core/Button';
import { Button } from './core/Button';
import type { DropdownItem, DropdownRef } from './core/Dropdown';
@@ -22,6 +21,7 @@ export function RecentRequestsDropdown({ className }: Pick<ButtonProps, 'classNa
const allRecentRequestIds = useRecentRequests();
const recentRequestIds = useMemo(() => allRecentRequestIds.slice(1), [allRecentRequestIds]);
const requests = useRequests();
const navigate = useNavigate();
// Handle key-up
useKeyPressEvent('Control', undefined, () => {
@@ -52,9 +52,9 @@ export function RecentRequestsDropdown({ className }: Pick<ButtonProps, 'classNa
label: fallbackRequestName(request),
// leftSlot: <CountBadge className="!ml-0 px-0 w-5" count={recentRequestItems.length} />,
leftSlot: <HttpMethodTag className="text-right" shortNames request={request} />,
onSelect: () => {
router.navigate({
to: Route.fullPath,
onSelect: async () => {
await navigate({
to: '/workspaces/$workspaceId/requests/$requestId',
params: {
requestId: request.id,
workspaceId: activeWorkspace.id,
@@ -77,7 +77,7 @@ export function RecentRequestsDropdown({ className }: Pick<ButtonProps, 'classNa
}
return recentRequestItems.slice(0, 20);
}, [activeWorkspace, recentRequestIds, requests]);
}, [activeWorkspace, navigate, recentRequestIds, requests]);
return (
<Dropdown ref={dropdownRef} items={items}>

View File

@@ -1,16 +1,15 @@
import { useNavigate } from '@tanstack/react-router';
import { useEffect } from 'react';
import { getRecentCookieJars } from '../hooks/useRecentCookieJars';
import { getRecentEnvironments } from '../hooks/useRecentEnvironments';
import { getRecentRequests } from '../hooks/useRecentRequests';
import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces';
import { useWorkspaces } from '../hooks/useWorkspaces';
import { router } from '../main';
import { Route as WorkspaceRoute } from '../routes/workspaces/$workspaceId';
import { Route as RequestRoute } from '../routes/workspaces/$workspaceId/requests/$requestId';
export function RedirectToLatestWorkspace() {
const workspaces = useWorkspaces();
const recentWorkspaces = useRecentWorkspaces();
const navigate = useNavigate();
useEffect(() => {
if (workspaces.length === 0) {
@@ -25,20 +24,20 @@ export function RedirectToLatestWorkspace() {
const requestId = (await getRecentRequests(workspaceId))[0] ?? null;
if (workspaceId != null && requestId != null) {
await router.navigate({
to: RequestRoute.fullPath,
await navigate({
to: '/workspaces/$workspaceId/requests/$requestId',
params: { workspaceId, requestId },
search: { cookieJarId, environmentId },
});
} else {
await router.navigate({
to: WorkspaceRoute.fullPath,
await navigate({
to: '/workspaces/$workspaceId',
params: { workspaceId },
search: { cookieJarId, environmentId },
});
}
})();
}, [recentWorkspaces, workspaces, workspaces.length]);
}, [navigate, recentWorkspaces, workspaces, workspaces.length]);
return <></>;
}

View File

@@ -15,7 +15,7 @@ import { getHttpRequest } from '../lib/store';
import type { DropdownItem } from './core/Dropdown';
import { ContextMenu } from './core/Dropdown';
import { Icon } from './core/Icon';
import { useDialog } from './DialogContext';
import { useDialog } from '../hooks/useDialog';
import { FolderSettingsDialog } from './FolderSettingsDialog';
import type { SidebarTreeNode } from './Sidebar';

View File

@@ -13,6 +13,7 @@ import { useRequestEditor, useRequestEditorEvent } from '../hooks/useRequestEdit
import { useRequests } from '../hooks/useRequests';
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
import { useSendAnyHttpRequest } from '../hooks/useSendAnyHttpRequest';
import { useToast } from '../hooks/useToast';
import { useUpdateAnyHttpRequest } from '../hooks/useUpdateAnyHttpRequest';
import { languageFromContentType } from '../lib/contentType';
import { fallbackRequestName } from '../lib/fallbackRequestName';
@@ -34,7 +35,7 @@ import { BasicAuth } from './BasicAuth';
import { BearerAuth } from './BearerAuth';
import { BinaryFileEditor } from './BinaryFileEditor';
import { CountBadge } from './core/CountBadge';
import { Editor } from './core/Editor';
import { Editor } from './core/Editor/Editor';
import type {
GenericCompletionConfig,
GenericCompletionOption,
@@ -50,7 +51,6 @@ import { FormUrlencodedEditor } from './FormUrlencodedEditor';
import { GraphQLEditor } from './GraphQLEditor';
import { HeadersEditor } from './HeadersEditor';
import { MarkdownEditor } from './MarkdownEditor';
import { useToast } from './ToastContext';
import { UrlBar } from './UrlBar';
import { UrlParametersEditor } from './UrlParameterEditor';
@@ -446,25 +446,28 @@ export const RequestPane = memo(function RequestPane({
<EmptyStateText>Empty Body</EmptyStateText>
)}
</TabContent>
<TabContent value={TAB_DESCRIPTION}><div className="grid grid-rows-[auto_minmax(0,1fr)] h-full">
<PlainInput
label="Request Name"
hideLabel
defaultValue={activeRequest.name}
className="font-sans !text-xl !px-0"
containerClassName="border-0"
placeholder={fallbackRequestName(activeRequest)}
onChange={(name) => updateRequest.mutate({ id: activeRequestId, update: { name } })}
/>
<MarkdownEditor
name="request-description"
placeholder="A Markdown description of this request."
defaultValue={activeRequest.description}
onChange={(description) =>
updateRequest.mutate({ id: activeRequestId, update: { description } })
}
/>
</div>
<TabContent value={TAB_DESCRIPTION}>
<div className="grid grid-rows-[auto_minmax(0,1fr)] h-full">
<PlainInput
label="Request Name"
hideLabel
defaultValue={activeRequest.name}
className="font-sans !text-xl !px-0"
containerClassName="border-0"
placeholder={fallbackRequestName(activeRequest)}
onChange={(name) =>
updateRequest.mutate({ id: activeRequestId, update: { name } })
}
/>
<MarkdownEditor
name="request-description"
placeholder="A Markdown description of this request."
defaultValue={activeRequest.description}
onChange={(description) =>
updateRequest.mutate({ id: activeRequestId, update: { description } })
}
/>
</div>
</TabContent>
</Tabs>
</>

View File

@@ -1,12 +1,12 @@
import { useNavigate } from '@tanstack/react-router';
import { useRouteError } from 'react-router-dom';
import { router } from '../main';
import { Route } from '../routes/workspaces';
import { Button } from './core/Button';
import { FormattedError } from './core/FormattedError';
import { Heading } from './core/Heading';
import { VStack } from './core/Stacks';
export default function RouteError() {
const navigate = useNavigate();
const error = useRouteError();
console.log('Error', error);
const stringified = JSON.stringify(error);
@@ -20,8 +20,8 @@ export default function RouteError() {
<VStack space={2}>
<Button
color="primary"
onClick={() => {
router.navigate({ to: Route.fullPath });
onClick={async () => {
await navigate({ to: '/workspaces' });
}}
>
Go Home

View File

@@ -1,3 +1,4 @@
import { useSearch } from '@tanstack/react-router';
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
import classNames from 'classnames';
import React, { useState } from 'react';
@@ -12,18 +13,10 @@ import { SettingsGeneral } from './SettingsGeneral';
import { SettingsLicense } from './SettingsLicense';
import { SettingsPlugins } from './SettingsPlugins';
import { SettingsProxy } from './SettingsProxy';
import { SettingsTab } from './SettingsTab';
interface Props {
hide?: () => void;
defaultTab?: SettingsTab;
}
export enum SettingsTab {
General = 'general',
Proxy = 'proxy',
Appearance = 'appearance',
Plugins = 'plugins',
License = 'license',
}
const tabs = [
@@ -34,9 +27,10 @@ const tabs = [
SettingsTab.License,
];
export default function Settings({ hide, defaultTab }: Props) {
export default function Settings({ hide }: Props) {
const osInfo = useOsInfo();
const [tab, setTab] = useState<string>(defaultTab ?? SettingsTab.General);
const { tab: tabFromQuery } = useSearch({ from: '/workspaces/$workspaceId/settings' });
const [tab, setTab] = useState<string>(tabFromQuery ?? SettingsTab.General);
// Close settings window on escape
// TODO: Could this be put in a better place? Eg. in Rust key listener when creating the window

View File

@@ -9,7 +9,7 @@ import { getThemes } from '../../lib/theme/themes';
import { isThemeDark } from '../../lib/theme/window';
import type { ButtonProps } from '../core/Button';
import { Checkbox } from '../core/Checkbox';
import { Editor } from '../core/Editor';
import {Editor} from "../core/Editor/Editor";
import type { IconProps } from '../core/Icon';
import { Icon } from '../core/Icon';
import { IconButton } from '../core/IconButton';

View File

@@ -8,7 +8,7 @@ import { yaakDark } from '../../lib/theme/themes/yaak';
import { getThemeCSS } from '../../lib/theme/window';
import { Banner } from '../core/Banner';
import { Button } from '../core/Button';
import { Editor } from '../core/Editor';
import {Editor} from "../core/Editor/Editor";
import type { IconProps } from '../core/Icon';
import { Icon } from '../core/Icon';
import { IconButton } from '../core/IconButton';

View File

@@ -20,7 +20,7 @@ export function SettingsGeneral() {
const settings = useSettings();
const updateSettings = useUpdateSettings();
const appInfo = useAppInfo();
const checkForUpdates = useCheckForUpdates();
const checkForUpdates = useCheckForUpdates();
if (settings == null || workspace == null) {
return null;

View File

@@ -0,0 +1,8 @@
export enum SettingsTab {
General = 'general',
Proxy = 'proxy',
Appearance = 'appearance',
Plugins = 'plugins',
License = 'license',
}

View File

@@ -10,7 +10,7 @@ import type { DropdownRef } from './core/Dropdown';
import { Dropdown } from './core/Dropdown';
import { Icon } from './core/Icon';
import { IconButton } from './core/IconButton';
import { useDialog } from './DialogContext';
import { useDialog } from '../hooks/useDialog';
import { KeyboardShortcutsDialog } from './KeyboardShortcutsDialog';
export function SettingsDropdown() {

View File

@@ -1,6 +1,7 @@
import { useNavigate } from '@tanstack/react-router';
import type { Folder, GrpcRequest, HttpRequest, Workspace } from '@yaakapp-internal/models';
import classNames from 'classnames';
import { atom, useAtom } from 'jotai';
import { useAtom } from 'jotai';
import React, { memo, useCallback, useMemo, useRef, useState } from 'react';
import { useKey, useKeyPressEvent } from 'react-use';
import { getActiveRequest } from '../hooks/useActiveRequest';
@@ -16,9 +17,8 @@ import { useSidebarHidden } from '../hooks/useSidebarHidden';
import { useUpdateAnyFolder } from '../hooks/useUpdateAnyFolder';
import { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest';
import { useUpdateAnyHttpRequest } from '../hooks/useUpdateAnyHttpRequest';
import { router } from '../main';
import { Route } from '../routes/workspaces/$workspaceId/requests/$requestId';
import { ContextMenu } from './core/Dropdown';
import { sidebarSelectedIdAtom } from './SidebarAtoms';
import type { SidebarItemProps } from './SidebarItem';
import { SidebarItems } from './SidebarItems';
@@ -32,9 +32,6 @@ export interface SidebarTreeNode {
depth: number;
}
// This is an atom so we can use it in the child items to avoid re-rendering the entire list
export const sidebarSelectedIdAtom = atom<string | null>(null);
export const Sidebar = memo(function Sidebar({ className }: Props) {
const [hidden, setHidden] = useSidebarHidden();
const sidebarRef = useRef<HTMLLIElement>(null);
@@ -52,6 +49,7 @@ export const Sidebar = memo(function Sidebar({ className }: Props) {
const [draggingId, setDraggingId] = useState<string | null>(null);
const [hoveredTree, setHoveredTree] = useState<SidebarTreeNode | null>(null);
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
const navigate = useNavigate();
const { value: collapsed, set: setCollapsed } = useKeyValue<Record<string, boolean>>({
key: ['sidebar_collapsed', activeWorkspace?.id ?? 'n/a'],
fallback: {},
@@ -165,8 +163,8 @@ export const Sidebar = memo(function Sidebar({ className }: Props) {
if (item.model === 'folder') {
await setCollapsed((c) => ({ ...c, [item.id]: !c[item.id] }));
} else {
router.navigate({
to: Route.fullPath,
await navigate({
to: '/workspaces/$workspaceId/requests/$requestId',
params: {
requestId: id,
workspaceId: item.workspaceId,
@@ -179,7 +177,7 @@ export const Sidebar = memo(function Sidebar({ className }: Props) {
setSelectedTree(tree);
}
},
[treeParentMap, setCollapsed, setHasFocus, setSelectedId],
[treeParentMap, setCollapsed, navigate, setSelectedId],
);
const handleClearSelected = useCallback(() => {
@@ -214,7 +212,7 @@ export const Sidebar = memo(function Sidebar({ className }: Props) {
);
});
useKeyPressEvent('Enter', (e) => {
useKeyPressEvent('Enter', async (e) => {
if (!hasFocus) return;
const selected = selectableRequests.find((r) => r.id === selectedId);
if (!selected || activeWorkspace == null) {
@@ -222,8 +220,8 @@ export const Sidebar = memo(function Sidebar({ className }: Props) {
}
e.preventDefault();
router.navigate({
to: Route.fullPath,
await navigate({
to: '/workspaces/$workspaceId/requests/$requestId',
params: {
requestId: selected.id,
workspaceId: activeWorkspace?.id ?? null,

View File

@@ -0,0 +1,5 @@
// This is an atom so we can use it in the child items to avoid re-rendering the entire list
import {atom} from "jotai/index";
export const sidebarSelectedIdAtom = atom<string | null>(null);

View File

@@ -8,14 +8,14 @@ import { activeRequestAtom } from '../hooks/useActiveRequest';
import { useScrollIntoView } from '../hooks/useScrollIntoView';
import { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest';
import { useUpdateAnyHttpRequest } from '../hooks/useUpdateAnyHttpRequest';
import { jotaiStore } from '../lib/jotai';
import { isResponseLoading } from '../lib/model_util';
import { jotaiStore } from '../routes/__root';
import { HttpMethodTag } from './core/HttpMethodTag';
import { Icon } from './core/Icon';
import { StatusTag } from './core/StatusTag';
import { RequestContextMenu } from './RequestContextMenu';
import type { SidebarTreeNode } from './Sidebar';
import { sidebarSelectedIdAtom } from './Sidebar';
import {sidebarSelectedIdAtom} from "./SidebarAtoms";
import type { SidebarItemsProps } from './SidebarItems';
enum ItemTypes {
@@ -42,7 +42,7 @@ type DragItem = {
itemName: string;
};
function SidebarItem_({
export const SidebarItem = memo(function SidebarItem({
itemName,
itemId,
itemModel,
@@ -106,7 +106,7 @@ function SidebarItem_({
const [selected, setSelected] = useState<boolean>(
jotaiStore.get(sidebarSelectedIdAtom) == itemId,
);
useEffect(() => {
useEffect(() => {
jotaiStore.sub(sidebarSelectedIdAtom, () => {
const value = jotaiStore.get(sidebarSelectedIdAtom);
setSelected(value === itemId);
@@ -262,17 +262,4 @@ function SidebarItem_({
{children}
</li>
);
}
export const SidebarItem = memo<SidebarItemProps>(SidebarItem_, (a, b) => {
const different = [];
for (const key of Object.keys(a) as (keyof SidebarItemProps)[]) {
if (a[key] !== b[key]) {
different.push(key);
}
}
if (different.length > 0) {
console.log('ITEM DIFFERENT -------------------', different.join(', '));
}
return different.length === 0;
});

View File

@@ -23,7 +23,7 @@ export interface SidebarItemsProps {
grpcConnections: GrpcConnection[];
}
function SidebarItems_({
export const SidebarItems = memo(function SidebarItems({
tree,
selectedTree,
draggingId,
@@ -102,17 +102,4 @@ function SidebarItems_({
)}
</VStack>
);
}
export const SidebarItems = memo<SidebarItemsProps>(SidebarItems_, (a, b) => {
const different = [];
for (const key of Object.keys(a) as (keyof SidebarItemsProps)[]) {
if (a[key] !== b[key]) {
different.push(key);
}
}
if (different.length > 0) {
console.log('ITEMS DIFFERENT -------------------', different.join(', '));
}
return different.length === 0;
});
})

View File

@@ -1,105 +1,4 @@
import type { ShowToastRequest } from '@yaakapp-internal/plugin';
import { AnimatePresence } from 'framer-motion';
import type { ReactNode } from 'react';
import React, { createContext, useContext, useMemo, useRef, useState } from 'react';
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
import { generateId } from '../lib/generateId';
import type { ToastProps } from './core/Toast';
import { Toast } from './core/Toast';
import { Portal } from './Portal';
import { createContext } from 'react';
import type { ToastState } from './Toasts';
type ToastEntry = {
id?: string;
message: ReactNode;
timeout?: 3000 | 5000 | 8000 | null;
onClose?: ToastProps['onClose'];
} & Omit<ToastProps, 'onClose' | 'open' | 'children' | 'timeout'>;
type PrivateToastEntry = ToastEntry & {
id: string;
timeout: number | null;
};
interface State {
toasts: PrivateToastEntry[];
actions: Actions;
}
interface Actions {
show: (d: ToastEntry) => void;
hide: (id: string) => void;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const ToastContext = createContext<State>({} as State);
export const ToastProvider = ({ children }: { children: React.ReactNode }) => {
const [toasts, setToasts] = useState<State['toasts']>([]);
const timeoutRef = useRef<NodeJS.Timeout>();
const actions = useMemo<Actions>(
() => ({
show({ id, timeout = 5000, ...props }: ToastEntry) {
id = id ?? generateId();
if (timeout != null) {
timeoutRef.current = setTimeout(() => this.hide(id), timeout);
}
setToasts((a) => {
if (a.some((v) => v.id === id)) {
// It's already visible with this id
return a;
}
return [...a, { id, timeout, ...props }];
});
return id;
},
hide: (id: string) => {
setToasts((all) => {
const t = all.find((t) => t.id === id);
t?.onClose?.();
return all.filter((t) => t.id !== id);
});
},
}),
[],
);
useListenToTauriEvent<ShowToastRequest>('show_toast', (event) => {
actions.show({ ...event.payload });
});
const state: State = { toasts, actions };
return <ToastContext.Provider value={state}>{children}</ToastContext.Provider>;
};
function ToastInstance({ id, message, timeout, ...props }: PrivateToastEntry) {
const { actions } = useContext(ToastContext);
return (
<Toast
open
timeout={timeout}
{...props}
// We call onClose inside actions.hide instead of passing to toast so that
// it gets called from external close calls as well
onClose={() => actions.hide(id)}
>
{message}
</Toast>
);
}
export const useToast = () => useContext(ToastContext).actions;
export const Toasts = () => {
const { toasts } = useContext(ToastContext);
return (
<Portal name="toasts">
<div className="absolute right-0 bottom-0 z-20">
<AnimatePresence>
{toasts.map((props: PrivateToastEntry) => (
<ToastInstance key={props.id} {...props} />
))}
</AnimatePresence>
</div>
</Portal>
);
};
export const ToastContext = createContext<ToastState>({} as ToastState);

View File

@@ -0,0 +1,100 @@
import type { ShowToastRequest } from '@yaakapp-internal/plugin';
import { AnimatePresence } from 'framer-motion';
import React, {type ReactNode, useContext, useMemo, useRef, useState} from 'react';
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
import { generateId } from '../lib/generateId';
import {Toast, type ToastProps} from './core/Toast';
import { Portal } from './Portal';
import { ToastContext } from './ToastContext';
type ToastEntry = {
id?: string;
message: ReactNode;
timeout?: 3000 | 5000 | 8000 | null;
onClose?: ToastProps['onClose'];
} & Omit<ToastProps, 'onClose' | 'open' | 'children' | 'timeout'>;
type PrivateToastEntry = ToastEntry & {
id: string;
timeout: number | null;
};
export interface ToastState {
toasts: PrivateToastEntry[];
actions: Actions;
}
export interface Actions {
show: (d: ToastEntry) => void;
hide: (id: string) => void;
}
export const ToastProvider = ({ children }: { children: React.ReactNode }) => {
const [toasts, setToasts] = useState<ToastState['toasts']>([]);
const timeoutRef = useRef<NodeJS.Timeout>();
const actions = useMemo<Actions>(
() => ({
show({ id, timeout = 5000, ...props }: ToastEntry) {
id = id ?? generateId();
if (timeout != null) {
timeoutRef.current = setTimeout(() => this.hide(id), timeout);
}
setToasts((a) => {
if (a.some((v) => v.id === id)) {
// It's already visible with this id
return a;
}
return [...a, { id, timeout, ...props }];
});
return id;
},
hide: (id: string) => {
setToasts((all) => {
const t = all.find((t) => t.id === id);
t?.onClose?.();
return all.filter((t) => t.id !== id);
});
},
}),
[],
);
useListenToTauriEvent<ShowToastRequest>('show_toast', (event) => {
actions.show({ ...event.payload });
});
const state: ToastState = { toasts, actions };
return <ToastContext.Provider value={state}>{children}</ToastContext.Provider>;
};
function ToastInstance({ id, message, timeout, ...props }: PrivateToastEntry) {
const { actions } = useContext(ToastContext);
return (
<Toast
open
timeout={timeout}
{...props}
// We call onClose inside actions.hide instead of passing to toast so that
// it gets called from external close calls as well
onClose={() => actions.hide(id)}
>
{message}
</Toast>
);
}
export const Toasts = () => {
const { toasts } = useContext(ToastContext);
return (
<Portal name="toasts">
<div className="absolute right-0 bottom-0 z-20">
<AnimatePresence>
{toasts.map((props: PrivateToastEntry) => (
<ToastInstance key={props.id} {...props} />
))}
</AnimatePresence>
</div>
</Portal>
);
};

View File

@@ -2,6 +2,7 @@ import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
import classNames from 'classnames';
import React, { useState } from 'react';
import { useOsInfo } from '../hooks/useOsInfo';
import {WINDOW_CONTROLS_WIDTH} from "../lib/constants";
import { Button } from './core/Button';
import { HStack } from './core/Stacks';
@@ -11,8 +12,6 @@ interface Props {
macos?: boolean;
}
export const WINDOW_CONTROLS_WIDTH = '10.5rem';
export function WindowControls({ className, onlyX }: Props) {
const [maximized, setMaximized] = useState<boolean>(false);
const osInfo = useOsInfo();

View File

@@ -1,7 +1,7 @@
import classNames from 'classnames';
import { motion } from 'framer-motion';
import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useCallback, useMemo, useRef, useState } from 'react';
import { useActiveRequest } from '../hooks/useActiveRequest';
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
import { useFloatingSidebarHidden } from '../hooks/useFloatingSidebarHidden';
@@ -41,10 +41,6 @@ export function Workspace() {
null,
);
useEffect(() => {
console.log('RENDER WORKSPACE');
}, []);
const unsub = () => {
if (moveState.current !== null) {
document.documentElement.removeEventListener('mousemove', moveState.current.move);

View File

@@ -14,7 +14,7 @@ import type { DropdownItem } from './core/Dropdown';
import { Icon } from './core/Icon';
import type { RadioDropdownItem } from './core/RadioDropdown';
import { RadioDropdown } from './core/RadioDropdown';
import { useDialog } from './DialogContext';
import { useDialog } from '../hooks/useDialog';
import { OpenWorkspaceDialog } from './OpenWorkspaceDialog';
import { WorkspaceSettingsDialog } from './WorkpaceSettingsDialog';

View File

@@ -1,5 +1,5 @@
import { useCallback, useMemo } from 'react';
import { Editor } from './Editor';
import {Editor} from "./Editor/Editor";
import type { PairEditorProps } from './PairEditor';
type Props = PairEditorProps;

View File

@@ -19,11 +19,11 @@ import {
useRef,
} from 'react';
import { useActiveEnvironmentVariables } from '../../../hooks/useActiveEnvironmentVariables';
import {useDialog} from "../../../hooks/useDialog";
import { parseTemplate } from '../../../hooks/useParseTemplate';
import { useRequestEditor } from '../../../hooks/useRequestEditor';
import { useSettings } from '../../../hooks/useSettings';
import { useTemplateFunctions } from '../../../hooks/useTemplateFunctions';
import { useDialog } from '../../DialogContext';
import { TemplateFunctionDialog } from '../../TemplateFunctionDialog';
import { TemplateVariableDialog } from '../../TemplateVariableDialog';
import { IconButton } from '../IconButton';
@@ -33,11 +33,6 @@ import { baseExtensions, getLanguageExtension, multiLineExtensions } from './ext
import type { GenericCompletionConfig } from './genericCompletion';
import { singleLineExt } from './singleLine';
// Export some things so all the code-split parts are in this file
export { buildClientSchema, getIntrospectionQuery } from 'graphql/utilities';
export { graphql } from 'cm6-graphql';
export { formatSdl } from 'format-graphql';
export interface EditorProps {
id?: string;
readOnly?: boolean;

View File

@@ -37,7 +37,7 @@ import type { EnvironmentVariable } from '@yaakapp-internal/models';
import type { TemplateFunction } from '@yaakapp-internal/plugin';
import { graphql } from 'cm6-graphql';
import { EditorView } from 'codemirror';
import type { EditorProps } from './index';
import type {EditorProps} from "./Editor";
import { pairs } from './pairs/extension';
import { text } from './text/extension';
import { twig } from './twig/extension';

View File

@@ -1,12 +0,0 @@
import * as editor from './Editor';
export type { EditorProps } from './Editor';
// TODO: Figure out why code-splitting breaks production build from
// showing any content
// const editor = await import('./Editor');
export const Editor = editor.Editor;
export const graphql = editor.graphql;
export const getIntrospectionQuery = editor.getIntrospectionQuery;
export const buildClientSchema = editor.buildClientSchema;
export const formatGraphQL = editor.formatSdl;

View File

@@ -3,8 +3,8 @@ import type { EditorView } from 'codemirror';
import type { HTMLAttributes, ReactNode } from 'react';
import { forwardRef, useCallback, useMemo, useRef, useState } from 'react';
import { useStateWithDeps } from '../../hooks/useStateWithDeps';
import type { EditorProps } from './Editor';
import { Editor } from './Editor';
import type { EditorProps } from './Editor/Editor';
import { Editor } from './Editor/Editor';
import { IconButton } from './IconButton';
import { HStack } from './Stacks';

View File

@@ -8,8 +8,8 @@ import { useFormatText } from '../../hooks/useFormatText';
import { useResponseBodyEventSource } from '../../hooks/useResponseBodyEventSource';
import { isJSON } from '../../lib/contentType';
import { Button } from '../core/Button';
import type { EditorProps } from '../core/Editor';
import { Editor } from '../core/Editor';
import type { EditorProps } from '../core/Editor/Editor';
import { Editor } from '../core/Editor/Editor';
import { Icon } from '../core/Icon';
import { InlineCode } from '../core/InlineCode';
import { Separator } from '../core/Separator';

View File

@@ -10,14 +10,14 @@ import { useToggle } from '../../hooks/useToggle';
import { CopyButton } from '../CopyButton';
import { Banner } from '../core/Banner';
import { Button } from '../core/Button';
import type { EditorProps } from '../core/Editor';
import { Editor } from '../core/Editor';
import { hyperlink } from '../core/Editor/hyperlink/extension';
import { IconButton } from '../core/IconButton';
import { InlineCode } from '../core/InlineCode';
import { Input } from '../core/Input';
import { SizeTag } from '../core/SizeTag';
import { HStack } from '../core/Stacks';
import type { EditorProps } from '../core/Editor/Editor';
import { Editor } from '../core/Editor/Editor';
const extraExtensions = [hyperlink];
const LARGE_RESPONSE_BYTES = 2 * 1000 * 1000;

View File

@@ -1,6 +1,5 @@
import { useSearch } from '@tanstack/react-router';
import { getRouteApi, useSearch } from '@tanstack/react-router';
import { useCallback, useEffect, useMemo } from 'react';
import { Route } from '../routes/workspaces/$workspaceId';
import { useCookieJars } from './useCookieJars';
export const QUERY_COOKIE_JAR_ID = 'cookie_jar_id';
@@ -39,13 +38,18 @@ export function useEnsureActiveCookieJar() {
}, [activeCookieJarId, cookieJars, setActiveCookieJarId]);
}
const routeApi = getRouteApi('/workspaces/$workspaceId/');
function useActiveCookieJarId() {
// NOTE: This query param is accessed from Rust side, so do not change
const navigate = Route.useNavigate();
const { cookieJarId: id } = useSearch({ strict: false });
const navigate = routeApi.useNavigate();
const setId = useCallback(
(id: string) => navigate({ search: (prev) => ({ ...prev, cookieJarId: id }) }),
(id: string) =>
navigate({
search: (prev) => ({ ...prev, cookieJarId: id }),
}),
[navigate],
);

View File

@@ -1,6 +1,5 @@
import { useSearch } from '@tanstack/react-router';
import { getRouteApi, useSearch } from '@tanstack/react-router';
import { useCallback, useMemo } from 'react';
import { Route } from '../routes/workspaces/$workspaceId';
import { useEnvironments } from './useEnvironments';
export function useActiveEnvironment() {
@@ -15,14 +14,18 @@ export function useActiveEnvironment() {
export const QUERY_ENVIRONMENT_ID = 'environment_id';
const routeApi = getRouteApi('/workspaces/$workspaceId/');
function useActiveEnvironmentId() {
// NOTE: This query param is accessed from Rust side, so do not change
const navigate = Route.useNavigate();
const { environmentId: id } = useSearch({ strict: false });
const navigate = routeApi.useNavigate();
const setId = useCallback(
(environment_id: string | null) =>
navigate({ search: (prev) => ({ ...prev, environment_id: environment_id ?? undefined }) }),
navigate({
search: (prev) => ({ ...prev, environment_id: environment_id ?? undefined }),
}),
[navigate],
);

View File

@@ -1,6 +1,6 @@
import type { GrpcRequest, HttpRequest } from '@yaakapp-internal/models';
import { atom, useAtomValue } from 'jotai';
import { jotaiStore } from '../routes/__root';
import {jotaiStore} from "../lib/jotai";
import { activeRequestIdAtom } from './useActiveRequestId';
import { grpcRequestsAtom } from './useGrpcRequests';
import { httpRequestsAtom } from './useHttpRequests';

View File

@@ -1,7 +1,7 @@
import { useParams } from '@tanstack/react-router';
import { atom, useAtomValue } from 'jotai';
import { useEffect } from 'react';
import { jotaiStore } from '../routes/__root';
import {jotaiStore} from "../lib/jotai";
export const activeRequestIdAtom = atom<string>();

View File

@@ -2,7 +2,7 @@ import { useParams } from '@tanstack/react-router';
import type { Workspace } from '@yaakapp-internal/models';
import { atom, useAtomValue } from 'jotai/index';
import { useEffect } from 'react';
import { jotaiStore } from '../routes/__root';
import { jotaiStore } from '../lib/jotai';
import { useWorkspaces } from './useWorkspaces';
export const activeWorkspaceIdAtom = atom<string>();
@@ -17,6 +17,10 @@ function useActiveWorkspaceId(): string | null {
return useAtomValue(activeWorkspaceIdAtom) ?? null;
}
export function getActiveWorkspaceId() {
return jotaiStore.get(activeWorkspaceIdAtom);
}
export function useSubscribeActiveWorkspaceId() {
const { workspaceId } = useParams({ strict: false });
useEffect(() => {

View File

@@ -1,7 +1,7 @@
import { useEffect, useState } from 'react';
import { InlineCode } from '../components/core/InlineCode';
import { useToast } from '../components/ToastContext';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useToast } from './useToast';
export function useActiveWorkspaceChangedToast() {
const toast = useToast();

View File

@@ -1,8 +1,8 @@
import { useCallback } from 'react';
import type { DialogProps } from '../components/core/Dialog';
import { useDialog } from '../components/DialogContext';
import type { AlertProps } from './Alert';
import { Alert } from './Alert';
import {useDialog} from "./useDialog";
interface AlertArg {
id: string;

View File

@@ -8,6 +8,7 @@ import { useAppInfo } from './useAppInfo';
export function useCheckForUpdates() {
const alert = useAlert();
const appInfo = useAppInfo();
return useMutation({
mutationKey: ['check_for_updates'],
mutationFn: async () => {

View File

@@ -1,7 +1,7 @@
import type { DialogProps } from '../components/core/Dialog';
import { useDialog } from '../components/DialogContext';
import type { ConfirmProps } from './Confirm';
import { Confirm } from './Confirm';
import { useDialog } from './useDialog';
export function useConfirm() {
const dialog = useDialog();

View File

@@ -1,6 +1,6 @@
import { clear, writeText } from '@tauri-apps/plugin-clipboard-manager';
import { useCallback } from 'react';
import { useToast } from '../components/ToastContext';
import { useToast } from './useToast';
export function useCopy({ disableToast }: { disableToast?: boolean } = {}) {
const toast = useToast();

View File

@@ -1,18 +1,18 @@
import { useFastMutation } from './useFastMutation';
import { useNavigate } from '@tanstack/react-router';
import type { GrpcRequest } from '@yaakapp-internal/models';
import { useSetAtom } from 'jotai';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import { router } from '../main';
import { Route } from '../routes/workspaces/$workspaceId/requests/$requestId';
import { getActiveRequest } from './useActiveRequest';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useFastMutation } from './useFastMutation';
import { grpcRequestsAtom } from './useGrpcRequests';
import { updateModelList } from './useSyncModelStores';
export function useCreateGrpcRequest() {
const workspace = useActiveWorkspace();
const setGrpcRequests = useSetAtom(grpcRequestsAtom);
const navigate = useNavigate();
return useFastMutation<
GrpcRequest,
@@ -46,8 +46,8 @@ export function useCreateGrpcRequest() {
// Optimistic update
setGrpcRequests(updateModelList(request));
router.navigate({
to: Route.fullPath,
navigate({
to: '/workspaces/$workspaceId/requests/$requestId',
params: {
workspaceId: request.workspaceId,
requestId: request.id,

View File

@@ -1,18 +1,18 @@
import { useFastMutation } from './useFastMutation';
import type { HttpRequest } from '@yaakapp-internal/models';
import { useSetAtom } from 'jotai/index';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import { router } from '../main';
import { Route } from '../routes/workspaces/$workspaceId/requests/$requestId';
import { getActiveRequest } from './useActiveRequest';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useFastMutation } from './useFastMutation';
import { httpRequestsAtom } from './useHttpRequests';
import { updateModelList } from './useSyncModelStores';
import { useNavigate } from '@tanstack/react-router';
export function useCreateHttpRequest() {
const activeWorkspace = useActiveWorkspace();
const setHttpRequests = useSetAtom(httpRequestsAtom);
const navigate = useNavigate();
return useFastMutation<HttpRequest, unknown, Partial<HttpRequest>>({
mutationKey: ['create_http_request'],
@@ -40,8 +40,8 @@ export function useCreateHttpRequest() {
// Optimistic update
setHttpRequests(updateModelList(request));
await router.navigate({
to: Route.fullPath,
await navigate({
to: '/workspaces/$workspaceId/requests/$requestId',
params: { workspaceId: request.workspaceId, requestId: request.id },
search: (prev) => ({ ...prev }),
});

View File

@@ -1,9 +1,8 @@
import { useFastMutation } from './useFastMutation';
import { useNavigate } from '@tanstack/react-router';
import type { Workspace } from '@yaakapp-internal/models';
import { useSetAtom } from 'jotai/index';
import { invokeCmd } from '../lib/tauri';
import { router } from '../main';
import { Route } from '../routes/workspaces/$workspaceId';
import { useFastMutation } from './useFastMutation';
import { usePrompt } from './usePrompt';
import { updateModelList } from './useSyncModelStores';
import { workspacesAtom } from './useWorkspaces';
@@ -11,6 +10,7 @@ import { workspacesAtom } from './useWorkspaces';
export function useCreateWorkspace() {
const prompt = usePrompt();
const setWorkspaces = useSetAtom(workspacesAtom);
const navigate = useNavigate();
return useFastMutation<Workspace | null, void, void>({
mutationKey: ['create_workspace'],
@@ -34,8 +34,8 @@ export function useCreateWorkspace() {
// Optimistic update
setWorkspaces(updateModelList(workspace));
router.navigate({
to: Route.fullPath,
navigate({
to: '/workspaces/$workspaceId',
params: { workspaceId: workspace.id },
});
},

View File

@@ -1,13 +1,12 @@
import { useFastMutation } from './useFastMutation';
import { useNavigate } from '@tanstack/react-router';
import type { Workspace } from '@yaakapp-internal/models';
import { useSetAtom } from 'jotai';
import { InlineCode } from '../components/core/InlineCode';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import { router } from '../main';
import { Route } from '../routes/workspaces';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useConfirm } from './useConfirm';
import { useFastMutation } from './useFastMutation';
import { removeModelById } from './useSyncModelStores';
import { workspacesAtom } from './useWorkspaces';
@@ -15,6 +14,7 @@ export function useDeleteWorkspace(workspace: Workspace | null) {
const activeWorkspace = useActiveWorkspace();
const confirm = useConfirm();
const setWorkspaces = useSetAtom(workspacesAtom);
const navigate = useNavigate();
return useFastMutation<Workspace | null, string>({
mutationKey: ['delete_workspace', workspace?.id],
@@ -41,7 +41,7 @@ export function useDeleteWorkspace(workspace: Workspace | null) {
const { id: workspaceId } = workspace;
if (workspaceId === activeWorkspace?.id) {
router.navigate({ to: Route.fullPath });
navigate({ to: '/workspaces' });
}
},
});

View File

@@ -0,0 +1,6 @@
import { useContext } from 'react';
import { DialogContext } from '../components/DialogContext';
export function useDialog() {
return useContext(DialogContext).actions;
}

View File

@@ -1,9 +1,8 @@
import { useFastMutation } from './useFastMutation';
import { useNavigate } from '@tanstack/react-router';
import type { GrpcRequest } from '@yaakapp-internal/models';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import { router } from '../main';
import { Route } from '../routes/workspaces/$workspaceId/requests/$requestId';
import { useFastMutation } from './useFastMutation';
import { getGrpcProtoFiles, setGrpcProtoFiles } from './useGrpcProtoFiles';
export function useDuplicateGrpcRequest({
@@ -13,6 +12,7 @@ export function useDuplicateGrpcRequest({
id: string | null;
navigateAfter: boolean;
}) {
const navigate = useNavigate();
return useFastMutation<GrpcRequest, string>({
mutationKey: ['duplicate_grpc_request', id],
mutationFn: async () => {
@@ -28,8 +28,8 @@ export function useDuplicateGrpcRequest({
await setGrpcProtoFiles(request.id, protoFiles);
if (navigateAfter) {
await router.navigate({
to: Route.fullPath,
await navigate({
to: '/workspaces/$workspaceId/requests/$requestId',
params: { workspaceId: request.workspaceId, requestId: request.id },
search: (prev) => ({ ...prev }),
});

View File

@@ -1,9 +1,8 @@
import { useFastMutation } from './useFastMutation';
import { useNavigate } from '@tanstack/react-router';
import type { HttpRequest } from '@yaakapp-internal/models';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import { router } from '../main';
import { Route } from '../routes/workspaces/$workspaceId/requests/$requestId';
import { useFastMutation } from './useFastMutation';
export function useDuplicateHttpRequest({
id,
@@ -12,6 +11,7 @@ export function useDuplicateHttpRequest({
id: string | null;
navigateAfter: boolean;
}) {
const navigate = useNavigate();
return useFastMutation<HttpRequest, string>({
mutationKey: ['duplicate_http_request', id],
mutationFn: async () => {
@@ -21,8 +21,8 @@ export function useDuplicateHttpRequest({
onSettled: () => trackEvent('http_request', 'duplicate'),
onSuccess: async (request) => {
if (navigateAfter) {
router.navigate({
to: Route.fullPath,
await navigate({
to: '/workspaces/$workspaceId/requests/$requestId',
params: {
workspaceId: request.workspaceId,
requestId: request.id,

View File

@@ -1,10 +1,10 @@
import {useDialog} from "./useDialog";
import { useFastMutation } from './useFastMutation';
import { useDialog } from '../components/DialogContext';
import { ExportDataDialog } from '../components/ExportDataDialog';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useAlert } from './useAlert';
import { useWorkspaces } from './useWorkspaces';
import { useToast } from '../components/ToastContext';
import { useToast } from './useToast';
export function useExportData() {
const workspaces = useWorkspaces();

View File

@@ -1,6 +1,6 @@
import { useQuery } from '@tanstack/react-query';
import type { EditorProps } from '../components/core/Editor';
import { tryFormatJson, tryFormatXml } from '../lib/formatters';
import type { EditorProps } from '../components/core/Editor/Editor';
export function useFormatText({
text,

View File

@@ -1,6 +1,6 @@
import { useFastMutation } from './useFastMutation';
import type { HttpRequest } from '@yaakapp-internal/models';
import { useToast } from '../components/ToastContext';
import { useToast } from './useToast';
import { invokeCmd } from '../lib/tauri';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useCreateHttpRequest } from './useCreateHttpRequest';

View File

@@ -1,4 +1,4 @@
import { useFastMutation } from './useFastMutation';
import { useNavigate } from '@tanstack/react-router';
import type {
Environment,
Folder,
@@ -9,19 +9,19 @@ import type {
import { Button } from '../components/core/Button';
import { FormattedError } from '../components/core/FormattedError';
import { VStack } from '../components/core/Stacks';
import { useDialog } from '../components/DialogContext';
import { ImportDataDialog } from '../components/ImportDataDialog';
import { count } from '../lib/pluralize';
import { invokeCmd } from '../lib/tauri';
import { Route } from '../routes/workspaces/$workspaceId';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useAlert } from './useAlert';
import { router } from '../main';
import { useDialog } from './useDialog';
import { useFastMutation } from './useFastMutation';
export function useImportData() {
const dialog = useDialog();
const alert = useAlert();
const activeWorkspace = useActiveWorkspace();
const navigate = useNavigate();
const importData = async (filePath: string): Promise<boolean> => {
const imported: {
@@ -65,8 +65,8 @@ export function useImportData() {
if (importedWorkspace != null) {
const environmentId = imported.environments[0]?.id ?? null;
router.navigate({
to: Route.fullPath,
await navigate({
to: '/workspaces/$workspaceId',
params: { workspaceId: importedWorkspace.id },
search: { environmentId },
});

View File

@@ -1,6 +1,6 @@
import { useFastMutation } from './useFastMutation';
import type { HttpUrlParameter } from '@yaakapp-internal/models';
import { useToast } from '../components/ToastContext';
import { useToast } from './useToast';
import { pluralize } from '../lib/pluralize';
import { getHttpRequest } from '../lib/store';
import { useRequestEditor } from './useRequestEditor';

View File

@@ -1,7 +1,6 @@
import type { HttpRequest } from '@yaakapp-internal/models';
import type { IntrospectionQuery } from 'graphql';
import { buildClientSchema, getIntrospectionQuery, type IntrospectionQuery } from 'graphql';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { buildClientSchema, getIntrospectionQuery } from '../components/core/Editor';
import { minPromiseMillis } from '../lib/minPromiseMillis';
import { getResponseBodyText } from '../lib/responseBody';
import { sendEphemeralRequest } from '../lib/sendEphemeralRequest';

View File

@@ -1,6 +1,6 @@
import {useDialog} from "./useDialog";
import { useFastMutation } from './useFastMutation';
import React from 'react';
import { useDialog } from '../components/DialogContext';
import { MoveToWorkspaceDialog } from '../components/MoveToWorkspaceDialog';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useRequests } from './useRequests';

View File

@@ -1,6 +1,6 @@
import { open } from '@tauri-apps/plugin-shell';
import { Button } from '../components/core/Button';
import { useToast } from '../components/ToastContext';
import { useToast } from './useToast';
import { invokeCmd } from '../lib/tauri';
import { useListenToTauriEvent } from './useListenToTauriEvent';

View File

@@ -1,27 +1,26 @@
import { useFastMutation } from './useFastMutation';
import { SettingsTab } from '../components/Settings/Settings';
import { useRouter } from '@tanstack/react-router';
import { SettingsTab } from '../components/Settings/SettingsTab';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import { router } from '../main';
import { Route as SettingsRoute } from '../routes/workspaces/settings';
import { useActiveWorkspace } from './useActiveWorkspace';
import { getActiveWorkspaceId } from './useActiveWorkspace';
import { useFastMutation } from './useFastMutation';
export function useOpenSettings(tab = SettingsTab.General) {
const workspace = useActiveWorkspace();
const router = useRouter();
return useFastMutation({
mutationKey: ['open_settings'],
mutationFn: async () => {
if (workspace == null) return;
const workspaceId = getActiveWorkspaceId();
if (workspaceId == null) return;
trackEvent('dialog', 'show', { id: 'settings', tab: `${tab}` });
const location = router.buildLocation({
to: SettingsRoute.fullPath,
params: { workspaceId: workspace.id },
to: '/workspaces/$workspaceId/settings',
params: { workspaceId },
search: { tab },
});
await invokeCmd('cmd_new_child_window', {
url: location,
url: location.href,
label: 'settings',
title: 'Yaak Settings',
innerSize: [600, 550],

View File

@@ -1,13 +1,14 @@
import { useFastMutation } from './useFastMutation';
import { useNavigate, useRouter } from '@tanstack/react-router';
import { invokeCmd } from '../lib/tauri';
import { router } from '../main';
import { Route as WorkspaceRoute } from '../routes/workspaces/$workspaceId';
import { Route as RequestRoute } from '../routes/workspaces/$workspaceId/requests/$requestId';
import { useFastMutation } from './useFastMutation';
import { getRecentCookieJars } from './useRecentCookieJars';
import { getRecentEnvironments } from './useRecentEnvironments';
import { getRecentRequests } from './useRecentRequests';
export function useOpenWorkspace() {
const router = useRouter();
const navigate = useNavigate();
return useFastMutation({
mutationKey: ['open_workspace'],
mutationFn: async ({
@@ -24,18 +25,22 @@ export function useOpenWorkspace() {
if (inNewWindow) {
const location = router.buildLocation({
to: WorkspaceRoute.fullPath,
to: '/workspaces/$workspaceId',
params: { workspaceId },
search,
});
await invokeCmd('cmd_new_main_window', { url: location });
await invokeCmd('cmd_new_main_window', { url: location.href });
return;
}
if (requestId != null) {
router.navigate({ to: RequestRoute.fullPath, params: { workspaceId, requestId }, search });
await navigate({
to: '/workspaces/$workspaceId/requests/$requestId',
params: { workspaceId, requestId },
search,
});
} else {
router.navigate({ to: WorkspaceRoute.fullPath, params: { workspaceId }, search });
await navigate({ to: '/workspaces/$workspaceId', params: { workspaceId }, search });
}
},
});

View File

@@ -1,7 +1,7 @@
import type { DialogProps } from '../components/core/Dialog';
import { useDialog } from '../components/DialogContext';
import type { PromptProps } from './Prompt';
import { Prompt } from './Prompt';
import {useDialog} from "./useDialog";
type Props = Pick<DialogProps, 'title' | 'description'> &
Omit<PromptProps, 'onClose' | 'onCancel' | 'onResult'> & { id: string };

View File

@@ -3,7 +3,7 @@ import { save } from '@tauri-apps/plugin-dialog';
import mime from 'mime';
import slugify from 'slugify';
import { InlineCode } from '../components/core/InlineCode';
import { useToast } from '../components/ToastContext';
import { useToast } from './useToast';
import type { HttpResponse } from '@yaakapp-internal/models';
import { getContentTypeHeader } from '../lib/model_util';
import { getHttpRequest } from '../lib/store';

View File

@@ -1,7 +1,7 @@
import { useQueryClient } from '@tanstack/react-query';
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
import type { AnyModel } from '@yaakapp-internal/models';
import { useSetAtom } from 'jotai/index';
import { jotaiStore } from '../lib/jotai';
import { extractKeyValue } from '../lib/keyValueStore';
import { modelsEq } from '../lib/model_util';
import { useActiveWorkspace } from './useActiveWorkspace';
@@ -30,17 +30,6 @@ export function useSyncModelStores() {
const queryClient = useQueryClient();
const { wasUpdatedExternally } = useRequestUpdateKey(null);
const setSettings = useSetAtom(settingsAtom);
const setWorkspaces = useSetAtom(workspacesAtom);
const setCookieJars = useSetAtom(cookieJarsAtom);
const setFolders = useSetAtom(foldersAtom);
const setPlugins = useSetAtom(pluginsAtom);
const setHttpRequests = useSetAtom(httpRequestsAtom);
const setHttpResponses = useSetAtom(httpResponsesAtom);
const setGrpcConnections = useSetAtom(grpcConnectionsAtom);
const setGrpcRequests = useSetAtom(grpcRequestsAtom);
const setEnvironments = useSetAtom(environmentsAtom);
useListenToTauriEvent<ModelPayload>('upserted_model', ({ payload }) => {
const { model, windowLabel } = payload;
const queryKey =
@@ -63,25 +52,25 @@ export function useSyncModelStores() {
if (shouldIgnoreModel(model, windowLabel)) return;
if (model.model === 'workspace') {
setWorkspaces(updateModelList(model));
jotaiStore.set(workspacesAtom, updateModelList(model));
} else if (model.model === 'plugin') {
setPlugins(updateModelList(model));
jotaiStore.set(pluginsAtom, updateModelList(model));
} else if (model.model === 'http_request') {
setHttpRequests(updateModelList(model));
jotaiStore.set(httpRequestsAtom, updateModelList(model));
} else if (model.model === 'folder') {
setFolders(updateModelList(model));
jotaiStore.set(foldersAtom, updateModelList(model));
} else if (model.model === 'http_response') {
setHttpResponses(updateModelList(model));
jotaiStore.set(httpResponsesAtom, updateModelList(model));
} else if (model.model === 'grpc_request') {
setGrpcRequests(updateModelList(model));
jotaiStore.set(grpcRequestsAtom, updateModelList(model));
} else if (model.model === 'grpc_connection') {
setGrpcConnections(updateModelList(model));
jotaiStore.set(grpcConnectionsAtom, updateModelList(model));
} else if (model.model === 'environment') {
setEnvironments(updateModelList(model));
jotaiStore.set(environmentsAtom, updateModelList(model));
} else if (model.model === 'cookie_jar') {
setCookieJars(updateModelList(model));
jotaiStore.set(cookieJarsAtom, updateModelList(model));
} else if (model.model === 'settings') {
setSettings(model);
jotaiStore.set(settingsAtom, model);
} else if (queryKey != null) {
// TODO: Convert all models to use Jotai
queryClient.setQueryData(queryKey, (current: unknown) => {
@@ -104,27 +93,27 @@ export function useSyncModelStores() {
console.log('Delete model', payload);
if (model.model === 'workspace') {
setWorkspaces(removeModelById(model));
jotaiStore.set(workspacesAtom, removeModelById(model));
} else if (model.model === 'plugin') {
setPlugins(removeModelById(model));
jotaiStore.set(pluginsAtom, removeModelById(model));
} else if (model.model === 'http_request') {
setHttpRequests(removeModelById(model));
jotaiStore.set(httpRequestsAtom, removeModelById(model));
} else if (model.model === 'http_response') {
setHttpResponses(removeModelById(model));
jotaiStore.set(httpResponsesAtom, removeModelById(model));
} else if (model.model === 'folder') {
setFolders(removeModelById(model));
jotaiStore.set(foldersAtom, removeModelById(model));
} else if (model.model === 'environment') {
setEnvironments(removeModelById(model));
jotaiStore.set(environmentsAtom, removeModelById(model));
} else if (model.model === 'grpc_request') {
setGrpcRequests(removeModelById(model));
jotaiStore.set(grpcRequestsAtom, removeModelById(model));
} else if (model.model === 'grpc_connection') {
setGrpcConnections(removeModelById(model));
jotaiStore.set(grpcConnectionsAtom, removeModelById(model));
} else if (model.model === 'grpc_event') {
queryClient.setQueryData(grpcEventsQueryKey(model), removeModelById(model));
} else if (model.model === 'key_value') {
queryClient.setQueryData(keyValueQueryKey(model), undefined);
} else if (model.model === 'cookie_jar') {
setCookieJars(removeModelById(model));
jotaiStore.set(cookieJarsAtom, removeModelById(model));
}
});
}

View File

@@ -0,0 +1,6 @@
import { useContext } from 'react';
import { ToastContext } from '../components/ToastContext';
export function useToast() {
return useContext(ToastContext).actions;
}

View File

@@ -1,6 +1,6 @@
import { useCallback } from 'react';
import { CommandPalette } from '../components/CommandPalette';
import { useDialog } from '../components/DialogContext';
import { useDialog } from './useDialog';
export function useToggleCommandPalette() {
const dialog = useDialog();

5
src-web/lib/constants.ts Normal file
View File

@@ -0,0 +1,5 @@
export const HEADER_SIZE_MD = '27px';
export const HEADER_SIZE_LG = '38px';
export const WINDOW_CONTROLS_WIDTH = '10.5rem';

View File

@@ -1,4 +1,4 @@
import type { EditorProps } from '../components/core/Editor';
import type { EditorProps } from '../components/core/Editor/Editor';
export function languageFromContentType(
contentType: string | null,

3
src-web/lib/jotai.ts Normal file
View File

@@ -0,0 +1,3 @@
import { createStore } from 'jotai/index';
export const jotaiStore = createStore();

12
src-web/lib/router.ts Normal file
View File

@@ -0,0 +1,12 @@
// Create a new router instance
import {createRouter} from "@tanstack/react-router";
import {routeTree} from "../routeTree.gen";
export const router = createRouter({ routeTree });
// Register the router instance for type safety
declare module '@tanstack/react-router' {
interface Register {
router: typeof router;
}
}

View File

@@ -1,10 +1,10 @@
import './main.css';
import { createRouter, RouterProvider } from '@tanstack/react-router';
import { RouterProvider } from '@tanstack/react-router';
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
import { type } from '@tauri-apps/plugin-os';
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { routeTree } from './routeTree.gen';
import { router } from './lib/router';
import('react-pdf').then(({ pdfjs }) => {
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
@@ -25,18 +25,7 @@ window.addEventListener('keydown', (e) => {
if (e.key === 'Backspace' && e.target === document.body) e.preventDefault();
});
// Create a new router instance
export const router = createRouter({
routeTree,
});
// Register the router instance for type safety
declare module '@tanstack/react-router' {
interface Register {
router: typeof router;
}
}
console.log('Creating React root');
createRoot(document.getElementById('root') as HTMLElement).render(
<StrictMode>
<RouterProvider router={router} />

View File

@@ -4,7 +4,7 @@
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite dev --force",
"dev": "vite dev --force --debug hmr",
"build": "vite build",
"lint": "tsc --noEmit && eslint . --ext .ts,.tsx"
},
@@ -54,7 +54,6 @@
"react-helmet-async": "^2.0.5",
"react-markdown": "^9.0.1",
"react-pdf": "^9.1.0",
"react-router-dom": "^6.26.2",
"react-use": "^17.5.1",
"remark-gfm": "^4.0.0",
"slugify": "^1.6.6",
@@ -79,6 +78,7 @@
"@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.20",
"decompress": "^4.2.1",
"eslint-plugin-react-refresh": "^0.4.16",
"internal-ip": "^8.0.0",
"postcss": "^8.4.45",
"postcss-nesting": "^13.0.0",

View File

@@ -13,8 +13,8 @@
import { Route as rootRoute } from './routes/__root'
import { Route as IndexImport } from './routes/index'
import { Route as WorkspacesIndexImport } from './routes/workspaces/index'
import { Route as WorkspacesSettingsImport } from './routes/workspaces/settings'
import { Route as WorkspacesWorkspaceIdIndexImport } from './routes/workspaces/$workspaceId/index'
import { Route as WorkspacesWorkspaceIdSettingsImport } from './routes/workspaces/$workspaceId/settings'
import { Route as WorkspacesWorkspaceIdRequestsRequestIdImport } from './routes/workspaces/$workspaceId/requests/$requestId'
// Create/Update Routes
@@ -31,12 +31,6 @@ const WorkspacesIndexRoute = WorkspacesIndexImport.update({
getParentRoute: () => rootRoute,
} as any)
const WorkspacesSettingsRoute = WorkspacesSettingsImport.update({
id: '/workspaces/settings',
path: '/workspaces/settings',
getParentRoute: () => rootRoute,
} as any)
const WorkspacesWorkspaceIdIndexRoute = WorkspacesWorkspaceIdIndexImport.update(
{
id: '/workspaces/$workspaceId/',
@@ -45,6 +39,13 @@ const WorkspacesWorkspaceIdIndexRoute = WorkspacesWorkspaceIdIndexImport.update(
} as any,
)
const WorkspacesWorkspaceIdSettingsRoute =
WorkspacesWorkspaceIdSettingsImport.update({
id: '/workspaces/$workspaceId/settings',
path: '/workspaces/$workspaceId/settings',
getParentRoute: () => rootRoute,
} as any)
const WorkspacesWorkspaceIdRequestsRequestIdRoute =
WorkspacesWorkspaceIdRequestsRequestIdImport.update({
id: '/workspaces/$workspaceId/requests/$requestId',
@@ -63,13 +64,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof IndexImport
parentRoute: typeof rootRoute
}
'/workspaces/settings': {
id: '/workspaces/settings'
path: '/workspaces/settings'
fullPath: '/workspaces/settings'
preLoaderRoute: typeof WorkspacesSettingsImport
parentRoute: typeof rootRoute
}
'/workspaces/': {
id: '/workspaces/'
path: '/workspaces'
@@ -77,6 +71,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof WorkspacesIndexImport
parentRoute: typeof rootRoute
}
'/workspaces/$workspaceId/settings': {
id: '/workspaces/$workspaceId/settings'
path: '/workspaces/$workspaceId/settings'
fullPath: '/workspaces/$workspaceId/settings'
preLoaderRoute: typeof WorkspacesWorkspaceIdSettingsImport
parentRoute: typeof rootRoute
}
'/workspaces/$workspaceId/': {
id: '/workspaces/$workspaceId/'
path: '/workspaces/$workspaceId'
@@ -98,16 +99,16 @@ declare module '@tanstack/react-router' {
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/workspaces/settings': typeof WorkspacesSettingsRoute
'/workspaces': typeof WorkspacesIndexRoute
'/workspaces/$workspaceId/settings': typeof WorkspacesWorkspaceIdSettingsRoute
'/workspaces/$workspaceId': typeof WorkspacesWorkspaceIdIndexRoute
'/workspaces/$workspaceId/requests/$requestId': typeof WorkspacesWorkspaceIdRequestsRequestIdRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/workspaces/settings': typeof WorkspacesSettingsRoute
'/workspaces': typeof WorkspacesIndexRoute
'/workspaces/$workspaceId/settings': typeof WorkspacesWorkspaceIdSettingsRoute
'/workspaces/$workspaceId': typeof WorkspacesWorkspaceIdIndexRoute
'/workspaces/$workspaceId/requests/$requestId': typeof WorkspacesWorkspaceIdRequestsRequestIdRoute
}
@@ -115,8 +116,8 @@ export interface FileRoutesByTo {
export interface FileRoutesById {
__root__: typeof rootRoute
'/': typeof IndexRoute
'/workspaces/settings': typeof WorkspacesSettingsRoute
'/workspaces/': typeof WorkspacesIndexRoute
'/workspaces/$workspaceId/settings': typeof WorkspacesWorkspaceIdSettingsRoute
'/workspaces/$workspaceId/': typeof WorkspacesWorkspaceIdIndexRoute
'/workspaces/$workspaceId/requests/$requestId': typeof WorkspacesWorkspaceIdRequestsRequestIdRoute
}
@@ -125,22 +126,22 @@ export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths:
| '/'
| '/workspaces/settings'
| '/workspaces'
| '/workspaces/$workspaceId/settings'
| '/workspaces/$workspaceId'
| '/workspaces/$workspaceId/requests/$requestId'
fileRoutesByTo: FileRoutesByTo
to:
| '/'
| '/workspaces/settings'
| '/workspaces'
| '/workspaces/$workspaceId/settings'
| '/workspaces/$workspaceId'
| '/workspaces/$workspaceId/requests/$requestId'
id:
| '__root__'
| '/'
| '/workspaces/settings'
| '/workspaces/'
| '/workspaces/$workspaceId/settings'
| '/workspaces/$workspaceId/'
| '/workspaces/$workspaceId/requests/$requestId'
fileRoutesById: FileRoutesById
@@ -148,16 +149,16 @@ export interface FileRouteTypes {
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
WorkspacesSettingsRoute: typeof WorkspacesSettingsRoute
WorkspacesIndexRoute: typeof WorkspacesIndexRoute
WorkspacesWorkspaceIdSettingsRoute: typeof WorkspacesWorkspaceIdSettingsRoute
WorkspacesWorkspaceIdIndexRoute: typeof WorkspacesWorkspaceIdIndexRoute
WorkspacesWorkspaceIdRequestsRequestIdRoute: typeof WorkspacesWorkspaceIdRequestsRequestIdRoute
}
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
WorkspacesSettingsRoute: WorkspacesSettingsRoute,
WorkspacesIndexRoute: WorkspacesIndexRoute,
WorkspacesWorkspaceIdSettingsRoute: WorkspacesWorkspaceIdSettingsRoute,
WorkspacesWorkspaceIdIndexRoute: WorkspacesWorkspaceIdIndexRoute,
WorkspacesWorkspaceIdRequestsRequestIdRoute:
WorkspacesWorkspaceIdRequestsRequestIdRoute,
@@ -174,8 +175,8 @@ export const routeTree = rootRoute
"filePath": "__root.tsx",
"children": [
"/",
"/workspaces/settings",
"/workspaces/",
"/workspaces/$workspaceId/settings",
"/workspaces/$workspaceId/",
"/workspaces/$workspaceId/requests/$requestId"
]
@@ -183,12 +184,12 @@ export const routeTree = rootRoute
"/": {
"filePath": "index.tsx"
},
"/workspaces/settings": {
"filePath": "workspaces/settings.tsx"
},
"/workspaces/": {
"filePath": "workspaces/index.tsx"
},
"/workspaces/$workspaceId/settings": {
"filePath": "workspaces/$workspaceId/settings.tsx"
},
"/workspaces/$workspaceId/": {
"filePath": "workspaces/$workspaceId/index.tsx"
},

View File

@@ -2,15 +2,16 @@ import { QueryCache, QueryClient, QueryClientProvider } from '@tanstack/react-qu
import { createRootRoute, Outlet } from '@tanstack/react-router';
import classNames from 'classnames';
import { MotionConfig } from 'framer-motion';
import { createStore, Provider as JotaiProvider } from 'jotai';
import { Provider as JotaiProvider } from 'jotai';
import React, { Suspense } from 'react';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { HelmetProvider } from 'react-helmet-async';
import { DialogProvider, Dialogs } from '../components/DialogContext';
import { GlobalHooks } from '../components/GlobalHooks';
import { ToastProvider, Toasts } from '../components/ToastContext';
import { useOsInfo } from '../hooks/useOsInfo';
import { jotaiStore } from '../lib/jotai';
import { ToastProvider, Toasts } from '../components/Toasts';
import { DialogProvider, Dialogs } from '../components/Dialogs';
const queryClient = new QueryClient({
queryCache: new QueryCache({
@@ -53,8 +54,6 @@ export const Route = createRootRoute({
component: RouteComponent,
});
export const jotaiStore = createStore();
function RouteComponent() {
const osInfo = useOsInfo();
return (

View File

@@ -1,11 +1,12 @@
import { createFileRoute } from '@tanstack/react-router'
import Settings, { SettingsTab } from '../../components/Settings/Settings'
import Settings from '../../../components/Settings/Settings'
import { SettingsTab } from '../../../components/Settings/SettingsTab'
interface SettingsSearchSchema {
tab?: SettingsTab
}
export const Route = createFileRoute('/workspaces/settings')({
export const Route = createFileRoute('/workspaces/$workspaceId/settings')({
component: RouteComponent,
validateSearch: (search: Record<string, unknown>): SettingsSearchSchema => ({
tab: (search.tab ?? SettingsTab.General) as SettingsTab,

View File

@@ -1,3 +1,3 @@
{
"autoCodeSplitting": true
"autoCodeSplitting": false
}

View File

@@ -1,4 +1,4 @@
import { TanStackRouterVite } from '@tanstack/router-plugin/vite';
import reactRefresh from 'eslint-plugin-react-refresh';
import react from '@vitejs/plugin-react';
import { internalIpV4 } from 'internal-ip';
import { createRequire } from 'node:module';
@@ -7,6 +7,8 @@ import { defineConfig, normalizePath } from 'vite';
import { viteStaticCopy } from 'vite-plugin-static-copy';
import svgr from 'vite-plugin-svgr';
import topLevelAwait from 'vite-plugin-top-level-await';
// @ts-ignore
import { TanStackRouterVite } from '@tanstack/router-plugin/vite';
const require = createRequire(import.meta.url);
const cMapsDir = normalizePath(
@@ -21,6 +23,7 @@ const mobile = !!/android|ios/.exec(process.env.TAURI_ENV_PLATFORM ?? '');
// https://vitejs.dev/config/
export default defineConfig(async () => ({
plugins: [
reactRefresh.configs.vite,
TanStackRouterVite({
routesDirectory: './routes',
generatedRouteTree: './routeTree.gen.ts',