chore: move client folders (#948)

This commit is contained in:
DarkSky
2023-02-10 20:41:01 +08:00
committed by GitHub
parent cb118149f3
commit 8a7393a961
235 changed files with 114 additions and 215 deletions

View File

@@ -0,0 +1,13 @@
import NotfoundPage from '@/components/404';
import Head from 'next/head';
export default function Custom404() {
return (
<>
<Head>
<title>404 - AFFiNE</title>
</Head>
<NotfoundPage></NotfoundPage>
</>
);
}

View File

@@ -0,0 +1,95 @@
import type { AppProps } from 'next/app';
import dynamic from 'next/dynamic';
import '../../public/globals.css';
import '../../public/variable.css';
import './temporary.css';
import { Logger } from '@toeverything/pathfinder-logger';
import '@fontsource/space-mono';
import '@fontsource/poppins';
import '../utils/print-build-info';
import ProviderComposer from '@/components/provider-composer';
import type { PropsWithChildren, ReactElement, ReactNode } from 'react';
import type { NextPage } from 'next';
import { AppStateProvider } from '@/providers/app-state-provider';
import ConfirmProvider from '@/providers/ConfirmProvider';
import { ModalProvider } from '@/store/globalModal';
// import AppStateProvider2 from '@/providers/app-state-provider2/provider';
import { useRouter } from 'next/router';
import { useEffect } from 'react';
import { useAppState } from '@/providers/app-state-provider';
import { PageLoading } from '@/components/loading';
import Head from 'next/head';
import '@affine/i18n';
import { useTranslation } from '@affine/i18n';
import React from 'react';
const ThemeProvider = dynamic(() => import('@/providers/ThemeProvider'), {
ssr: false,
});
export type NextPageWithLayout<P = Record<string, unknown>, IP = P> = NextPage<
P,
IP
> & {
getLayout?: (page: ReactElement) => ReactNode;
};
type AppPropsWithLayout = AppProps & {
Component: NextPageWithLayout;
};
const App = ({ Component, pageProps }: AppPropsWithLayout) => {
const getLayout = Component.getLayout || (page => page);
const { i18n } = useTranslation();
React.useEffect(() => {
document.documentElement.lang = i18n.language;
}, [i18n.language]);
return (
<>
<Head>
<meta name="theme-color" content="#fafafa" />
<link rel="manifest" href="/manifest.json" />
<link
rel="apple-touch-icon"
sizes="180x180"
href="/icons/apple-touch-icon.png"
/>
<title>AFFiNE</title>
</Head>
<Logger />
<ProviderComposer
contexts={[
<ThemeProvider key="ThemeProvider" />,
<AppStateProvider key="appStateProvider" />,
<ModalProvider key="ModalProvider" />,
<ConfirmProvider key="ConfirmProvider" />,
]}
>
<AppDefender>{getLayout(<Component {...pageProps} />)}</AppDefender>
</ProviderComposer>
</>
);
};
const AppDefender = ({ children }: PropsWithChildren) => {
const router = useRouter();
const { synced } = useAppState();
useEffect(() => {
if (['/index.html', '/'].includes(router.asPath)) {
router.replace('/workspace');
}
}, [router]);
// if you visit /404, you will see the children directly
if (router.route === '/404') {
return <div>{children}</div>;
}
return <div>{synced ? children : <PageLoading />}</div>;
};
export default App;

View File

@@ -0,0 +1,53 @@
import createEmotionServer from '@emotion/server/create-instance';
import { cache } from '@emotion/css';
import Document, {
Html,
Head,
Main,
NextScript,
DocumentContext,
} from 'next/document';
import * as React from 'react';
export const renderStatic = async (html: string) => {
if (html === undefined) {
throw new Error('did you forget to return html from renderToString?');
}
const { extractCritical } = createEmotionServer(cache);
const { ids, css } = extractCritical(html);
return { html, ids, css };
};
export default class AppDocument extends Document {
static async getInitialProps(ctx: DocumentContext) {
const page = await ctx.renderPage();
const { css, ids } = await renderStatic(page.html);
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: (
<React.Fragment>
{initialProps.styles}
<style
data-emotion={`css ${ids.join(' ')}`}
dangerouslySetInnerHTML={{ __html: css }}
/>
</React.Fragment>
),
};
}
render() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}

