refactor: move AddWorkspace form into hooks

This commit is contained in:
tiddlygit-test 2021-03-31 23:00:14 +08:00
parent 52fa8943e4
commit 67a70f2829
16 changed files with 264 additions and 228 deletions

View file

@ -0,0 +1,40 @@
import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
import { Paper, Typography } from '@material-ui/core';
import SearchRepo from '@/components/github/SearchRepo';
import { GithubTokenForm } from '../github/git-token-form';
const SyncContainer = styled(Paper)`
margin-top: 5px;
`;
const GithubRepoLink = styled(Typography)`
cursor: pointer;
opacity: 50%;
&:hover {
opacity: 100%;
}
`;
export function TokenForm(): JSX.Element {
return (
<SyncContainer elevation={2} square>
<GithubTokenForm>
{githubWikiUrl?.length > 0 ? (
<GithubRepoLink onClick={async () => await window.service.native.open(githubWikiUrl)} variant="subtitle2" align="center">
({githubWikiUrl})
</GithubRepoLink>
) : undefined}
<SearchRepo
githubWikiUrl={githubWikiUrl}
accessToken={accessToken}
githubWikiUrlSetter={githubWikiUrlSetter}
userInfo={userInfo}
currentTab={currentTab}
wikiFolderNameSetter={wikiFolderNameSetter}
isCreateMainWorkspace={isCreateMainWorkspace}
/>
</GithubTokenForm>
</SyncContainer>
)
}

View file

