+
{props.item.title}
@@ -72,13 +80,11 @@ export function ConnectionItem(props: ConnectionItemProps) {
{props.item.clusterName}
-
+
props.theme.colors.primary.lighter};
+ background: ${props => props.theme.colors.primary.light};
padding: 9px;
width: 30px;
height: 30px;
diff --git a/packages/teleterm/src/ui/TopBar/Identity/EmptyIdentityList/EmptyIdentityList.tsx b/packages/teleterm/src/ui/TopBar/Identity/EmptyIdentityList/EmptyIdentityList.tsx
index 5a4ab8624..7a26c6bf1 100644
--- a/packages/teleterm/src/ui/TopBar/Identity/EmptyIdentityList/EmptyIdentityList.tsx
+++ b/packages/teleterm/src/ui/TopBar/Identity/EmptyIdentityList/EmptyIdentityList.tsx
@@ -1,16 +1,13 @@
import React from 'react';
import { ButtonPrimary, Flex, Text } from 'design';
-import { useAppContext } from 'teleterm/ui/appContextProvider';
import Image from 'design/Image';
import clusterPng from './clusters.png';
-export function EmptyIdentityList() {
- const ctx = useAppContext();
-
- function handleConnect() {
- ctx.commandLauncher.executeCommand('cluster-connect', {});
- }
+interface EmptyIdentityListProps {
+ onConnect(): void;
+}
+export function EmptyIdentityList(props: EmptyIdentityListProps) {
return (
Lorem ipsum dolor sit amet, consectetur adipiscing elit
-
+
Connect
diff --git a/packages/teleterm/src/ui/TopBar/Identity/Identity.tsx b/packages/teleterm/src/ui/TopBar/Identity/Identity.tsx
index 2e587e466..32f2404e0 100644
--- a/packages/teleterm/src/ui/TopBar/Identity/Identity.tsx
+++ b/packages/teleterm/src/ui/TopBar/Identity/Identity.tsx
@@ -23,6 +23,15 @@ export function Identity() {
setIsPopoverOpened(wasOpened => !wasOpened);
}, [setIsPopoverOpened]);
+ function withClose any>(
+ fn: T
+ ): (...args: Parameters) => ReturnType {
+ return (...args) => {
+ setIsPopoverOpened(false);
+ return fn(...args);
+ };
+ }
+
useKeyboardShortcuts(
useMemo(
() => ({
@@ -55,12 +64,12 @@ export function Identity() {
) : (
-
+
)}
@@ -69,5 +78,5 @@ export function Identity() {
}
const Container = styled(Box)`
- background: ${props => props.theme.colors.primary.dark};
+ background: ${props => props.theme.colors.primary.light};
`;
diff --git a/packages/teleterm/src/ui/TopBar/Identity/IdentityList/AddNewClusterItem.tsx b/packages/teleterm/src/ui/TopBar/Identity/IdentityList/AddNewClusterItem.tsx
index 70308583c..6032a703b 100644
--- a/packages/teleterm/src/ui/TopBar/Identity/IdentityList/AddNewClusterItem.tsx
+++ b/packages/teleterm/src/ui/TopBar/Identity/IdentityList/AddNewClusterItem.tsx
@@ -25,6 +25,8 @@ export function AddNewClusterItem(props: AddNewClusterItemProps) {
}
const StyledListItem = styled(ListItem)`
+ border-radius: 0;
+ height: 38px;
justify-content: center;
color: ${props => props.theme.colors.text.secondary};
`;
diff --git a/packages/teleterm/src/ui/TopBar/Identity/IdentityList/IdentityList.tsx b/packages/teleterm/src/ui/TopBar/Identity/IdentityList/IdentityList.tsx
index 460a616a3..8c72555ff 100644
--- a/packages/teleterm/src/ui/TopBar/Identity/IdentityList/IdentityList.tsx
+++ b/packages/teleterm/src/ui/TopBar/Identity/IdentityList/IdentityList.tsx
@@ -20,10 +20,10 @@ interface IdentityListProps {
export function IdentityList(props: IdentityListProps) {
return (
-
+
{props.loggedInUser && (
<>
-
+
{props.loggedInUser.name}
@@ -36,7 +36,7 @@ export function IdentityList(props: IdentityListProps) {
)}
{focusGrabber}
-
+
{props.clusters.map((i, index) => (
-
+
props.theme.colors.primary.lighter};
- margin: 0 16px;
height: 1px;
`;
diff --git a/packages/teleterm/src/ui/TopBar/Identity/IdentityList/IdentityListItem.tsx b/packages/teleterm/src/ui/TopBar/Identity/IdentityList/IdentityListItem.tsx
index 1f3a7fddc..d861f88c8 100644
--- a/packages/teleterm/src/ui/TopBar/Identity/IdentityList/IdentityListItem.tsx
+++ b/packages/teleterm/src/ui/TopBar/Identity/IdentityList/IdentityListItem.tsx
@@ -32,6 +32,7 @@ export function IdentityListItem(props: IdentityListItemProps) {
return (
props.theme.colors.primary.main};
display: grid;
grid-template-columns: 1fr 4fr 2fr;
width: 100%;
diff --git a/packages/teleterm/src/ui/appContext.ts b/packages/teleterm/src/ui/appContext.ts
index 4e95accdf..1560658ff 100644
--- a/packages/teleterm/src/ui/appContext.ts
+++ b/packages/teleterm/src/ui/appContext.ts
@@ -55,7 +55,7 @@ export default class AppContext {
this.modalsService,
this.clustersService,
this.notificationsService,
- this.statePersistenceService,
+ this.statePersistenceService
);
this.terminalsService = new TerminalsService(ptyServiceClient);
@@ -81,9 +81,6 @@ export default class AppContext {
async init(): Promise {
await this.clustersService.syncRootClusters();
- const { rootClusterUri } = this.statePersistenceService.getWorkspaces();
- if (rootClusterUri) {
- this.workspacesService.setActiveWorkspace(rootClusterUri);
- }
+ this.workspacesService.restorePersistedState();
}
}
diff --git a/packages/teleterm/src/ui/components/FilterableList/FilterableList.tsx b/packages/teleterm/src/ui/components/FilterableList/FilterableList.tsx
index 33107dd19..84c879636 100644
--- a/packages/teleterm/src/ui/components/FilterableList/FilterableList.tsx
+++ b/packages/teleterm/src/ui/components/FilterableList/FilterableList.tsx
@@ -66,22 +66,22 @@ const DarkInput = styled(Input)`
background: inherit;
border: 1px ${props => props.theme.colors.action.disabledBackground} solid;
border-radius: 51px;
- color: ${props => props.theme.colors.light};
+ color: ${props => props.theme.colors.text.primary};
margin-bottom: 10px;
font-size: 14px;
- opacity: 0.6;
height: 34px;
+ transition : border 300ms ease-out;
::placeholder {
opacity: 1;
+ color: ${props => props.theme.colors.text.secondary};
}
- & :hover {
- border-color: ${props => props.theme.colors.action.active};
+ &:hover {
+ border-color: ${props => props.theme.colors.text.secondary};
}
&:focus {
border-color: ${props => props.theme.colors.secondary.main};
- opacity: 1;
}
`;
diff --git a/packages/teleterm/src/ui/components/Notifcations/Notification.tsx b/packages/teleterm/src/ui/components/Notifcations/Notification.tsx
index a2450e030..909af2c26 100644
--- a/packages/teleterm/src/ui/components/Notifcations/Notification.tsx
+++ b/packages/teleterm/src/ui/components/Notifcations/Notification.tsx
@@ -111,7 +111,7 @@ function getRenderedContent(
if (typeof content === 'string') {
return (
-
+
props.theme.colors.primary.darker};
+ background: ${props => props.theme.colors.primary.light};
min-height: 40px;
width: 320px;
margin-bottom: 15px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.24);
color: ${props => props.theme.colors.text.primary};
- opacity: 0.95;
border-radius: 4px;
-
- &:hover {
- opacity: 1;
- cursor: pointer;
- }
+ cursor: pointer;
`;
diff --git a/packages/teleterm/src/ui/components/Notifcations/Notifications.tsx b/packages/teleterm/src/ui/components/Notifcations/Notifications.tsx
index 3601ae544..5d4f2c900 100644
--- a/packages/teleterm/src/ui/components/Notifcations/Notifications.tsx
+++ b/packages/teleterm/src/ui/components/Notifcations/Notifications.tsx
@@ -27,4 +27,5 @@ const Container = styled.div`
position: fixed;
bottom: 12px;
right: 12px;
+ z-index: 10;
`;
diff --git a/packages/teleterm/src/ui/components/Table.tsx b/packages/teleterm/src/ui/components/Table.tsx
new file mode 100644
index 000000000..963e34832
--- /dev/null
+++ b/packages/teleterm/src/ui/components/Table.tsx
@@ -0,0 +1,30 @@
+import React from 'react';
+import { ThemeProvider, useTheme } from 'styled-components';
+import DesignTable from 'design/DataTable/Table';
+
+export const Table: typeof DesignTable = props => {
+ const theme = useTheme();
+
+ return (
+
+
+
+ );
+};
+
+function getTableTheme(theme) {
+ return {
+ ...theme,
+ colors: {
+ ...theme.colors,
+ primary: {
+ ...theme.colors.primary,
+ dark: 'rgba(255, 255, 255, 0.05)',
+ light: theme.colors.primary.dark,
+ lighter: theme.colors.primary.light,
+ main: theme.colors.primary.darker,
+ },
+ link: theme.colors.text.primary,
+ },
+ };
+}
diff --git a/packages/teleterm/src/ui/services/clusters/clustersService.test.ts b/packages/teleterm/src/ui/services/clusters/clustersService.test.ts
index 02c2bde39..a3ac86d22 100644
--- a/packages/teleterm/src/ui/services/clusters/clustersService.test.ts
+++ b/packages/teleterm/src/ui/services/clusters/clustersService.test.ts
@@ -22,6 +22,7 @@ const gatewayMock: tsh.Gateway = {
localPort: '2000',
protocol: 'https',
targetName: 'Test',
+ targetSubresourceName: '',
targetUser: '',
targetUri: 'clusters/xxx/',
cliCommand: 'psql postgres://postgres@localhost:5432/postgres',
diff --git a/packages/teleterm/src/ui/services/clusters/clustersService.ts b/packages/teleterm/src/ui/services/clusters/clustersService.ts
index 85f21c853..50b3b5135 100644
--- a/packages/teleterm/src/ui/services/clusters/clustersService.ts
+++ b/packages/teleterm/src/ui/services/clusters/clustersService.ts
@@ -278,12 +278,28 @@ export class ClustersService extends ImmutableStore {
}
}
+ /**
+ * Removes cluster and its leaf clusters (if any)
+ */
async removeCluster(clusterUri: string) {
await this.client.removeCluster(clusterUri);
+ const leafClustersUris = this.getClusters()
+ .filter(
+ item =>
+ item.leaf && routing.ensureRootClusterUri(item.uri) === clusterUri
+ )
+ .map(cluster => cluster.uri);
this.setState(draft => {
draft.clusters.delete(clusterUri);
+ leafClustersUris.forEach(leafClusterUri => {
+ draft.clusters.delete(leafClusterUri);
+ });
});
+
this.removeResources(clusterUri);
+ leafClustersUris.forEach(leafClusterUri => {
+ this.removeResources(leafClusterUri);
+ });
}
async getAuthSettings(clusterUri: string) {
@@ -413,6 +429,10 @@ export class ClustersService extends ImmutableStore {
return [...this.state.dbs.values()];
}
+ async getDbUsers(dbUri: string): Promise {
+ return await this.client.listDatabaseUsers(dbUri);
+ }
+
useState() {
return useStore(this).state;
}
diff --git a/packages/teleterm/src/ui/services/connectionTracker/connectionTrackerService.ts b/packages/teleterm/src/ui/services/connectionTracker/connectionTrackerService.ts
index 93e00c378..c669f2bc8 100644
--- a/packages/teleterm/src/ui/services/connectionTracker/connectionTrackerService.ts
+++ b/packages/teleterm/src/ui/services/connectionTracker/connectionTrackerService.ts
@@ -102,6 +102,12 @@ export class ConnectionTrackerService extends ImmutableStore {
this.setState(draft => {
+ // filter out connections from removed clusters
+ draft.connections = draft.connections.filter(i => {
+ const uri = i.kind === 'connection.gateway' ? i.targetUri : i.serverUri;
+ return !!this._clusterService.findClusterByResource(uri);
+ });
+
// assign default "connected" values
draft.connections.forEach(i => {
if (i.kind === 'connection.gateway') {
@@ -134,6 +140,9 @@ export class ConnectionTrackerService extends ImmutableStore('state');
- if (restored) {
- this.state = restored;
- }
+ saveConnectionTrackerState(connectionTracker: ConnectionTrackerState): void {
+ const newState: StatePersistenceState = {
+ ...this.getState(),
+ connectionTracker,
+ };
+ this.putState(newState);
}
- saveConnectionTrackerState(navigatorState: ConnectionTrackerState): void {
- this.state.connectionTracker = navigatorState;
- this._fileStorage.put('state', this.state);
+ getConnectionTrackerState(): ConnectionTrackerState {
+ return this.getState().connectionTracker;
}
- getConnectionTrackerState(): ConnectionTrackerState {
- return this.state.connectionTracker;
+ saveWorkspacesState(workspacesState: WorkspacesState): void {
+ const newState: StatePersistenceState = {
+ ...this.getState(),
+ workspacesState,
+ };
+ this.putState(newState);
+ }
+
+ getWorkspacesState(): WorkspacesState {
+ return this.getState().workspacesState;
}
- saveWorkspaces(workspacesState: WorkspacesState): void {
- this.state.workspacesState.rootClusterUri = workspacesState.rootClusterUri;
- for (let w in workspacesState.workspaces) {
- if (workspacesState.workspaces[w]) {
- this.state.workspacesState.workspaces[w] = {
- location: workspacesState.workspaces[w].location,
- localClusterUri: workspacesState.workspaces[w].localClusterUri,
- documents: workspacesState.workspaces[w].documents,
- };
- }
- }
- this._fileStorage.put('state', this.state);
+ private getState(): StatePersistenceState {
+ const defaultState: StatePersistenceState = {
+ connectionTracker: {
+ connections: [],
+ },
+ workspacesState: {
+ workspaces: {},
+ },
+ };
+ return this._fileStorage.get('state') || defaultState;
}
- getWorkspaces(): WorkspacesState {
- return this.state.workspacesState;
+ private putState(state: StatePersistenceState): void {
+ this._fileStorage.put('state', state);
}
}
diff --git a/packages/teleterm/src/ui/services/workspacesService/documentsService/documentsService.test.ts b/packages/teleterm/src/ui/services/workspacesService/documentsService/documentsService.test.ts
index 3532a8d03..514ea2ab8 100644
--- a/packages/teleterm/src/ui/services/workspacesService/documentsService/documentsService.test.ts
+++ b/packages/teleterm/src/ui/services/workspacesService/documentsService/documentsService.test.ts
@@ -58,6 +58,7 @@ describe('document should be added', () => {
kind: 'doc.gateway',
gatewayUri: '',
targetUri: '',
+ targetName: '',
targetUser: 'foo',
};
@@ -129,6 +130,7 @@ test('only gateway documents should be returned', () => {
title: 'gw',
gatewayUri: '',
targetUri: '',
+ targetName: '',
targetUser: 'foo',
};
diff --git a/packages/teleterm/src/ui/services/workspacesService/documentsService/documentsService.ts b/packages/teleterm/src/ui/services/workspacesService/documentsService/documentsService.ts
index a956c2e77..fb99f8f3c 100644
--- a/packages/teleterm/src/ui/services/workspacesService/documentsService/documentsService.ts
+++ b/packages/teleterm/src/ui/services/workspacesService/documentsService/documentsService.ts
@@ -88,15 +88,39 @@ export class DocumentsService {
};
}
+ /**
+ * If title is not present in opts, createGatewayDocument will create one based on opts.
+ */
createGatewayDocument(opts: CreateGatewayDocumentOpts): DocumentGateway {
- const { targetUri, title, targetUser } = opts;
+ const {
+ targetUri,
+ targetUser,
+ targetName,
+ targetSubresourceName,
+ port,
+ gatewayUri,
+ } = opts;
const uri = routing.getDocUri({ docId: unique() });
+ let title = opts.title;
+
+ if (!title) {
+ title = `${targetUser}@${targetName}`;
+
+ if (targetSubresourceName) {
+ title += `/${targetSubresourceName}`;
+ }
+ }
+
return {
uri,
kind: 'doc.gateway',
targetUri,
targetUser,
+ targetName,
+ targetSubresourceName,
+ gatewayUri,
title,
+ port,
};
}
diff --git a/packages/teleterm/src/ui/services/workspacesService/documentsService/types.ts b/packages/teleterm/src/ui/services/workspacesService/documentsService/types.ts
index d6c407a4d..626b28c8d 100644
--- a/packages/teleterm/src/ui/services/workspacesService/documentsService/types.ts
+++ b/packages/teleterm/src/ui/services/workspacesService/documentsService/types.ts
@@ -56,6 +56,8 @@ export interface DocumentGateway extends DocumentBase {
gatewayUri?: string;
targetUri: string;
targetUser: string;
+ targetName: string;
+ targetSubresourceName?: string;
port?: string;
}
@@ -84,8 +86,10 @@ export type Document =
export type CreateGatewayDocumentOpts = {
gatewayUri?: string;
targetUri: string;
+ targetName: string;
targetUser: string;
- title: string;
+ targetSubresourceName?: string;
+ title?: string;
port?: string;
};
diff --git a/packages/teleterm/src/ui/services/workspacesService/workspacesService.ts b/packages/teleterm/src/ui/services/workspacesService/workspacesService.ts
index 65c4c8b3e..166cd8824 100644
--- a/packages/teleterm/src/ui/services/workspacesService/workspacesService.ts
+++ b/packages/teleterm/src/ui/services/workspacesService/workspacesService.ts
@@ -6,6 +6,7 @@ import { ClustersService } from 'teleterm/ui/services/clusters';
import { StatePersistenceService } from 'teleterm/ui/services/statePersistence';
import { isEqual } from 'lodash';
import { NotificationsService } from 'teleterm/ui/services/notifications';
+import { routing } from 'teleterm/ui/uri';
export interface WorkspacesState {
rootClusterUri?: string;
@@ -107,36 +108,29 @@ export class WorkspacesService extends ImmutableStore {
setState(nextState: (draftState: WorkspacesState) => WorkspacesState | void) {
super.setState(nextState);
- this.statePersistenceService.saveWorkspaces(this.state);
+ this.persistState();
}
setActiveWorkspace(clusterUri: string): Promise {
const setWorkspace = () => {
this.setState(draftState => {
- // clusterUri can be undefined - we don't want to create a workspace for it
- if (clusterUri && !draftState.workspaces[clusterUri]) {
- const persistedWorkspace =
- this.statePersistenceService.getWorkspaces().workspaces[clusterUri];
- const defaultDocument = this.getWorkspaceDocumentService(
- clusterUri
- ).createClusterDocument({ clusterUri });
-
- draftState.workspaces[clusterUri] = {
- localClusterUri: persistedWorkspace?.localClusterUri || clusterUri,
- location: defaultDocument.uri,
- documents: [defaultDocument],
- previous: persistedWorkspace?.documents
- ? {
- documents: persistedWorkspace.documents,
- location: persistedWorkspace.location,
- }
- : undefined,
- };
+ // adding a new workspace
+ if (!draftState.workspaces[clusterUri]) {
+ draftState.workspaces[clusterUri] =
+ this.getWorkspaceDefaultState(clusterUri);
}
draftState.rootClusterUri = clusterUri;
});
};
+ // empty cluster URI - no cluster selected
+ if (!clusterUri) {
+ this.setState(draftState => {
+ draftState.rootClusterUri = undefined;
+ });
+ return Promise.resolve();
+ }
+
const cluster = this.clustersService.findCluster(clusterUri);
if (!cluster) {
this.notificationsService.notifyError({
@@ -167,7 +161,7 @@ export class WorkspacesService extends ImmutableStore {
})
.then(() => {
return new Promise(resolve => {
- if (!this.canReopenPreviousDocuments(this.getWorkspace(clusterUri))) {
+ if (!this.getWorkspace(clusterUri)?.previous) {
return resolve();
}
this.modalsService.openDocumentsReopenDialog({
@@ -197,6 +191,41 @@ export class WorkspacesService extends ImmutableStore {
);
}
+ restorePersistedState(): void {
+ const persistedState = this.statePersistenceService.getWorkspacesState();
+ const restoredWorkspaces = this.clustersService
+ .getClusters()
+ .reduce((workspaces, cluster) => {
+ const persistedWorkspace = persistedState.workspaces[cluster.uri];
+ const workspaceDefaultState = this.getWorkspaceDefaultState(
+ persistedWorkspace?.localClusterUri || cluster.uri
+ );
+ const persistedWorkspaceDocuments = persistedWorkspace.documents;
+
+ workspaces[cluster.uri] = {
+ ...workspaceDefaultState,
+ previous: this.canReopenPreviousDocuments({
+ previousDocuments: persistedWorkspaceDocuments,
+ currentDocuments: workspaceDefaultState.documents,
+ })
+ ? {
+ location: persistedWorkspace.location,
+ documents: persistedWorkspaceDocuments,
+ }
+ : undefined,
+ };
+ return workspaces;
+ }, {});
+
+ this.setState(draftState => {
+ draftState.workspaces = restoredWorkspaces;
+ });
+
+ if (persistedState.rootClusterUri) {
+ this.setActiveWorkspace(persistedState.rootClusterUri);
+ }
+ }
+
private reopenPreviousDocuments(clusterUri: string): void {
this.setState(draftState => {
const workspace = draftState.workspaces[clusterUri];
@@ -213,17 +242,47 @@ export class WorkspacesService extends ImmutableStore {
});
}
- private canReopenPreviousDocuments(workspace: Workspace): boolean {
+ private canReopenPreviousDocuments({
+ previousDocuments,
+ currentDocuments,
+ }: {
+ previousDocuments: Document[];
+ currentDocuments: Document[];
+ }): boolean {
const removeUri = (documents: Document[]) =>
documents.map(d => ({ ...d, uri: undefined }));
return (
- workspace.previous &&
- workspace.previous.documents?.length &&
- !isEqual(
- removeUri(workspace.previous.documents),
- removeUri(workspace.documents)
- )
+ previousDocuments?.length &&
+ !isEqual(removeUri(previousDocuments), removeUri(currentDocuments))
);
}
+
+ private getWorkspaceDefaultState(localClusterUri: string): Workspace {
+ const rootClusterUri = routing.ensureRootClusterUri(localClusterUri);
+ const defaultDocument = this.getWorkspaceDocumentService(
+ rootClusterUri
+ ).createClusterDocument({ clusterUri: localClusterUri });
+ return {
+ localClusterUri,
+ location: defaultDocument.uri,
+ documents: [defaultDocument],
+ };
+ }
+
+ private persistState(): void {
+ const stateToSave: WorkspacesState = {
+ rootClusterUri: this.state.rootClusterUri,
+ workspaces: {},
+ };
+ for (let w in this.state.workspaces) {
+ const workspace = this.state.workspaces[w];
+ stateToSave.workspaces[w] = {
+ localClusterUri: workspace.localClusterUri,
+ location: workspace.previous?.location || workspace.location,
+ documents: workspace.previous?.documents || workspace.documents,
+ };
+ }
+ this.statePersistenceService.saveWorkspacesState(stateToSave);
+ }
}
diff --git a/packages/teleterm/src/ui/useAsync.ts b/packages/teleterm/src/ui/useAsync.ts
deleted file mode 100644
index 1f753c564..000000000
--- a/packages/teleterm/src/ui/useAsync.ts
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
-Copyright 2019 Gravitational, Inc.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-/* eslint-disable @typescript-eslint/ban-types */
-
-import React from 'react';
-
-export default function useAsync(cb?: AsyncCb) {
- const [state, setState] = React.useState>(() => ({
- data: null,
- status: '',
- statusText: '',
- }));
-
- const run = async (...p: Parameters>) => {
- try {
- setState({
- ...state,
- status: 'processing',
- });
-
- const data = (await cb.call(null, ...p)) as R;
-
- setState({
- ...state,
- status: 'success',
- data,
- });
-
- return [data, null] as [R, Error];
- } catch (err) {
- setState({
- ...state,
- status: 'error',
- statusText: err.message,
- data: null,
- });
-
- return [null, err] as [R, Error];
- }
- };
-
- function setAttempt(attempt: Attempt) {
- setState(attempt);
- }
-
- return [state, run, setAttempt] as const;
-}
-
-export type Attempt = {
- data?: T;
- status: 'processing' | 'success' | 'error' | '';
- statusText: string;
-};
-
-type IsValidArg = T extends object
- ? keyof T extends never
- ? false
- : true
- : true;
-
-type AsyncCb = T extends (...args: any[]) => Promise
- ? T
- : T extends (
- a: infer A,
- b: infer B,
- c: infer C,
- d: infer D,
- e: infer E,
- f: infer F,
- g: infer G,
- h: infer H,
- i: infer I,
- j: infer J
- ) => Promise
- ? IsValidArg extends true
- ? (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J) => Promise
- : IsValidArg extends true
- ? (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I) => Promise
- : IsValidArg extends true
- ? (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H) => Promise
- : IsValidArg extends true
- ? (a: A, b: B, c: C, d: D, e: E, f: F, g: G) => Promise
- : IsValidArg extends true
- ? (a: A, b: B, c: C, d: D, e: E, f: F) => Promise
- : IsValidArg extends true
- ? (a: A, b: B, c: C, d: D, e: E) => Promise
- : IsValidArg extends true
- ? (a: A, b: B, c: C, d: D) => Promise
- : IsValidArg extends true
- ? (a: A, b: B, c: C) => Promise
- : IsValidArg extends true
- ? (a: A, b: B) => Promise
- : IsValidArg extends true
- ? (a: A) => Promise
- : () => Promise
- : never;