View File

@@ -0,0 +1,7 @@
import type { NextPage } from 'next';
const Home: NextPage = () => {
return <div title="Home Page"></div>;
};
export default Home;

View File

@@ -0,0 +1,144 @@
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
import { styled } from '@affine/component';
import { Empty } from '@affine/component';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
import { PageLoading } from '@/components/loading';
// const User = ({ name, avatar }: { name: string; avatar?: string }) => {
// return (
// <UserContent>
// {avatar ? (
// <Avatar src={avatar}></Avatar>
// ) : (
// <UserIcon>{name.slice(0, 1)}</UserIcon>
// )}
// <span>{name}</span>
// </UserContent>
// );
// };
export default function DevPage() {
const [loading, setLoading] = useState(true);
const router = useRouter();
const [successInvited, setSuccessInvited] = useState<boolean>(false);
const { acceptInvite } = useWorkspaceHelper();
useEffect(() => {
if (router.query.invite_code) {
acceptInvite(router.query.invite_code as string)
.then(data => {
if (data && data.accepted) {
setSuccessInvited(true);
}
})
.finally(() => {
setLoading(false);
});
} else {
setLoading(false);
}
}, [router, acceptInvite]);
return loading ? (
<PageLoading />
) : (
<Invited>
<div>
<Empty width={310} height={310}></Empty>
<Content>
{/* TODO add inviteInfo*/}
{/* <User name={inviteData?.name ? inviteData.name : '-'}></User> invited */}
{/* you to join */}
{/* <User
name={inviteData?.workspaceName ? inviteData.workspaceName : '-'}
></User> */}
{successInvited ? (
<Status>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle opacity="0.14" cx="12" cy="12" r="9" fill="#6880FF" />
<path
fillRule="evenodd"
clipRule="evenodd"
d="M12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4ZM2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12ZM16.6783 8.2652C17.0841 8.6398 17.1094 9.27246 16.7348 9.67828L11.1963 15.6783C11.007 15.8834 10.7406 16 10.4615 16C10.1824 16 9.91604 15.8834 9.72674 15.6783L7.2652 13.0116C6.89059 12.6058 6.9159 11.9731 7.32172 11.5985C7.72754 11.2239 8.3602 11.2492 8.7348 11.6551L10.4615 13.5257L15.2652 8.32172C15.6398 7.9159 16.2725 7.89059 16.6783 8.2652Z"
fill="#6880FF"
/>
</svg>
Successfully joined
</Status>
) : (
<Status>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect
opacity="0.14"
x="2"
y="7"
width="12"
height="10"
rx="5"
fill="#6880FF"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M2.29289 2.29289C2.68342 1.90237 3.31658 1.90237 3.70711 2.29289L7.70678 6.29256C7.707 6.29278 7.70655 6.29234 7.70678 6.29256L21.7071 20.2929C22.0976 20.6834 22.0976 21.3166 21.7071 21.7071C21.3166 22.0976 20.6834 22.0976 20.2929 21.7071L16.5858 18H16.5C15.9477 18 15.5 17.5523 15.5 17C15.5 16.9722 15.5011 16.9447 15.5033 16.9176L13.961 15.3752C12.8815 16.959 11.0631 18 9 18H7C3.68629 18 1 15.3137 1 12C1 9.40763 2.64407 7.19925 4.94642 6.36064L2.29289 3.70711C1.90237 3.31658 1.90237 2.68342 2.29289 2.29289ZM6.60504 8.01925C4.5813 8.21761 3 9.92414 3 12C3 14.2091 4.79086 16 7 16H9C10.511 16 11.8282 15.1618 12.5087 13.9229L10.9367 12.3509C10.7946 12.7301 10.4288 13 10 13C9.44772 13 9 12.5523 9 12C9 11.5127 9.0583 11.0379 9.16858 10.5828L6.60504 8.01925ZM15 8C14.4436 8 13.9162 8.11302 13.4375 8.31642C12.9291 8.53238 12.342 8.29538 12.1261 7.78707C11.9101 7.27876 12.1471 6.69162 12.6554 6.47566C13.377 6.1691 14.17 6 15 6H17C20.3137 6 23 8.68629 23 12C23 13.4573 22.4792 14.7955 21.6146 15.8349C21.2615 16.2595 20.6309 16.3174 20.2063 15.9642C19.7817 15.6111 19.7239 14.9806 20.077 14.556C20.6539 13.8624 21 12.973 21 12C21 9.79086 19.2091 8 17 8H15Z"
fill="#6880FF"
/>
</svg>
The link has expired
</Status>
)}
</Content>
</div>
</Invited>
);
}
// const UserIcon = styled('div')({
// display: 'inline-block',
// width: '28px',
// height: '28px',
// borderRadius: '50%',
// backgroundColor: '#FFF5AB',
// textAlign: 'center',
// color: '#896406',
// lineHeight: '28px',
// });
const Invited = styled('div')(({ theme }) => {
return {
height: '100vh',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
textAlign: 'center',
color: theme.colors.textColor,
backgroundColor: theme.colors.pageBackground,
};
});
const Content = styled('div')({
fontSize: '16px',
marginTop: '35px',
});
const Status = styled('div')(() => {
return {
marginTop: '16px',
svg: {
verticalAlign: 'middle',
marginRight: '12px',
},
};
});