@ -16,6 +16,18 @@ export const getGithubToken = async (): Promise<string | undefined> => await win
export function GithubTokenForm(props: { children?: JSX.Element | Array<JSX.Element | undefined | string> }): JSX.Element {
const { children } = props;
const { t } = useTranslation();
useEffect(() => {
// on startup, loading the cachedGithubToken
if (accessToken === undefined && cachedGithubToken !== undefined) {
graphqlClient.setHeader('Authorization', `bearer ${cachedGithubToken}`);
accessTokenSetter(cachedGithubToken);
} else if (accessToken !== undefined && accessToken !== cachedGithubToken) {
// if user or login button changed the token, we use latest token
Object.keys(graphqlClient.headers).map((key) => graphqlClient.removeHeader(key));
accessTokenSetter(accessToken);
void setGithubToken(accessToken);
}
}, [cachedGithubToken, accessToken]);
const userInfo = useUserInfoObservable();
if (userInfo === undefined) {
return <div>Loading...</div>;

View file

@ -0,0 +1,49 @@
import React from 'react';
import { invert } from 'lodash';
import { useTranslation } from 'react-i18next';
import { Paper, AppBar, Tabs, Tab } from '@material-ui/core';
export enum CreateWorkspaceTabs {
CloneOnlineWiki = 'CloneOnlineWiki',
CreateNewWiki = 'CreateNewWiki',
OpenLocalWiki = 'OpenLocalWiki',
}
const a11yProps = (
index: CreateWorkspaceTabs,
): {
id: string;
'aria-controls': string;
} => ({
id: index,
'aria-controls': `simple-tabpanel-${index}`,
});
const tabIndexMap = {
[CreateWorkspaceTabs.CloneOnlineWiki]: 0,
[CreateWorkspaceTabs.CreateNewWiki]: 1,
[CreateWorkspaceTabs.OpenLocalWiki]: 2,
};
export interface IProps {
currentTab: CreateWorkspaceTabs;
currentTabSetter: (id: CreateWorkspaceTabs) => void;
}
export function TabBar({ currentTab, currentTabSetter }: IProps): JSX.Element {
const { t } = useTranslation();
return (
<AppBar position="static">
<Paper square>
<Tabs
value={tabIndexMap[currentTab]}
onChange={(_event, newValue: number) => currentTabSetter((invert(tabIndexMap) as Record<number, CreateWorkspaceTabs>)[newValue])}
aria-label={t('AddWorkspace.SwitchCreateNewOrOpenExisted')}>
<Tab label={t(`AddWorkspace.CloneOnlineWiki`)} {...a11yProps(CreateWorkspaceTabs.CloneOnlineWiki)} />
<Tab label={t('AddWorkspace.CreateNewWiki')} {...a11yProps(CreateWorkspaceTabs.CreateNewWiki)} />
<Tab label={t('AddWorkspace.OpenLocalWiki')} {...a11yProps(CreateWorkspaceTabs.OpenLocalWiki)} />
</Tabs>
</Paper>
</AppBar>
);
}

View file

@ -39,7 +39,7 @@ interface StateProps {
type Props = OwnProps & DispatchProps & StateProps;
function CloneWikiDoneButton({
export function CloneWikiDoneButton({
isCreateMainWorkspace,
wikiPort,
mainWikiToLink,

View file

@ -2,23 +2,18 @@ import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
import { GraphQLClient, ClientContext } from 'graphql-hooks';
import Typography from '@material-ui/core/Typography';
import Paper from '@material-ui/core/Paper';
import { GITHUB_GRAPHQL_API } from '../../constants/auth';
import Description from './description-and-mode-switch';
import SearchRepo from './search-repo';
import NewWikiDoneButton from './new-wiki-done-button';
import NewWikiPathForm from './new-wiki-path-form';
import ExistedWikiPathForm from './existed-wiki-path-form';
import ExistedWikiDoneButton from './existed-wiki-done-button';
import CloneWikiDoneButton from './clone-wiki-done-button';
import type { ISubWikiPluginContent } from '@services/wiki/update-plugin-content';
import type { IAuthingUserInfo } from '@services/types';
import TabBar from './tab-bar';
import { GithubTokenForm, getGithubToken, setGithubToken } from '../../components/github/git-token-form';
import { usePromiseValue, usePromiseValueAndSetter } from '@/helpers/useServiceValue';
import { TabBar, CreateWorkspaceTabs } from './TabBar';
import { useIsCreateMainWorkspace, useWikiWorkspaceForm } from './useForm';
const graphqlClient = new GraphQLClient({
url: GITHUB_GRAPHQL_API,
@ -32,183 +27,36 @@ const Container = styled.main`
width: 0;
}
`;
const SyncContainer = styled(Paper)`
margin-top: 5px;
`;
const GithubRepoLink = styled(Typography)`
cursor: pointer;
opacity: 50%;
&:hover {
opacity: 100%;
}
`;
export default function AddWorkspace(): JSX.Element {
const [currentTab, currentTabSetter] = useState('CloneOnlineWiki');
const [isCreateMainWorkspace, isCreateMainWorkspaceSetter] = useState(false);
useEffect(() => {
void window.service.workspace.countWorkspaces().then((workspaceCount) => isCreateMainWorkspaceSetter(workspaceCount === 0));
}, []);
const parentFolderLocation = usePromiseValue(async () => (await window.service.context.get('DESKTOP_PATH')) as string);
const existedFolderLocation = usePromiseValue(async () => (await window.service.context.get('DESKTOP_PATH')) as string);
const [currentTab, currentTabSetter] = useState<CreateWorkspaceTabs>(CreateWorkspaceTabs.CreateNewWiki);
const [isCreateMainWorkspace, isCreateMainWorkspaceSetter] = useIsCreateMainWorkspace();
const [wikiPort, wikiPortSetter] = useState(5212);
useEffect(() => {
// only update default port on component mount
void window.service.workspace.countWorkspaces().then((workspaceCount) => wikiPortSetter(wikiPort + workspaceCount));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// TODO: refactor GITHUB related things out, make it can switch between vendors
const cachedGithubToken = usePromiseValue(getGithubToken);
// try get token on start up, so Github GraphQL client can use it
const [accessToken, accessTokenSetter] = useState<string | undefined>();
// try get token from local storage, and set to state for gql to use
useEffect(() => {
// on startup, loading the cachedGithubToken
if (accessToken === undefined && cachedGithubToken !== undefined) {
graphqlClient.setHeader('Authorization', `bearer ${cachedGithubToken}`);
accessTokenSetter(cachedGithubToken);
} else if (accessToken !== undefined && accessToken !== cachedGithubToken) {
// if user or login button changed the token, we use latest token
Object.keys(graphqlClient.headers).map((key) => graphqlClient.removeHeader(key));
accessTokenSetter(accessToken);
void setGithubToken(accessToken);
}
}, [cachedGithubToken, accessToken]);
const [userName, userNameSetter] = usePromiseValueAndSetter(
async () => await window.service.auth.get('userName'),
async (newUserName) => await window.service.auth.set('userName', newUserName),
);
const [mainWikiToLink, mainWikiToLinkSetter] = useState({ name: '', port: 0 });
const [tagName, tagNameSetter] = useState<string>('');
const [fileSystemPaths, fileSystemPathsSetter] = useState<ISubWikiPluginContent[]>([]);
useEffect(() => {
void window.service.wiki.getSubWikiPluginContent(mainWikiToLink.name).then(fileSystemPathsSetter);
}, [mainWikiToLink]);
const [githubWikiUrl, githubWikiUrlSetter] = useState<string>('');
useEffect(() => {
void (async function getWorkspaceRemoteInEffect(): Promise<void> {
if (existedFolderLocation !== undefined) {
const url = await window.service.git.getWorkspacesRemote(existedFolderLocation);
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
if (url) {
githubWikiUrlSetter(url);
}
}
})();
}, [githubWikiUrl, existedFolderLocation]);
const [wikiFolderName, wikiFolderNameSetter] = useState('tiddlywiki');
const form = useWikiWorkspaceForm();
return (
<ClientContext.Provider value={graphqlClient}>
<TabBar currentTab={currentTab} currentTabSetter={currentTabSetter} />
<Description isCreateMainWorkspace={isCreateMainWorkspace} isCreateMainWorkspaceSetter={isCreateMainWorkspaceSetter} />
<SyncContainer elevation={2} square>
<GithubTokenForm>
{githubWikiUrl?.length > 0 ? (
<GithubRepoLink onClick={async () => await window.service.native.open(githubWikiUrl)} variant="subtitle2" align="center">
({githubWikiUrl})
</GithubRepoLink>
) : undefined}
<SearchRepo
githubWikiUrl={githubWikiUrl}
accessToken={accessToken}
githubWikiUrlSetter={githubWikiUrlSetter}
userInfo={userInfo}
currentTab={currentTab}
wikiFolderNameSetter={wikiFolderNameSetter}
isCreateMainWorkspace={isCreateMainWorkspace}
/>
</GithubTokenForm>
</SyncContainer>
{currentTab === 'CreateNewWiki' && (
<Container>
<NewWikiPathForm
parentFolderLocation={parentFolderLocation}
parentFolderLocationSetter={parentFolderLocationSetter}
wikiFolderName={wikiFolderName}
tagName={tagName}
tagNameSetter={tagNameSetter}
wikiFolderNameSetter={wikiFolderNameSetter}
mainWikiToLink={mainWikiToLink}
mainWikiToLinkSetter={mainWikiToLinkSetter}
wikiPort={wikiPort}
wikiPortSetter={wikiPortSetter}
fileSystemPaths={fileSystemPaths}
isCreateMainWorkspace={isCreateMainWorkspace}
/>
<NewWikiPathForm form={form} />
<NewWikiDoneButton
isCreateMainWorkspace={isCreateMainWorkspace}
wikiPort={wikiPort}
mainWikiToLink={mainWikiToLink}
githubWikiUrl={githubWikiUrl}
wikiFolderName={wikiFolderName}
tagName={tagName}
parentFolderLocation={parentFolderLocation}
userInfo={userInfo}
/>
<NewWikiDoneButton form={form} />
</Container>
)}
{currentTab === 'OpenLocalWiki' && (
<Container>
<ExistedWikiPathForm
existedFolderLocationSetter={existedFolderLocationSetter}
existedFolderLocation={existedFolderLocation}
wikiFolderName={wikiFolderName}
tagName={tagName}
tagNameSetter={tagNameSetter}
wikiFolderNameSetter={wikiFolderNameSetter}
mainWikiToLink={mainWikiToLink}
mainWikiToLinkSetter={mainWikiToLinkSetter}
wikiPort={wikiPort}
wikiPortSetter={wikiPortSetter}
fileSystemPaths={fileSystemPaths}
isCreateMainWorkspace={isCreateMainWorkspace}
/>
<ExistedWikiPathForm form={form} />
<ExistedWikiDoneButton
isCreateMainWorkspace={isCreateMainWorkspace}
wikiPort={wikiPort}
mainWikiToLink={mainWikiToLink}
githubWikiUrl={githubWikiUrl}
tagName={tagName}
existedFolderLocation={existedFolderLocation}
userInfo={userInfo}
/>
<ExistedWikiDoneButton form={form} />
</Container>
)}
{currentTab === 'CloneOnlineWiki' && (
<Container>
<NewWikiPathForm
parentFolderLocation={parentFolderLocation}
parentFolderLocationSetter={parentFolderLocationSetter}
wikiFolderName={wikiFolderName}
tagName={tagName}
tagNameSetter={tagNameSetter}
wikiFolderNameSetter={wikiFolderNameSetter}
mainWikiToLink={mainWikiToLink}
mainWikiToLinkSetter={mainWikiToLinkSetter}
wikiPort={wikiPort}
wikiPortSetter={wikiPortSetter}
fileSystemPaths={fileSystemPaths}
isCreateMainWorkspace={isCreateMainWorkspace}
/>
<CloneWikiDoneButton
isCreateMainWorkspace={isCreateMainWorkspace}
wikiPort={wikiPort}
mainWikiToLink={mainWikiToLink}
githubWikiUrl={githubWikiUrl}
wikiFolderName={wikiFolderName}
tagName={tagName}
parentFolderLocation={parentFolderLocation}
userInfo={userInfo}
/>
<NewWikiPathForm form={form} />
<CloneWikiDoneButton form={form} />
</Container>
)}
</ClientContext.Provider>

View file

@ -1,44 +0,0 @@
import React from 'react';
import { invert } from 'lodash';
import { useTranslation } from 'react-i18next';
import Paper from '@material-ui/core/Paper';
import AppBar from '@material-ui/core/AppBar';
import Tabs from '@material-ui/core/Tabs';
import Tab from '@material-ui/core/Tab';
function a11yProps(index: any) {
return {
id: index,
'aria-controls': `simple-tabpanel-${index}`,
};
}
const tabIndexMap = {
CloneOnlineWiki: 0,
CreateNewWiki: 1,
OpenLocalWiki: 2,
};
export interface IProps {
currentTab: string;
currentTabSetter: (id: string) => void;
}
export default function TabBar({ currentTab, currentTabSetter }: IProps) {
const { t } = useTranslation();
return (
<AppBar position="static">
<Paper square>
<Tabs
// @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
value={tabIndexMap[currentTab]}
onChange={(event, newValue) => currentTabSetter(invert(tabIndexMap)[newValue])}
aria-label={t('AddWorkspace.SwitchCreateNewOrOpenExisted')}>
<Tab label={t('AddWorkspace.CloneOnlineWiki')} {...a11yProps('CloneOnlineWiki')} />
<Tab label={t('AddWorkspace.CreateNewWiki')} {...a11yProps('CreateNewWiki')} />
<Tab label={t('AddWorkspace.OpenLocalWiki')} {...a11yProps('OpenLocalWiki')} />
</Tabs>
</Paper>
</AppBar>
);
}

View file

@ -0,0 +1,18 @@
import { useCallback } from 'react';
export function useCloneWiki(): [() => void] {
const onSubmit = useCallback(async () => {
if (!userInfo) {
setWikiCreationMessage(t('AddWorkspace.NotLoggedIn'));
return;
}
try {
await window.service.wiki.cloneWiki(parentFolderLocation, wikiFolderName, githubWikiUrl, userInfo);
save(workspaceFormData);
} catch (error) {
setWikiCreationMessage(String(error));
}
});
return [onSubmit];
}

View file

@ -0,0 +1,105 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { usePromiseValueAndSetter } from '@/helpers/useServiceValue';
import { SupportedStorageServices } from '@services/types';
import { ISubWikiPluginContent } from '@services/wiki/update-plugin-content';
import { useEffect, useState } from 'react';
export function useIsCreateMainWorkspace(): [boolean, React.Dispatch<React.SetStateAction<boolean>>] {
const [isCreateMainWorkspace, isCreateMainWorkspaceSetter] = useState(false);
useEffect(() => {
void window.service.workspace.countWorkspaces().then((workspaceCount) => isCreateMainWorkspaceSetter(workspaceCount === 0));
}, []);
return [isCreateMainWorkspace, isCreateMainWorkspaceSetter];
}
export function useWikiWorkspaceForm() {
const [wikiPort, wikiPortSetter] = useState(5212);
useEffect(() => {
// only update default port on component mount
void window.service.workspace.countWorkspaces().then((workspaceCount) => wikiPortSetter(wikiPort + workspaceCount));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
/**
* Set storage service used by this workspace, for example, Github.
*/
const [storageProvider, storageProviderSetter] = useState<SupportedStorageServices>(SupportedStorageServices.github);
/**
*
*/
const [userName, userNameSetter] = usePromiseValueAndSetter(
async () => await window.service.auth.get('userName'),
async (newUserName) => await window.service.auth.set('userName', newUserName),
);
/**
* For sub-wiki, we need to link it to a main wiki's folder, so all wiki contents can be loaded together.
*/
const [mainWikiToLink, mainWikiToLinkSetter] = useState({ name: '', port: 0 });
const [tagName, tagNameSetter] = useState<string>('');
/**
* For sub-wiki, we need `fileSystemPaths` which is a TiddlyWiki concept that tells wiki where to put sub-wiki files.
*/
const [fileSystemPaths, fileSystemPathsSetter] = useState<ISubWikiPluginContent[]>([]);
useEffect(() => {
void window.service.wiki.getSubWikiPluginContent(mainWikiToLink.name).then(fileSystemPathsSetter);
}, [mainWikiToLink]);
/**
* For importing existed nodejs wiki into TiddlyGit, we use existedWikiFolderPath to determine which folder to import
*/
const [existedWikiFolderPath, existedWikiFolderPathSetter] = useState<string | undefined>();
/**
* For creating new wiki, we use parentFolderLocation to determine in which folder we create the new wiki folder.
* New folder will basically be created in `${parentFolderLocation}/${wikiFolderName}`
*/
const [parentFolderLocation, parentFolderLocationSetter] = useState<string | undefined>();
/**
* For creating new wiki, we put `tiddlers` folder in this `${parentFolderLocation}/${wikiFolderName}` folder
*/
const [wikiFolderName, wikiFolderNameSetter] = useState('tiddlywiki');
useEffect(() => {
void (async function getDefaultExistedWikiFolderPathEffect() {
const desktopPathAsDefaultExistedWikiFolderPath = (await window.service.context.get('DESKTOP_PATH')) as string;
existedWikiFolderPathSetter(desktopPathAsDefaultExistedWikiFolderPath);
parentFolderLocationSetter(desktopPathAsDefaultExistedWikiFolderPath);
})();
}, [mainWikiToLink]);
const [githubWikiUrl, githubWikiUrlSetter] = useState<string>('');
useEffect(() => {
void (async function getWorkspaceRemoteEffect(): Promise<void> {
if (existedWikiFolderPath !== undefined) {
const url = await window.service.git.getWorkspacesRemote(existedWikiFolderPath);
if (typeof url === 'string' && url.length > 0) {
githubWikiUrlSetter(url);
}
}
})();
}, [githubWikiUrlSetter, existedWikiFolderPath]);
return {
storageProvider,
storageProviderSetter,
wikiPort,
wikiPortSetter,
userName,
userNameSetter,
mainWikiToLink,
mainWikiToLinkSetter,
tagName,
tagNameSetter,
fileSystemPaths,
fileSystemPathsSetter,
githubWikiUrl,
githubWikiUrlSetter,
existedWikiFolderPath,
existedWikiFolderPathSetter,
parentFolderLocation,
parentFolderLocationSetter,
wikiFolderName,
wikiFolderNameSetter,
};
}

View file

@ -22,7 +22,7 @@ import type { ISubWikiPluginContent } from '@services/wiki/update-plugin-content
import { WindowNames, WindowMeta } from '@services/windows/WindowProperties';
import { usePromiseValue } from '@/helpers/useServiceValue';
import { useWorkspaceObservable } from '@services/workspaces/hooks';
import { useForm } from './formHook';
import { useForm } from './useForm';
import { IWorkspace } from '@services/workspaces/interface';
const Root = styled.div`

View file

@ -1,21 +1,11 @@
import { compact, trim } from 'lodash';
import { trim } from 'lodash';
import { GitProcess } from 'dugite';
import { getRemoteUrl } from './inspect';
const getGitUrlWithCredential = (rawUrl: string, username: string, accessToken: string): string =>
trim(`${rawUrl}.git`.replace(/\n/g, '').replace('https://github.com/', `https://${username}:${accessToken}@github.com/`));
const getGitUrlWithOutCredential = (urlWithCredential: string): string => trim(urlWithCredential.replace(/.+@/, 'https://'));
export async function getRemoteUrl(wikiFolderPath: string): Promise<string> {
const { stdout: remoteStdout } = await GitProcess.exec(['remote'], wikiFolderPath);
const remotes = compact(remoteStdout.split('\n'));
const githubRemote = remotes.find((remote) => remote === 'origin') ?? remotes[0] ?? '';
if (githubRemote.length > 0) {
const { stdout: remoteUrlStdout } = await GitProcess.exec(['remote', 'get-url', githubRemote], wikiFolderPath);
return remoteUrlStdout.replace('.git', '');
}
return '';
}
/**
* Add remote with credential
* @param {string} wikiFolderPath

View file

@ -11,7 +11,7 @@ import type { IViewService } from '@services/view/interface';
import type { IPreferenceService } from '@services/preferences/interface';
import { logger } from '@services/libs/log';
import i18n from '@services/libs/i18n';
import { getModifiedFileList, ModifiedFileList } from './inspect';
import { getModifiedFileList, ModifiedFileList, getRemoteUrl } from './inspect';
import { IGitService, IGitUserInfos } from './interface';
@injectable()
@ -29,7 +29,7 @@ export class Git implements IGitService {
public debounceCommitAndSync: (wikiFolderPath: string, githubRepoUrl: string, userInfo: IGitUserInfos) => Promise<void> | undefined;
public async getWorkspacesRemote(wikiFolderPath: string): Promise<string> {
return await github.getRemoteUrl(wikiFolderPath);
return await getRemoteUrl(wikiFolderPath);
}
public async getModifiedFileList(wikiFolderPath: string): Promise<ModifiedFileList[]> {

View file

@ -20,3 +20,19 @@ export async function getModifiedFileList(wikiFolderPath: string): Promise<Modif
filePath: path.join(wikiFolderPath, fileRelativePath),
}));
}
/**
* Inspect git's remote url from folder's .git config
* @param wikiFolderPath git folder to inspect
* @returns remote url
*/
export async function getRemoteUrl(wikiFolderPath: string): Promise<string> {
const { stdout: remoteStdout } = await GitProcess.exec(['remote'], wikiFolderPath);
const remotes = compact(remoteStdout.split('\n'));
const githubRemote = remotes.find((remote) => remote === 'origin') ?? remotes[0] ?? '';
if (githubRemote.length > 0) {
const { stdout: remoteUrlStdout } = await GitProcess.exec(['remote', 'get-url', githubRemote], wikiFolderPath);
return remoteUrlStdout.replace('.git', '');
}
return '';
}

View file

@ -24,6 +24,7 @@ export interface IGitService {
getModifiedFileList(wikiFolderPath: string): Promise<ModifiedFileList[]>;
initWikiGit(wikiFolderPath: string, githubRepoUrl: string, userInfo: IGitUserInfos, isMainWiki: boolean): Promise<void>;
commitAndSync(wikiFolderPath: string, githubRepoUrl: string, userInfo: IGitUserInfos): Promise<void>;
/** Inspect git's remote url from folder's .git config */
getWorkspacesRemote(wikiFolderPath: string): Promise<string>;
clone(githubRepoUrl: string, repoFolderPath: string, userInfo: IGitUserInfos): Promise<void>;
}

View file

@ -3,13 +3,14 @@ export interface IService {
}
export enum SupportedStorageServices {
local = 'local',
/** High availability git service without storage limit, but is blocked by GFW in China somehow */
github,
github = 'github',
/** SocialLinkedData, a privacy first DApp platform leading by Tim Berners-Lee, you can run a server by you own */
solid,
solid = 'solid',
/** China's Collaboration platform for software development & code hosting,
* with some official background, very secure in China, but have 500M storage limit */
gitee,
gitee = 'gitee',
}
export interface IAuthingResponse {