ahooks#useRequest TypeScript Examples

The following examples show how to use ahooks#useRequest. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: hottest-latest.tsx    From bext with MIT License 6 votes vote down vote up
HottestLatest: FC = () => {
  const { latestUpdateList, metaMap } = useMeta();

  const { data, error, loading } = useRequest(async () => {
    const result = await fetchHottest;
    if (result.code !== 200) {
      throw new Error(result?.message);
    }
    return result.data as string[];
  });

  const hottestMetas = useMemo(() => {
    if (!data?.length) {
      return [];
    }
    return take(data.map((id) => metaMap[id]).filter(Boolean), 10);
  }, [data, metaMap]);

  const renderHottest = () => {
    if (loading) {
      return <Spinner className="py-4" />;
    }
    if (error) {
      return <div className="py-4 text-center">获取失败...</div>;
    }
    return <MetaList list={hottestMetas} />;
  };

  return (
    <>
      <Title>最近热门</Title>
      {renderHottest()}
      <Title>最近更新</Title>
      <MetaList list={latestUpdateList} />
    </>
  );
}
Example #2
Source File: db.ts    From bext with MIT License 6 votes vote down vote up
[DbProvider, useDb] = constate(() => {
  const { data: ready } = useRequest(async () => {
    try {
      await initDb();
      return true;
    } catch (error) {
      return false;
    }
  });

  return { ready: !!ready };
})
Example #3
Source File: meta.tsx    From bext with MIT License 5 votes vote down vote up
MetaProvider: FC = ({ children }) => {
  const { data, loading, error } = useRequest(async () => {
    const response = await fetch(config.meta, {
      method: 'GET',
    });
    return await response.json();
  });

  const { previewId } = usePreview(true);

  const value: MetaValue | undefined = useMemo(() => {
    if (!data) {
      return undefined;
    }
    const { metas: metaList, latestUpdate } = data.meta;
    const metaMap = Object.fromEntries(
      metaList.map((meta: Meta) => [meta.id, meta]),
    );
    const latestUpdateList: any[] = take(
      latestUpdate.map((id: string) => metaMap[id]),
      3,
    );
    if (previewId) {
      latestUpdateList.unshift({
        id: 'preview',
        name: '预览脚本',
        synopsis: '当前正处于预览模式,请点击预览脚本',
      });
    }

    const { metaTag, hash } = data;
    const tagList = metaTag;
    const tagMap = Object.fromEntries(
      tagList.map((tag: Tag) => [tag.name, tag]),
    );

    return {
      metaList,
      metaMap,
      latestUpdateList,
      tagList,
      tagMap,
      prefix: `https://cdn.staticaly.com/gh/ikkz/bext-meta@${hash}/public/meta`,
      hash,
    };
  }, [data, previewId]);

  const { notify } = useNotifications();

  useEffect(() => {
    if (previewId) {
      notify({
        message:
          '当前处于预览模式,可前往首页最近更新进入预览,点击左上角图标/前往"开发"退出预览模式',
        status: 'info',
      });
    }
  }, [previewId]);

  if (loading) {
    return <PageLoading />;
  }

  if (error || !value) {
    return <div>出错了...</div>;
  }

  return <MetaContext.Provider value={value}>{children}</MetaContext.Provider>;
}
Example #4
Source File: config-install.tsx    From bext with MIT License 4 votes vote down vote up
ConfigInstall: FC<{
  onInstall: (build: string) => void;
  hide?: () => void;
}> = ({ onInstall, hide }) => {
  const { currentMeta } = useMetaDetail();

  const [formData, setFormData] = useState(
    () => currentMeta?.defaultConfig || {},
  );
  const [hasError, setHasError] = useState(false);

  const { notify } = useNotifications();

  const updateConfigCache = useMemoizedFn(() => setConfigCache(formData));

  const { run: install, loading } = useRequest(
    async (config?: any) => {
      const { id, name, version, source, defaultConfig } = currentMeta!;
      onInstall(
        await excuteCompile({
          meta: {
            id,
            name,
            version,
            source,
            defaultConfig: config ?? defaultConfig,
          },
        }),
      );
      hide?.();
    },
    {
      manual: true,
      onError: () =>
        notify({
          message: '编译失败,请点击“更多” -> “报告问题”',
          status: 'error',
        }),
      onSuccess: updateConfigCache,
    },
  );

  const [configCache, setConfigCache] = useLocalStorageState(
    bextConfigCacheKey(currentMeta?.id || 'unknown'),
  );

  const validate = useMemo(() => {
    try {
      return ajv.compile(currentMeta?.configSchema);
    } catch (error) {
      console.error(error);
    }
  }, [currentMeta?.configSchema]);

  const restoreCache = () => {
    if (validate?.(configCache)) {
      setFormData(configCache);
      notify({
        message: '已填充上次安装选项',
        status: 'success',
        dismissAfter: 1000,
      });
    } else {
      notify({
        message: '选项缓存已过期,请重新编辑配置',
        status: 'error',
      });
    }
  };

  return (
    <Dialog
      hidden={false}
      onDismiss={hide}
      dialogContentProps={{ type: DialogType.normal, title: '安装选项' }}
      minWidth={400}
      modalProps={{ layerProps: { hostId: LAYER_HOST_ID } }}
    >
      已为你填充默认选项
      {configCache ? (
        <>
          ,或者点击<Link onClick={restoreCache}>恢复之前的安装配置</Link>
        </>
      ) : null}
      ,你可以直接安装或者修改下方选项后安装。
      <Separator />
      <JsonSchemaForm
        schema={currentMeta?.configSchema}
        formData={formData}
        onChange={({ formData, errors }) => {
          setFormData(formData);
          setHasError(!!errors.length);
        }}
        omitExtraData
        liveOmit
        liveValidate
      >
        <></>
      </JsonSchemaForm>
      <DialogFooter>
        <PrimaryButton
          onClick={() => install(formData)}
          text={loading ? '处理中...' : '安装'}
          disabled={hasError || loading}
        />
        <DefaultButton onClick={hide} text="取消" />
      </DialogFooter>
    </Dialog>
  );
}
Example #5
Source File: github-login.tsx    From bext with MIT License 4 votes vote down vote up
GithubLogin: FC = () => {
  const [{ code }] = useUrlState({ code: undefined });
  const historyReplace = useHistoryReplace();
  const { notify, dismissNotification } = useNotifications();

  useRequest(
    async () => {
      const id = uniqueId('login-loading');
      notify({
        id,
        status: 'loading',
        message: '登录中',
        dismissAfter: 0,
        dismissible: false,
      });
      const response = await fetch(`/api/gh/login?code=${code}`);
      const data = await response.json();
      if (
        response.ok &&
        data.code === 200 &&
        typeof data?.data?.access_token === 'string'
      ) {
        accessToken$.next(data.data.access_token);
        notify({
          status: 'success',
          message: '登录成功',
        });
        historyReplace(location.pathname);
        trackEvent(Events.ghLoginSuccess);
      } else {
        notify({
          status: 'error',
          message:
            data?.data?.error_description || data?.msg || response.statusText,
        });
      }
      dismissNotification(id);
    },
    {
      ready: !!code,
      refreshDeps: [code],
      onError: () => notify({ status: 'error', message: '登录失败' }),
    },
  );

  const el = useObservableState(
    useObservable(() =>
      octokit$.pipe(
        switchMap((octokit) =>
          octokit
            ? user$.pipe(
                map(({ status, user }) => {
                  switch (status) {
                    case 'complete':
                      return (
                        <div className="flex justify-between">
                          <Persona
                            imageUrl={user?.avatar_url}
                            text={user?.name || user?.login}
                            secondaryText={user?.bio || '暂无签名'}
                          />
                          <Link onClick={() => accessToken$.next(undefined)}>
                            退出
                          </Link>
                        </div>
                      );
                    case 'loading':
                      return '获取用户信息...';
                    default:
                      return (
                        <>
                          发布、更新脚本请先
                          <LoginLink />
                        </>
                      );
                  }
                }),
                startWith('获取用户信息...'),
              )
            : of(
                <>
                  发布、更新脚本请先
                  <LoginLink />
                </>,
              ),
        ),
      ),
    ),
    null,
  );

  return (
    <>
      <Title>Github</Title>
      {el}
    </>
  );
}
Example #6
Source File: install-button.tsx    From bext with MIT License 4 votes vote down vote up
InstallButton: FC = () => {
  const { currentMeta, metaLoading } = useMetaDetail();
  const [configVisible, { setTrue: showConfig, setFalse: hideConfig }] =
    useBoolean(false);

  const installedChecker = useMemoizedFn((): InstallStatus => {
    const box = browserMethods.call('installed', currentMeta);
    if (box.code === 0) {
      return box.result ? 'installed' : 'notinstalled';
    }
    return 'unknown';
  });

  const [installStatus, setInstallStatus] = useState(installedChecker);

  useInterval(() => {
    setInstallStatus(installedChecker);
  }, 1000);

  const { notify } = useNotifications();

  const showUninstall =
    installStatus === 'installed' && browserMethods.support('uninstall');

  const onUninstall = () => {
    trackEvent(Events.metaUninstall, currentMeta?.id);
    console.log(
      browserMethods.call('uninstall', {
        ...currentMeta,
        author: `bext/${currentMeta?.id}`,
      }),
    );
  };

  const onInstall = (build: string) => {
    trackEvent(Events.metaInstallSuccess, currentMeta?.id);
    console.log(
      browserMethods.call('install', {
        ...currentMeta,
        author: `bext/${currentMeta?.id}`,
        build,
      }),
    );
  };

  const { run: install, loading } = useRequest(
    async () => {
      const { id, name, version, source, defaultConfig } = currentMeta!;
      onInstall(
        await excuteCompile({
          meta: { id, name, version, source, defaultConfig },
        }),
      );
    },
    {
      manual: true,
      onError: () =>
        notify({
          message: '编译失败,请点击“更多” -> “报告问题”',
          status: 'error',
        }),
    },
  );

  return showUninstall ? (
    <PrimaryButton
      className="ml-2"
      onClick={onUninstall}
      disabled={metaLoading}
    >
      卸载
    </PrimaryButton>
  ) : (
    <>
      <PrimaryButton
        className="ml-2"
        onClick={currentMeta?.configSchema ? showConfig : install}
        text={loading ? '处理中' : '安装此版本'}
        disabled={loading || metaLoading}
      />
      {configVisible ? (
        <ConfigInstall onInstall={onInstall} hide={hideConfig} />
      ) : null}
    </>
  );
}
Example #7
Source File: _layout.tsx    From bext with MIT License 4 votes vote down vote up
MetaLayout: FC = ({ children }) => {
  const params = useParams<{ id: string }>();
  const { getPreviewMeta } = usePreview();
  const { prefix } = useMeta();

  const [versions, setVersions] = useState<MetaVersion[]>();
  const [currentMeta, setCurrentMeta] = useState<Meta>();
  const [currentVersion, setCurrentVersion] = useState<string>();

  const { loading: allLoading, error: indexError } = useRequest(
    async () => {
      if (params.id === 'preview') {
        setCurrentMeta(await getPreviewMeta());
      } else {
        const response = await fetch(`${prefix}/${params.id}/_index.json`);
        const metaIndex: MetaIndex = await response.json();

        setVersions(metaIndex.versions);
        setCurrentMeta(
          Object.assign({}, metaIndex.meta, { id: String(params.id) }),
        );
        setCurrentVersion(metaIndex.versions[0].hash);
      }
    },
    {
      refreshDeps: [params.id, getPreviewMeta],
    },
  );

  const {
    loading: metaLoading,
    run: setVersion,
    error: metaError,
  } = useRequest(
    async (hash: string) => {
      setCurrentVersion(hash);
      const response = await fetch(`${prefix}/${params.id}/${hash}.json`);
      setCurrentMeta({ ...(await response.json()), id: String(params.id) });
    },
    { manual: true, refreshDeps: [params.id] },
  );

  const { notify } = useNotifications();

  useEventListener('message', ({ data }) => {
    if (
      data?.type === 'bext/unknown-install' &&
      typeof data?.payload?.build === 'string'
    ) {
      copy(data.payload.build);
      notify({
        message: '当前浏览器不支持安装,已将脚本内容复制到剪切板',
        status: 'info',
      });
    }
  });

  if (allLoading) {
    return <Spinner size={SpinnerSize.large} className="w-full h-full" />;
  }

  if (indexError || metaError) {
    return (
      <div className="w-full h-full flex flex-col items-center justify-center">
        出错了...
      </div>
    );
  }

  return (
    <MetaDetailContext.Provider
      value={{
        versions,
        currentVersion,
        setVersion,
        currentMeta,
        allLoading,
        metaLoading,
        isPreview: params.id === 'preview',
      }}
    >
      {children}
    </MetaDetailContext.Provider>
  );
}
Example #8
Source File: dev.tsx    From bext with MIT License 4 votes vote down vote up
DevScreen: FC = () => {
  const [loading, setLoading] = useState(true);
  const { params } = useRoute<any>();
  const { id, modify } = params || {};
  const { data: draft, mutate } = useRequest(
    async () => {
      try {
        return await getDraft(id);
      } catch (error) {}
    },
    {
      ready: !!id,
    },
  );
  useUpdateEffect(() => {
    if (draft?.id) {
      updateUrl(draft.id, draft.url || '');
    }
  }, [draft?.url, draft?.id]);

  const navigation = useNavigation();
  useLayoutEffect(() => {
    navigation.setOptions({
      headerRight: () => (loading ? <Text>加载中...</Text> : null),
      ...(draft?.name ? { title: draft.name } : null),
    });
  }, [navigation, loading, draft?.name]);
  const [modalVisible, setModalVisible] = useState(false);
  const { script } = useDebug();
  const webView = useRef<WebView>(null);

  const onMessage = (msg: string = '{}') => {
    try {
      const data = JSON.parse(msg);
      switch (data.type) {
        case 'save':
          updateJson(id, JSON.stringify(data.payload));
          break;
        case 'ready':
          webView.current?.injectJavaScript(`
            if (window.injectDraft) {
              window.injectDraft(decodeURIComponent("${encodeURIComponent(
                draft?.json || '{}',
              )}"));
            }
            true;
          `);
          break;
        case 'debug':
          setModalVisible(true);
          script.current = data.payload;
          break;
        default:
          break;
      }
    } catch (error) {}
  };

  const navigateToDebug = () => {
    navigation.navigate(
      'debug' as never,
      {
        url: draft?.url,
      } as never,
    );
    setModalVisible(false);
  };

  return (
    <>
      <Overlay
        transparent
        isVisible={modalVisible}
        onBackdropPress={() => setModalVisible(false)}
        overlayStyle={styles.overlay}
      >
        <Input
          label="输入窗口链接"
          value={draft?.url}
          onChangeText={(url) => mutate((old) => ({ ...old, url } as any))}
        />
        <Button title="确定" disabled={!draft?.url} onPress={navigateToDebug} />
      </Overlay>
      <WebView
        ref={webView}
        originWhitelist={['*']}
        source={{
          uri: `${BEXT_ORIGIN}${
            modify
              ? '/meta?from=dev&devPath=%2Fdev%2Fscript-m'
              : '/dev/script-m'
          }`,
        }}
        onLoad={() => setLoading(false)}
        onMessage={(e) => onMessage(e.nativeEvent.data)}
      />
    </>
  );
}
Example #9
Source File: home.tsx    From bext with MIT License 4 votes vote down vote up
HomeScreen: FC = () => {
  const navigation = useNavigation();
  const [modalVisible, setModalVisible] = useState(false);
  useLayoutEffect(() => {
    navigation.setOptions({
      headerRight: () => (
        <Icon name="add" onPress={() => setModalVisible(true)} />
      ),
    });
  }, [navigation]);
  const db = useDb();
  const { loading, data, run } = useRequest(getDrafts, {
    ready: db.ready,
  });
  const [name, setName] = useState('');
  useFocusEffect(useMemoizedFn(run));

  const onDelete = useMemoizedFn(async (id: number) => {
    await deleteDraft(id);
    run();
  });

  const onPress = useMemoizedFn((id: number) => {
    navigation.navigate(
      'dev' as never,
      {
        id,
      } as never,
    );
  });

  const createDraft = useMemoizedFn(async (empty: boolean) => {
    const id = await addDraft(name);
    setName('');
    run();
    setModalVisible(false);
    if (id) {
      navigation.navigate(
        'dev' as never,
        {
          id,
          modify: empty ? undefined : true,
        } as never,
      );
    }
  });

  return (
    <View style={{ flex: 1 }}>
      <Overlay
        transparent
        isVisible={modalVisible}
        onBackdropPress={() => setModalVisible(false)}
        overlayStyle={styles.overlay}
      >
        <Input
          label="输入草稿名称(不是脚本名称)"
          value={name}
          onChangeText={setName}
        />
        <View style={styles.buttons}>
          <Button
            title="创建空白草稿"
            disabled={!name.length}
            onPress={() => createDraft(true)}
            containerStyle={styles.button}
          />
          <View style={styles.space} />
          <Button
            title="从现有脚本创建"
            disabled={!name.length}
            onPress={() => createDraft(false)}
            containerStyle={styles.button}
          />
        </View>
      </Overlay>
      <FlatList
        data={data || []}
        keyExtractor={(item) => String(item.id)}
        renderItem={({ item }) => (
          <DraftItem
            draft={item}
            onDelete={() => onDelete(item.id)}
            onPress={() => onPress(item.id)}
          />
        )}
        onRefresh={run}
        refreshing={loading}
      />
    </View>
  );
}