View File

@@ -0,0 +1,146 @@
import { ReactElement, useEffect, useState } from 'react';
import { useAppState } from '@/providers/app-state-provider';
import type { NextPageWithLayout } from '../..//_app';
import { displayFlex, styled } from '@affine/component';
import dynamic from 'next/dynamic';
import { useRouter } from 'next/router';
import { Page as PageStore, Workspace } from '@blocksuite/store';
import { PageLoading } from '@/components/loading';
import { Breadcrumbs } from '@affine/component';
import { IconButton } from '@affine/component';
import NextLink from 'next/link';
import { PaperIcon, SearchIcon } from '@blocksuite/icons';
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
import { useModal } from '@/store/globalModal';
const DynamicBlocksuite = dynamic(() => import('@/components/editor'), {
ssr: false,
});
const Page: NextPageWithLayout = () => {
const [workspace, setWorkspace] = useState<Workspace>();
const [page, setPage] = useState<PageStore>();
const { dataCenter } = useAppState();
const router = useRouter();
const [loaded, setLoaded] = useState(false);
const [workspaceName, setWorkspaceName] = useState('');
const [pageTitle, setPageTitle] = useState('');
const { triggerQuickSearchModal } = useModal();
useEffect(() => {
dataCenter
.loadPublicWorkspace(router.query.workspaceId as string)
.then(data => {
setWorkspaceName(data.blocksuiteWorkspace?.meta.name as string);
if (data.blocksuiteWorkspace) {
setWorkspace(data.blocksuiteWorkspace);
if (
router.query.pageId &&
data.blocksuiteWorkspace.meta.pageMetas.find(
p => p.id === router.query.pageId
)
) {
const page = data.blocksuiteWorkspace.getPage(
router.query.pageId as string
);
page && setPageTitle(page.meta.title);
page && setPage(page);
} else {
router.push('/404');
}
}
})
.catch(() => {
router.push('/404');
});
}, [router, dataCenter]);
return (
<>
{!loaded && <PageLoading />}
<PageContainer>
<NavContainer>
<Breadcrumbs>
<StyledBreadcrumbs
href={`/public-workspace/${router.query.workspaceId}`}
>
<WorkspaceUnitAvatar size={24} name={workspaceName} />
<span>{workspaceName}</span>
</StyledBreadcrumbs>
<StyledBreadcrumbs
href={`/public-workspace/${router.query.workspaceId}/${router.query.pageId}`}
>
<PaperIcon fontSize={24} />
<span>{pageTitle ? pageTitle : 'Untitled'}</span>
</StyledBreadcrumbs>
</Breadcrumbs>
<SearchButton
onClick={() => {
triggerQuickSearchModal();
}}
>
<SearchIcon />
</SearchButton>
</NavContainer>
{workspace && page && (
<DynamicBlocksuite
page={page}
workspace={workspace}
setEditor={editor => {
editor.readonly = true;
setLoaded(true);
}}
/>
)}
</PageContainer>
</>
);
};
Page.getLayout = function getLayout(page: ReactElement) {
return <div>{page}</div>;
};
export default Page;
export const PageContainer = styled.div(({ theme }) => {
return {
height: '100vh',
overflowY: 'auto',
backgroundColor: theme.colors.pageBackground,
};
});
export const NavContainer = styled.div(({ theme }) => {
return {
width: '100vw',
padding: '0 12px',
height: '60px',
...displayFlex('start', 'center'),
backgroundColor: theme.colors.pageBackground,
};
});
export const StyledBreadcrumbs = styled(NextLink)(({ theme }) => {
return {
flex: 1,
...displayFlex('center', 'center'),
paddingLeft: '12px',
span: {
padding: '0 12px',
fontSize: theme.font.base,
lineHeight: theme.font.lineHeight,
},
':hover': { color: theme.colors.primaryColor },
transition: 'all .15s',
':visited': {
color: theme.colors.popoverColor,
':hover': { color: theme.colors.primaryColor },
},
};
});
export const SearchButton = styled(IconButton)(({ theme }) => {
return {
color: theme.colors.iconColor,
fontSize: '24px',
marginLeft: 'auto',
padding: '0 24px',
};
});

View File

@@ -0,0 +1,65 @@
import { PageList } from '@/components/page-list';
import { ReactElement, useEffect, useState } from 'react';
import { PageMeta, useAppState } from '@/providers/app-state-provider';
import { useRouter } from 'next/router';
import {
PageContainer,
NavContainer,
StyledBreadcrumbs,
SearchButton,
} from './[pageId]';
import { Breadcrumbs } from '@affine/component';
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
import { SearchIcon } from '@blocksuite/icons';
import { useModal } from '@/store/globalModal';
const All = () => {
const { dataCenter } = useAppState();
const router = useRouter();
const [pageList, setPageList] = useState<PageMeta[]>([]);
const [workspaceName, setWorkspaceName] = useState('');
const { triggerQuickSearchModal } = useModal();
useEffect(() => {
dataCenter
.loadPublicWorkspace(router.query.workspaceId as string)
.then(data => {
setPageList(data.blocksuiteWorkspace?.meta.pageMetas as PageMeta[]);
setWorkspaceName(data.blocksuiteWorkspace?.meta.name as string);
})
.catch(() => {
router.push('/404');
});
}, [router, dataCenter]);
return (
<PageContainer>
<NavContainer>
<Breadcrumbs>
<StyledBreadcrumbs
href={`/public-workspace/${router.query.workspaceId}`}
>
<WorkspaceUnitAvatar size={24} name={workspaceName} />
<span>{workspaceName}</span>
</StyledBreadcrumbs>
</Breadcrumbs>
<SearchButton
onClick={() => {
triggerQuickSearchModal();
}}
>
<SearchIcon />
</SearchButton>
</NavContainer>
<PageList
pageList={pageList.filter(p => !p.trash)}
showFavoriteTag={false}
isPublic={true}
/>
</PageContainer>
);
};
All.getLayout = function getLayout(page: ReactElement) {
return <div>{page}</div>;
};
export default All;

View File

@@ -0,0 +1,15 @@
.affine-default-page-block-title-container {
margin-top: 78px;
margin-bottom: 40px;
}
.affine-default-page-block-container {
width: 686px !important;
}
affine-block-hub {
position: unset !important;
}
.block-hub-menu-container {
position: unset !important;
}

View File

@@ -0,0 +1,82 @@
import {
PropsWithChildren,
ReactElement,
useCallback,
useEffect,
useState,
} from 'react';
import { EditorHeader } from '@/components/header';
import MobileModal from '@/components/mobile-modal';
import { useAppState } from '@/providers/app-state-provider';
import type { NextPageWithLayout } from '../..//_app';
import WorkspaceLayout from '@/components/workspace-layout';
import { useRouter } from 'next/router';
import { usePageHelper } from '@/hooks/use-page-helper';
import dynamic from 'next/dynamic';
import { EditorContainer } from '@blocksuite/editor';
import Head from 'next/head';
import { useTranslation } from '@affine/i18n';
const DynamicBlocksuite = dynamic(() => import('@/components/editor'), {
ssr: false,
});
const Page: NextPageWithLayout = () => {
const { currentPage, currentWorkspace, setEditor } = useAppState();
const setEditorHandler = useCallback(
(editor: EditorContainer) => setEditor.current(editor),
[setEditor]
);
const { t } = useTranslation();
return (
<>
<Head>
<title>{currentPage?.meta?.title || t('Untitled')} - AFFiNE</title>
</Head>
<EditorHeader />
<MobileModal />
{currentPage && currentWorkspace?.blocksuiteWorkspace && (
<DynamicBlocksuite
page={currentPage}
workspace={currentWorkspace.blocksuiteWorkspace}
setEditor={setEditorHandler}
/>
)}
</>
);
};
const PageDefender = ({ children }: PropsWithChildren) => {
const router = useRouter();
const [pageLoaded, setPageLoaded] = useState(false);
const { currentWorkspace, loadPage } = useAppState();
const { createPage } = usePageHelper();
useEffect(() => {
const initPage = async () => {
const pageId = router.query.pageId as string;
const isPageExist =
currentWorkspace?.blocksuiteWorkspace?.meta?.pageMetas.find(
p => p.id === pageId
);
if (!isPageExist) {
await createPage({ pageId });
}
await loadPage(pageId);
setPageLoaded(true);
};
initPage();
}, [createPage, currentWorkspace, loadPage, router.query.pageId]);
return <>{pageLoaded ? children : null}</>;
};
Page.getLayout = function getLayout(page: ReactElement) {
return (
<WorkspaceLayout>
<PageDefender>{page}</PageDefender>
</WorkspaceLayout>
);
};
export default Page;

View File

@@ -0,0 +1,34 @@
import { PageList } from '@/components/page-list';
import { AllPagesIcon } from '@blocksuite/icons';
import { PageListHeader } from '@/components/header';
import { ReactElement } from 'react';
import WorkspaceLayout from '@/components/workspace-layout';
import { useTranslation } from '@affine/i18n';
import { PageMeta, useAppState } from '@/providers/app-state-provider';
import Head from 'next/head';
const All = () => {
const { currentWorkspace } = useAppState();
const pageList = (currentWorkspace?.blocksuiteWorkspace?.meta.pageMetas ||
[]) as PageMeta[];
const { t } = useTranslation();
return (
<>
<Head>
<title>{t('All pages')} - AFFiNE</title>
</Head>
<PageListHeader icon={<AllPagesIcon />}>{t('All pages')}</PageListHeader>
<PageList
pageList={pageList.filter(p => !p.trash)}
showFavoriteTag={true}
listType="all"
/>
</>
);
};
All.getLayout = function getLayout(page: ReactElement) {
return <WorkspaceLayout>{page}</WorkspaceLayout>;
};
export default All;

View File

@@ -0,0 +1,30 @@
import { PageListHeader } from '@/components/header';
import { PageList } from '@/components/page-list';
import { FavouritesIcon } from '@blocksuite/icons';
import { ReactElement } from 'react';
import WorkspaceLayout from '@/components/workspace-layout';
import { useTranslation } from '@affine/i18n';
import { useAppState } from '@/providers/app-state-provider';
import Head from 'next/head';
export const Favorite = () => {
const { pageList } = useAppState();
const { t } = useTranslation();
return (
<>
<Head>
<title>{t('Favorites')} - AFFiNE</title>
</Head>
<PageListHeader icon={<FavouritesIcon />}>
{t('Favorites')}
</PageListHeader>
<PageList
pageList={pageList.filter(p => p.favorite && !p.trash)}
listType="favorite"
/>
</>
);
};
Favorite.getLayout = function getLayout(page: ReactElement) {
return <WorkspaceLayout>{page}</WorkspaceLayout>;
};
export default Favorite;

View File

@@ -0,0 +1,41 @@
import { useEffect } from 'react';
import { useRouter } from 'next/router';
import { useAppState } from '@/providers/app-state-provider';
import useEnsureWorkspace from '@/hooks/use-ensure-workspace';
import { PageLoading } from '@/components/loading';
import usePageHelper from '@/hooks/use-page-helper';
const WorkspaceIndex = () => {
const router = useRouter();
const { currentWorkspace } = useAppState();
const { createPage } = usePageHelper();
const { workspaceLoaded, activeWorkspaceId } = useEnsureWorkspace();
useEffect(() => {
const initPage = async () => {
if (!workspaceLoaded) {
return;
}
const savedPageId =
currentWorkspace?.blocksuiteWorkspace?.meta.pageMetas[0]?.id;
if (savedPageId) {
router.replace(`/workspace/${activeWorkspaceId}/${savedPageId}`);
return;
}
const pageId = await createPage();
router.replace(`/workspace/${activeWorkspaceId}/${pageId}`);
};
initPage();
}, [
currentWorkspace,
createPage,
router,
workspaceLoaded,
activeWorkspaceId,
]);
return <PageLoading />;
};
export default WorkspaceIndex;

View File

@@ -0,0 +1,90 @@
import { styled } from '@affine/component';
import { ReactElement, ReactNode } from 'react';
import WorkspaceLayout from '@/components/workspace-layout';
import { Button } from '@affine/component';
export const FeatureCardDiv = styled('section')({
width: '800px',
border: '1px #eee solid',
margin: '20px auto',
minHeight: '100px',
padding: '15px',
});
const FeatureCard = (props: {
name: string;
children: ReactNode | ReactNode[];
}) => {
return (
<FeatureCardDiv>
<h1>Feature - {props.name}</h1>
{props.children}
</FeatureCardDiv>
);
};
export const Playground = () => {
return (
<>
<FeatureCard name="Account">
<Button>Sign In</Button>
<Button>Sign Out</Button>
</FeatureCard>
<FeatureCard name="Workspace List">
<ul>
<li>AFFiNE Demo</li>
<li>AFFiNE XXX</li>
</ul>
<Button>New Workspace</Button>
</FeatureCard>
<FeatureCard name="Active Workspace">
<div>Workspace Name /[Workspace Members Count]/[Workspace Avatar]</div>
<div>Cloud Sync [Yes/No]</div>
<div>Auth [Public/Private]</div>
<div>
<Button>Update Workspace Name</Button>
<Button>Upload Workspace Avatar</Button>
<Button>Update Workspace Avatar</Button>
</div>
<div>
<Button>Leave Workspace</Button>
<Button>Delete Workspace </Button>
</div>
<div>
Cloud Sync <Button>Enalbe</Button>
<Button>Disable</Button>
</div>
</FeatureCard>
<FeatureCard name="Workspace Members">
<Button>Add Member</Button>
<ul>
<li>
terrychinaz@gmail <button>Delete Members</button>
</li>
</ul>
</FeatureCard>
<FeatureCard name="Cloud Search">
<input type="text" value="AFFiNE Keywords" />
<Button>Search</Button>
<ul></ul>
</FeatureCard>
<FeatureCard name="Import/Exeport Worpsace">
<div>Workspace Name</div>
<Button> Export Workspace</Button>
<Button> Import Workspace</Button>
</FeatureCard>
</>
);
};
Playground.getLayout = function getLayout(page: ReactElement) {
return <WorkspaceLayout>{page}</WorkspaceLayout>;
};
export default Playground;

View File

@@ -0,0 +1,117 @@
import {
StyledSettingContainer,
StyledSettingContent,
StyledSettingSidebar,
StyledSettingTabContainer,
WorkspaceSettingTagItem,
} from '@/components/workspace-setting/style';
import { ReactElement, ReactNode, useState } from 'react';
import {
GeneralPage,
MembersPage,
PublishPage,
SyncPage,
ExportPage,
} from '@/components/workspace-setting';
import { SettingsIcon } from '@blocksuite/icons';
import { useAppState } from '@/providers/app-state-provider';
import WorkspaceLayout from '@/components/workspace-layout';
import { WorkspaceUnit } from '@affine/datacenter';
import { useTranslation } from '@affine/i18n';
import { PageListHeader } from '@/components/header';
import Head from 'next/head';
const useTabMap = () => {
const { t } = useTranslation();
const { isOwner } = useAppState();
const tabMap: {
name: string;
panelRender: (workspace: WorkspaceUnit) => ReactNode;
}[] = [
{
name: t('General'),
panelRender: workspace => <GeneralPage workspace={workspace} />,
},
{
name: t('Sync'),
panelRender: workspace => <SyncPage workspace={workspace} />,
},
{
name: t('Collaboration'),
panelRender: workspace => <MembersPage workspace={workspace} />,
},
{
name: t('Publish'),
panelRender: workspace => <PublishPage workspace={workspace} />,
},
// TODO: next version will finish this feature
{
name: t('Export'),
panelRender: workspace => <ExportPage workspace={workspace} />,
},
];
const [activeTab, setActiveTab] = useState<string>(tabMap[0].name);
const handleTabChange = (tab: string) => {
setActiveTab(tab);
};
const activeTabPanelRender = tabMap.find(
tab => tab.name === activeTab
)?.panelRender;
let tableArr: {
name: string;
panelRender: (workspace: WorkspaceUnit) => ReactNode;
}[] = tabMap;
if (!isOwner) {
tableArr = [
{
name: 'General',
panelRender: workspace => <GeneralPage workspace={workspace} />,
},
];
}
return { activeTabPanelRender, tableArr, handleTabChange, activeTab };
};
const WorkspaceSetting = () => {
const { t } = useTranslation();
const { currentWorkspace } = useAppState();
const { activeTabPanelRender, tableArr, handleTabChange, activeTab } =
useTabMap();
return (
<>
<Head>
<title>{t('Settings')} - AFFiNE</title>
</Head>
<StyledSettingContainer>
<PageListHeader icon={<SettingsIcon />}>{t('Settings')}</PageListHeader>
<StyledSettingSidebar>
<StyledSettingTabContainer>
{tableArr.map(({ name }) => {
return (
<WorkspaceSettingTagItem
key={name}
isActive={activeTab === name}
onClick={() => {
handleTabChange(name);
}}
>
{name}
</WorkspaceSettingTagItem>
);
})}
</StyledSettingTabContainer>
</StyledSettingSidebar>
<StyledSettingContent>
{currentWorkspace && activeTabPanelRender?.(currentWorkspace)}
</StyledSettingContent>
</StyledSettingContainer>
</>
);
};
WorkspaceSetting.getLayout = function getLayout(page: ReactElement) {
return <WorkspaceLayout>{page}</WorkspaceLayout>;
};
export default WorkspaceSetting;

View File

@@ -0,0 +1,31 @@
import { PageListHeader } from '@/components/header';
import { PageList } from '@/components/page-list';
import { TrashIcon } from '@blocksuite/icons';
import { ReactElement } from 'react';
import WorkspaceLayout from '@/components/workspace-layout';
import { useTranslation } from '@affine/i18n';
import { useAppState } from '@/providers/app-state-provider';
import Head from 'next/head';
export const Trash = () => {
const { pageList } = useAppState();
const { t } = useTranslation();
return (
<>
<Head>
<title>{t('Trash')} - AFFiNE</title>
</Head>
<PageListHeader icon={<TrashIcon />}>{t('Trash')}</PageListHeader>
<PageList
pageList={pageList.filter(p => p.trash)}
isTrash={true}
listType="trash"
/>
</>
);
};
Trash.getLayout = function getLayout(page: ReactElement) {
return <WorkspaceLayout>{page}</WorkspaceLayout>;
};
export default Trash;

View File

@@ -0,0 +1,21 @@
import { useEffect } from 'react';
import { useRouter } from 'next/router';
import { useAppState } from '@/providers/app-state-provider';
import useEnsureWorkspace from '@/hooks/use-ensure-workspace';
import { PageLoading } from '@/components/loading';
export const WorkspaceIndex = () => {
const router = useRouter();
const { currentWorkspace } = useAppState();
const { workspaceLoaded } = useEnsureWorkspace();
useEffect(() => {
if (workspaceLoaded) {
router.push(`/workspace/${currentWorkspace?.id}`);
}
}, [currentWorkspace, router, workspaceLoaded]);
return <PageLoading />;
};
export default WorkspaceIndex;