mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-04 00:28:33 +00:00
Merge remote-tracking branch 'refs/remotes/origin/feat/poc'
Conflicts: package.json packages/data-center/package.json pnpm-lock.yaml
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
**/webpack.config.js
|
||||
**/jest.config.js
|
||||
**/scripts/*.js
|
||||
**/node_modules/**
|
||||
.github/**
|
||||
|
||||
2
.github/workflows/build-test-version.yml
vendored
2
.github/workflows/build-test-version.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
||||
scope: '@toeverything'
|
||||
cache: 'pnpm'
|
||||
|
||||
- run: node scripts/module-resolve/ci.js
|
||||
- run: node scripts/module-resolve/ci.cjs
|
||||
|
||||
- name: Restore cache
|
||||
uses: actions/cache@v3
|
||||
|
||||
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -244,7 +244,7 @@ jobs:
|
||||
scope: '@toeverything'
|
||||
cache: 'pnpm'
|
||||
|
||||
- run: node scripts/module-resolve/ci.js
|
||||
- run: node scripts/module-resolve/ci.cjs
|
||||
|
||||
- name: Restore cache
|
||||
uses: actions/cache@v3
|
||||
|
||||
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
scope: '@toeverything'
|
||||
cache: 'pnpm'
|
||||
|
||||
- run: node scripts/module-resolve/ci.js
|
||||
- run: node scripts/module-resolve/ci.cjs
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --no-frozen-lockfile
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -48,6 +48,7 @@ Thumbs.db
|
||||
out/
|
||||
|
||||
module-resolve.js
|
||||
module-resolve.cjs
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/playwright/.cache/
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
const fs = require('fs');
|
||||
|
||||
function getCustomize() {
|
||||
const customed = fs.existsSync('./module-resolve.js');
|
||||
const customed = fs.existsSync('./module-resolve.cjs');
|
||||
if (!customed) {
|
||||
return null;
|
||||
}
|
||||
const script = require('./module-resolve.js');
|
||||
const script = require('./module-resolve.cjs');
|
||||
return script && script.resolve;
|
||||
}
|
||||
|
||||
|
||||
7
.vscode/extensions.json
vendored
7
.vscode/extensions.json
vendored
@@ -1,3 +1,8 @@
|
||||
{
|
||||
"recommendations": ["ms-playwright.playwright", "esbenp.prettier-vscode"]
|
||||
"recommendations": [
|
||||
"ms-playwright.playwright",
|
||||
"esbenp.prettier-vscode",
|
||||
"deepscan.vscode-deepscan",
|
||||
"streetsidesoftware.code-spell-checker"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"private": true,
|
||||
"author": "toeverything",
|
||||
"license": "MPL-2.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "cross-env NODE_ENV=development pnpm --filter=!@affine/app build && pnpm --filter @affine/app dev",
|
||||
"dev:ac": "pnpm --filter=!@affine/app build && cross-env NODE_API_SERVER=ac pnpm --filter @affine/app dev",
|
||||
@@ -47,8 +48,7 @@
|
||||
"lint-staged": "^13.1.0",
|
||||
"prettier": "^2.7.1",
|
||||
"ts-jest": "^29.0.3",
|
||||
"typescript": "^4.9.3",
|
||||
"vitest": "^0.26.3"
|
||||
"typescript": "^4.9.3"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
};
|
||||
@@ -10,10 +10,10 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@affine/datacenter": "workspace:*",
|
||||
"@blocksuite/blocks": "=0.3.1-20230106060050-1aad55d",
|
||||
"@blocksuite/editor": "=0.3.1-20230106060050-1aad55d",
|
||||
"@blocksuite/blocks": "0.3.1-20230109032243-37ad3ba",
|
||||
"@blocksuite/editor": "0.3.1-20230109032243-37ad3ba",
|
||||
"@blocksuite/icons": "^2.0.2",
|
||||
"@blocksuite/store": "=0.3.1-20230106060050-1aad55d",
|
||||
"@blocksuite/store": "0.3.1-20230109032243-37ad3ba",
|
||||
"@emotion/css": "^11.10.0",
|
||||
"@emotion/react": "^11.10.4",
|
||||
"@emotion/server": "^11.10.0",
|
||||
@@ -49,8 +49,8 @@
|
||||
"eslint-config-next": "12.3.1",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"raw-loader": "^4.0.2",
|
||||
"next-pwa": "^5.6.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"typescript": "4.8.3"
|
||||
},
|
||||
"eslintConfig": {
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
TelegramIcon,
|
||||
RedditIcon,
|
||||
LinkIcon,
|
||||
} from './icons';
|
||||
} from './Icons';
|
||||
import logo from './affine-text-logo.png';
|
||||
import {
|
||||
StyledBigLink,
|
||||
|
||||
94
packages/app/src/components/create-workspace/index.tsx
Normal file
94
packages/app/src/components/create-workspace/index.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
import { styled } from '@/styles';
|
||||
import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal';
|
||||
import { Button } from '@/ui/button';
|
||||
import { useState } from 'react';
|
||||
import Input from '@/ui/input';
|
||||
import { useTemporaryHelper } from '@/providers/temporary-helper-provider';
|
||||
import { KeyboardEvent } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
interface ICloseParams {
|
||||
workspaceId?: string;
|
||||
}
|
||||
interface ModalProps {
|
||||
open: boolean;
|
||||
onClose: (opts: ICloseParams) => void;
|
||||
}
|
||||
|
||||
export const CreateWorkspaceModal = ({ open, onClose }: ModalProps) => {
|
||||
const [workspaceName, setWorkspaceName] = useState('');
|
||||
const { createWorkspace, setActiveWorkspace } = useTemporaryHelper();
|
||||
const handleCreateWorkspace = () => {
|
||||
const workspace = createWorkspace(workspaceName);
|
||||
onClose({ workspaceId: workspace.id });
|
||||
setActiveWorkspace(workspace);
|
||||
};
|
||||
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (event.key === 'Enter') {
|
||||
// 👇 Get input value
|
||||
handleCreateWorkspace();
|
||||
}
|
||||
};
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div>
|
||||
<Modal open={open} onClose={onClose}>
|
||||
<ModalWrapper width={620} height={334} style={{ padding: '10px' }}>
|
||||
<Header>
|
||||
<ContentTitle>{t('New Workspace')}</ContentTitle>
|
||||
<ModalCloseButton
|
||||
top={6}
|
||||
right={6}
|
||||
onClick={() => {
|
||||
onClose({});
|
||||
}}
|
||||
/>
|
||||
</Header>
|
||||
<Content>
|
||||
<p>{t('Workspace description')}</p>
|
||||
<Input
|
||||
onKeyDown={handleKeyDown}
|
||||
onChange={value => {
|
||||
setWorkspaceName(value);
|
||||
}}
|
||||
></Input>
|
||||
<Button
|
||||
onClick={() => {
|
||||
handleCreateWorkspace();
|
||||
}}
|
||||
>
|
||||
{t('Create')}
|
||||
</Button>
|
||||
</Content>
|
||||
</ModalWrapper>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Header = styled('div')({
|
||||
position: 'relative',
|
||||
height: '44px',
|
||||
});
|
||||
|
||||
const Content = styled('div')({
|
||||
display: 'flex',
|
||||
padding: '0 48px',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
gap: '16px',
|
||||
});
|
||||
|
||||
const ContentTitle = styled('span')({
|
||||
fontSize: '20px',
|
||||
lineHeight: '28px',
|
||||
fontWeight: 600,
|
||||
textAlign: 'left',
|
||||
paddingBottom: '16px',
|
||||
});
|
||||
|
||||
// const Footer = styled('div')({
|
||||
// height: '70px',
|
||||
// paddingLeft: '24px',
|
||||
// marginTop: '32px',
|
||||
// textAlign: 'center',
|
||||
// });
|
||||
@@ -13,79 +13,83 @@ import {
|
||||
ConnectorIcon,
|
||||
UndoIcon,
|
||||
RedoIcon,
|
||||
} from './icons';
|
||||
} from './Icons';
|
||||
import { Tooltip } from '@/ui/tooltip';
|
||||
import Slide from '@mui/material/Slide';
|
||||
import useCurrentPageMeta from '@/hooks/use-current-page-meta';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import useHistoryUpdated from '@/hooks/use-history-update';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const toolbarList1 = [
|
||||
{
|
||||
flavor: 'select',
|
||||
icon: <SelectIcon />,
|
||||
toolTip: 'Select',
|
||||
disable: false,
|
||||
callback: () => {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('affine.switch-mouse-mode', {
|
||||
detail: {
|
||||
type: 'default',
|
||||
},
|
||||
})
|
||||
);
|
||||
const useToolbarList1 = () => {
|
||||
const { t } = useTranslation();
|
||||
return [
|
||||
{
|
||||
flavor: 'select',
|
||||
icon: <SelectIcon />,
|
||||
toolTip: t('Select'),
|
||||
disable: false,
|
||||
callback: () => {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('affine.switch-mouse-mode', {
|
||||
detail: {
|
||||
type: 'default',
|
||||
},
|
||||
})
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
flavor: 'text',
|
||||
icon: <TextIcon />,
|
||||
toolTip: 'Text (coming soon)',
|
||||
disable: true,
|
||||
},
|
||||
{
|
||||
flavor: 'shape',
|
||||
icon: <ShapeIcon />,
|
||||
toolTip: 'Shape',
|
||||
disable: false,
|
||||
callback: () => {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('affine.switch-mouse-mode', {
|
||||
detail: {
|
||||
type: 'shape',
|
||||
color: 'black',
|
||||
shape: 'rectangle',
|
||||
},
|
||||
})
|
||||
);
|
||||
{
|
||||
flavor: 'text',
|
||||
icon: <TextIcon />,
|
||||
toolTip: t('Text'),
|
||||
disable: true,
|
||||
},
|
||||
{
|
||||
flavor: 'shape',
|
||||
icon: <ShapeIcon />,
|
||||
toolTip: t('Shape'),
|
||||
disable: false,
|
||||
callback: () => {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('affine.switch-mouse-mode', {
|
||||
detail: {
|
||||
type: 'shape',
|
||||
color: 'black',
|
||||
shape: 'rectangle',
|
||||
},
|
||||
})
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
flavor: 'sticky',
|
||||
icon: <StickerIcon />,
|
||||
toolTip: t('Sticky'),
|
||||
disable: true,
|
||||
},
|
||||
{
|
||||
flavor: 'pen',
|
||||
icon: <PenIcon />,
|
||||
toolTip: t('Pen'),
|
||||
disable: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
flavor: 'sticky',
|
||||
icon: <StickerIcon />,
|
||||
toolTip: 'Sticky (coming soon)',
|
||||
disable: true,
|
||||
},
|
||||
{
|
||||
flavor: 'pen',
|
||||
icon: <PenIcon />,
|
||||
toolTip: 'Pen (coming soon)',
|
||||
disable: true,
|
||||
},
|
||||
|
||||
{
|
||||
flavor: 'connector',
|
||||
icon: <ConnectorIcon />,
|
||||
toolTip: 'Connector (coming soon)',
|
||||
disable: true,
|
||||
},
|
||||
];
|
||||
{
|
||||
flavor: 'connector',
|
||||
icon: <ConnectorIcon />,
|
||||
toolTip: t('Connector'),
|
||||
disable: true,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const UndoRedo = () => {
|
||||
const [canUndo, setCanUndo] = useState(false);
|
||||
const [canRedo, setCanRedo] = useState(false);
|
||||
const { currentPage } = useAppState();
|
||||
const onHistoryUpdated = useHistoryUpdated();
|
||||
|
||||
const { t } = useTranslation();
|
||||
useEffect(() => {
|
||||
onHistoryUpdated(page => {
|
||||
setCanUndo(page.canUndo);
|
||||
@@ -95,7 +99,7 @@ const UndoRedo = () => {
|
||||
|
||||
return (
|
||||
<StyledToolbarWrapper>
|
||||
<Tooltip content="Undo" placement="right-start">
|
||||
<Tooltip content={t('Undo')} placement="right-start">
|
||||
<StyledToolbarItem
|
||||
disable={!canUndo}
|
||||
onClick={() => {
|
||||
@@ -105,7 +109,7 @@ const UndoRedo = () => {
|
||||
<UndoIcon />
|
||||
</StyledToolbarItem>
|
||||
</Tooltip>
|
||||
<Tooltip content="Redo" placement="right-start">
|
||||
<Tooltip content={t('Redo')} placement="right-start">
|
||||
<StyledToolbarItem
|
||||
disable={!canRedo}
|
||||
onClick={() => {
|
||||
@@ -131,7 +135,7 @@ export const EdgelessToolbar = () => {
|
||||
>
|
||||
<StyledEdgelessToolbar aria-label="edgeless-toolbar">
|
||||
<StyledToolbarWrapper>
|
||||
{toolbarList1.map(
|
||||
{useToolbarList1().map(
|
||||
({ icon, toolTip, flavor, disable, callback }, index) => {
|
||||
return (
|
||||
<Tooltip key={index} content={toolTip} placement="right-start">
|
||||
|
||||
@@ -11,8 +11,8 @@ import type {
|
||||
AnimateRadioProps,
|
||||
AnimateRadioItemProps,
|
||||
} from './type';
|
||||
import { useTheme } from '@/providers/themeProvider';
|
||||
import { EdgelessIcon, PaperIcon } from './icons';
|
||||
import { useTheme } from '@/providers/ThemeProvider';
|
||||
import { EdgelessIcon, PaperIcon } from './Icons';
|
||||
import useCurrentPageMeta from '@/hooks/use-current-page-meta';
|
||||
import { usePageHelper } from '@/hooks/use-page-helper';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Button } from '@/ui/button';
|
||||
import { FC, useRef, ChangeEvent, ReactElement } from 'react';
|
||||
import { styled } from '@/styles';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
interface Props {
|
||||
uploadType?: string;
|
||||
children?: ReactElement;
|
||||
@@ -9,6 +10,7 @@ interface Props {
|
||||
}
|
||||
export const Upload: FC<Props> = props => {
|
||||
const { fileChange, accept } = props;
|
||||
const { t } = useTranslation();
|
||||
const input_ref = useRef<HTMLInputElement>(null);
|
||||
const _chooseFile = () => {
|
||||
if (input_ref.current) {
|
||||
@@ -28,7 +30,7 @@ export const Upload: FC<Props> = props => {
|
||||
};
|
||||
return (
|
||||
<UploadStyle onClick={_chooseFile}>
|
||||
{props.children ?? <Button>Upload</Button>}
|
||||
{props.children ?? <Button>{t('Upload')}</Button>}
|
||||
<input
|
||||
ref={input_ref}
|
||||
type="file"
|
||||
|
||||
@@ -8,8 +8,8 @@ import {
|
||||
import { Content } from '@/ui/layout';
|
||||
import { useAppState } from '@/providers/app-state-provider/context';
|
||||
import EditorModeSwitch from '@/components/editor-mode-switch';
|
||||
import QuickSearchButton from './quick-search-button';
|
||||
import Header from './header';
|
||||
import QuickSearchButton from './QuickSearchButton';
|
||||
import Header from './Header';
|
||||
import usePropsUpdated from '@/hooks/use-props-updated';
|
||||
import useCurrentPageMeta from '@/hooks/use-current-page-meta';
|
||||
|
||||
@@ -8,10 +8,10 @@ import {
|
||||
} from './styles';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import { getWarningMessage, shouldShowWarning } from './utils';
|
||||
import EditorOptionMenu from './header-right-items/editor-option-menu';
|
||||
import TrashButtonGroup from './header-right-items/trash-button-group';
|
||||
import EditorOptionMenu from './header-right-items/EditorOptionMenu';
|
||||
import TrashButtonGroup from './header-right-items/TrashButtonGroup';
|
||||
import ThemeModeSwitch from './header-right-items/theme-mode-switch';
|
||||
import SyncUser from './header-right-items/sync-user';
|
||||
import SyncUser from './header-right-items/SyncUser';
|
||||
|
||||
const BrowserWarning = ({
|
||||
show,
|
||||
@@ -1,7 +1,7 @@
|
||||
import { PropsWithChildren, ReactNode } from 'react';
|
||||
import Header from './header';
|
||||
import Header from './Header';
|
||||
import { StyledPageListTittleWrapper } from './styles';
|
||||
import QuickSearchButton from './quick-search-button';
|
||||
import QuickSearchButton from './QuickSearchButton';
|
||||
|
||||
export type PageListHeaderProps = PropsWithChildren<{
|
||||
icon?: ReactNode;
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { IconButton, IconButtonProps } from '@/ui/button';
|
||||
import { Tooltip } from '@/ui/tooltip';
|
||||
import { ArrowDownIcon } from '@blocksuite/icons';
|
||||
import { useModal } from '@/providers/global-modal-provider';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
export const QuickSearchButton = ({
|
||||
onClick,
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
} from '@blocksuite/icons';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { usePageHelper } from '@/hooks/use-page-helper';
|
||||
import { useConfirm } from '@/providers/confirm-provider';
|
||||
import { useConfirm } from '@/providers/ConfirmProvider';
|
||||
import useCurrentPageMeta from '@/hooks/use-current-page-meta';
|
||||
import { toast } from '@/ui/toast';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -1,5 +1,5 @@
|
||||
import { CloudUnsyncedIcon, CloudInsyncIcon } from '@blocksuite/icons';
|
||||
import { useModal } from '@/providers/global-modal-provider';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
import { useAppState } from '@/providers/app-state-provider/context';
|
||||
import { IconButton } from '@/ui/button';
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Button } from '@/ui/button';
|
||||
import { usePageHelper } from '@/hooks/use-page-helper';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { useConfirm } from '@/providers/confirm-provider';
|
||||
import { useConfirm } from '@/providers/ConfirmProvider';
|
||||
import { useRouter } from 'next/router';
|
||||
import useCurrentPageMeta from '@/hooks/use-current-page-meta';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const TrashButtonGroup = () => {
|
||||
const { permanentlyDeletePage } = usePageHelper();
|
||||
@@ -12,7 +13,7 @@ export const TrashButtonGroup = () => {
|
||||
const { confirm } = useConfirm();
|
||||
const router = useRouter();
|
||||
const { id = '' } = useCurrentPageMeta() || {};
|
||||
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
@@ -23,7 +24,7 @@ export const TrashButtonGroup = () => {
|
||||
toggleDeletePage(id);
|
||||
}}
|
||||
>
|
||||
Restore it
|
||||
{t('Restore it')}
|
||||
</Button>
|
||||
<Button
|
||||
bold={true}
|
||||
@@ -31,10 +32,9 @@ export const TrashButtonGroup = () => {
|
||||
type="danger"
|
||||
onClick={() => {
|
||||
confirm({
|
||||
title: 'Permanently delete',
|
||||
content:
|
||||
"Once deleted, you can't undo this action. Do you confirm?",
|
||||
confirmText: 'Delete',
|
||||
title: t('TrashButtonGroupTitle'),
|
||||
content: t('TrashButtonGroupDescription'),
|
||||
confirmText: t('Delete'),
|
||||
confirmType: 'danger',
|
||||
}).then(confirm => {
|
||||
if (confirm) {
|
||||
@@ -44,7 +44,7 @@ export const TrashButtonGroup = () => {
|
||||
});
|
||||
}}
|
||||
>
|
||||
Delete permanently
|
||||
{t('Delete permanently')}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { useTheme } from '@/providers/themeProvider';
|
||||
import { MoonIcon, SunIcon } from './icons';
|
||||
import { useTheme } from '@/providers/ThemeProvider';
|
||||
import { MoonIcon, SunIcon } from './Icons';
|
||||
import { StyledThemeModeSwitch, StyledSwitchItem } from './style';
|
||||
|
||||
export const ThemeModeSwitch = () => {
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export * from './header';
|
||||
export * from './editor-header';
|
||||
export * from './page-list-header';
|
||||
export * from './Header';
|
||||
export * from './EditorHeader';
|
||||
export * from './PageListHeader';
|
||||
|
||||
@@ -5,12 +5,12 @@ import {
|
||||
StyledIslandWrapper,
|
||||
StyledTransformIcon,
|
||||
} from './style';
|
||||
import { CloseIcon, ContactIcon, HelpIcon, KeyboardIcon } from './icons';
|
||||
import { CloseIcon, ContactIcon, HelpIcon, KeyboardIcon } from './Icons';
|
||||
import Grow from '@mui/material/Grow';
|
||||
import { Tooltip } from '@/ui/tooltip';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useModal } from '@/providers/global-modal-provider';
|
||||
import { useTheme } from '@/providers/themeProvider';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
import { useTheme } from '@/providers/ThemeProvider';
|
||||
import useCurrentPageMeta from '@/hooks/use-current-page-meta';
|
||||
export type IslandItemNames = 'contact' | 'shortcuts';
|
||||
export const HelpIsland = ({
|
||||
|
||||
@@ -4,8 +4,9 @@ import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal';
|
||||
import { Button } from '@/ui/button';
|
||||
import Input from '@/ui/input';
|
||||
import { useState } from 'react';
|
||||
import { getDataCenter } from '@affine/datacenter';
|
||||
// import { getDataCenter } from '@affine/datacenter';
|
||||
import { Avatar } from '@mui/material';
|
||||
import { setMember } from '@/hooks/mock-data/mock';
|
||||
interface LoginModalProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
@@ -54,29 +55,34 @@ export const InviteMembers = ({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const [userData, setUserData] = useState<any>({});
|
||||
const inputChange = (value: string) => {
|
||||
setEmail(value);
|
||||
setShowMember(true);
|
||||
if (gmailReg.test(value)) {
|
||||
setEmail(value);
|
||||
setShowTip(false);
|
||||
debounce(
|
||||
() => {
|
||||
// getDataCenter()
|
||||
// .then(dc =>
|
||||
// dc.apis.getUserByEmail({
|
||||
// email: value,
|
||||
// workspace_id: workspaceId,
|
||||
// })
|
||||
// )
|
||||
// .then(data => {
|
||||
// if (data?.name) {
|
||||
// setUserData(data);
|
||||
// setShowTip(false);
|
||||
// }
|
||||
// });
|
||||
},
|
||||
300,
|
||||
true
|
||||
)();
|
||||
setUserData({
|
||||
name: 'wxl',
|
||||
avatar: 'https://avatars.githubusercontent.com/u/20501502?v=4',
|
||||
email: value,
|
||||
});
|
||||
// debounce(
|
||||
// () => {
|
||||
// getDataCenter()
|
||||
// .then(dc =>
|
||||
// dc.apis.getUserByEmail({
|
||||
// email: value,
|
||||
// workspace_id: workspaceId,
|
||||
// })
|
||||
// )
|
||||
// .then(data => {
|
||||
// if (data?.name) {
|
||||
// setUserData(data);
|
||||
// setShowTip(false);
|
||||
// }
|
||||
// });
|
||||
// },
|
||||
// 300,
|
||||
// true
|
||||
// )();
|
||||
} else {
|
||||
setShowTip(true);
|
||||
}
|
||||
@@ -134,6 +140,8 @@ export const InviteMembers = ({
|
||||
shape="circle"
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
setMember(workspaceId, userData);
|
||||
onInviteSuccess();
|
||||
// getDataCenter()
|
||||
// .then(dc => dc.apis.inviteMember({ id: workspaceId, email }))
|
||||
// .then(() => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { styled } from '@/styles';
|
||||
import Loading from './loading';
|
||||
import Loading from './Loading';
|
||||
|
||||
// Used for the full page loading
|
||||
const StyledLoadingContainer = styled('div')(() => {
|
||||
@@ -1,3 +1,3 @@
|
||||
import Loading from './loading';
|
||||
export * from './page-loading';
|
||||
import Loading from './Loading';
|
||||
export * from './PageLoading';
|
||||
export default Loading;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { getDataCenter } from '@affine/datacenter';
|
||||
import { styled } from '@/styles';
|
||||
import { Button } from '@/ui/button';
|
||||
import { useModal } from '@/providers/global-modal-provider';
|
||||
import { GoogleIcon, StayLogOutIcon } from './icons';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
import { GoogleIcon, StayLogOutIcon } from './Icons';
|
||||
|
||||
export const GoogleLoginButton = () => {
|
||||
const { triggerLoginModal } = useModal();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useConfirm } from '@/providers/confirm-provider';
|
||||
import { useConfirm } from '@/providers/ConfirmProvider';
|
||||
import { PageMeta } from '@/providers/app-state-provider';
|
||||
import { Menu, MenuItem } from '@/ui/menu';
|
||||
import { Wrapper } from '@/ui/layout';
|
||||
@@ -12,18 +12,18 @@ import {
|
||||
StyledTitleWrapper,
|
||||
} from './styles';
|
||||
import { Table, TableBody, TableCell, TableHead, TableRow } from '@/ui/table';
|
||||
import { OperationCell, TrashOperationCell } from './operation-cell';
|
||||
import Empty from './empty';
|
||||
import { OperationCell, TrashOperationCell } from './OperationCell';
|
||||
import Empty from './Empty';
|
||||
import { Content } from '@/ui/layout';
|
||||
import React from 'react';
|
||||
import DateCell from '@/components/page-list/date-cell';
|
||||
import DateCell from '@/components/page-list/DateCell';
|
||||
import { IconButton } from '@/ui/button';
|
||||
import { Tooltip } from '@/ui/tooltip';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useAppState } from '@/providers/app-state-provider/context';
|
||||
import { toast } from '@/ui/toast';
|
||||
import { usePageHelper } from '@/hooks/use-page-helper';
|
||||
import { useTheme } from '@/providers/themeProvider';
|
||||
import { useTheme } from '@/providers/ThemeProvider';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
const FavoriteTag = ({
|
||||
pageMeta: { favorite, id },
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { AddIcon } from '@blocksuite/icons';
|
||||
import { StyledModalFooterContent } from './style';
|
||||
import { useModal } from '@/providers/global-modal-provider';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
import { Command } from 'cmdk';
|
||||
import { usePageHelper } from '@/hooks/use-page-helper';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Command } from 'cmdk';
|
||||
import { StyledListItem, StyledNotFound } from './style';
|
||||
import { useModal } from '@/providers/global-modal-provider';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
import { PaperIcon, EdgelessIcon } from '@blocksuite/icons';
|
||||
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useSwitchToConfig } from './config';
|
||||
import { NoResultSVG } from './noResultSVG';
|
||||
import { NoResultSVG } from './NoResultSVG';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import usePageHelper from '@/hooks/use-page-helper';
|
||||
import usePageMetaList from '@/hooks/use-page-meta-list';
|
||||
@@ -6,12 +6,12 @@ import {
|
||||
StyledModalDivider,
|
||||
StyledShortcut,
|
||||
} from './style';
|
||||
import { Input } from './input';
|
||||
import { Results } from './results';
|
||||
import { Footer } from './footer';
|
||||
import { Input } from './Input';
|
||||
import { Results } from './Results';
|
||||
import { Footer } from './Footer';
|
||||
import { Command } from 'cmdk';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useModal } from '@/providers/global-modal-provider';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
import { getUaHelper } from '@/utils';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
type TransitionsModalProps = {
|
||||
|
||||
@@ -13,7 +13,8 @@ export const useMacKeyboardShortcuts = (): ShortcutTip => {
|
||||
[t('Strikethrough')]: '⌘+⇧+S',
|
||||
[t('Inline code')]: ' ⌘+E',
|
||||
[t('Code block')]: '⌘+⌥+C',
|
||||
[t('Link')]: '⌘+K',
|
||||
[t('Hyperlink(with selected text)')]: '⌘+K',
|
||||
[t('Quick search')]: '⌘+K',
|
||||
[t('Body text')]: '⌘+⌥+0',
|
||||
[t('Heading', { number: '1' })]: '⌘+⌥+1',
|
||||
[t('Heading', { number: '2' })]: '⌘+⌥+2',
|
||||
@@ -56,7 +57,8 @@ export const useWindowsKeyboardShortcuts = (): ShortcutTip => {
|
||||
[t('Strikethrough')]: 'Ctrl+Shift+S',
|
||||
[t('Inline code')]: ' Ctrl+E',
|
||||
[t('Code block')]: 'Ctrl+Alt+C',
|
||||
[t('Link')]: 'Ctrl+K',
|
||||
[t('Hyperlink(with selected text)')]: 'Ctrl+K',
|
||||
[t('Quick search')]: 'Ctrl+K',
|
||||
[t('Body text')]: 'Ctrl+Shift+0',
|
||||
[t('Heading', { number: '1' })]: 'Ctrl+Shift+1',
|
||||
[t('Heading', { number: '2' })]: 'Ctrl+Shift+2',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createPortal } from 'react-dom';
|
||||
import { KeyboardIcon } from './icons';
|
||||
import { KeyboardIcon } from './Icons';
|
||||
import {
|
||||
StyledListItem,
|
||||
StyledModalHeader,
|
||||
|
||||
30
packages/app/src/components/workspace-avatar/index.tsx
Normal file
30
packages/app/src/components/workspace-avatar/index.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { stringToColour } from '@/utils';
|
||||
|
||||
interface IWorkspaceAvatar {
|
||||
size: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export const WorkspaceAvatar = (props: IWorkspaceAvatar) => {
|
||||
const size = props.size || 20;
|
||||
const sizeStr = size + 'px';
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
width: sizeStr,
|
||||
height: sizeStr,
|
||||
border: '1px solid #fff',
|
||||
color: '#fff',
|
||||
fontSize: Math.ceil(0.5 * size) + 'px',
|
||||
background: stringToColour(props.name || 'AFFiNE'),
|
||||
borderRadius: '50%',
|
||||
textAlign: 'center',
|
||||
lineHeight: size + 'px',
|
||||
}}
|
||||
>
|
||||
{(props.name || 'AFFiNE').substring(0, 1)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
228
packages/app/src/components/workspace-modal/index.tsx
Normal file
228
packages/app/src/components/workspace-modal/index.tsx
Normal file
@@ -0,0 +1,228 @@
|
||||
import { styled } from '@/styles';
|
||||
import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal';
|
||||
import { Button } from '@/ui/button';
|
||||
import { useState } from 'react';
|
||||
import { SignOut } from '@/hooks/mock-data/mock';
|
||||
import { CreateWorkspaceModal } from '../create-workspace';
|
||||
import {
|
||||
CloudUnsyncedIcon,
|
||||
CloudInsyncIcon,
|
||||
UsersIcon,
|
||||
AddIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import { useConfirm } from '@/providers/confirm-provider';
|
||||
import { toast } from '@/ui/toast';
|
||||
import { WorkspaceAvatar } from '@/components/workspace-avatar';
|
||||
import { useTemporaryHelper } from '@/providers/temporary-helper-provider';
|
||||
|
||||
interface LoginModalProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const WorkspaceModal = ({ open, onClose }: LoginModalProps) => {
|
||||
const [createWorkspaceOpen, setCreateWorkspaceOpen] = useState(false);
|
||||
const { confirm } = useConfirm();
|
||||
const {
|
||||
user,
|
||||
login,
|
||||
workspaceMetaList,
|
||||
setActiveWorkspace,
|
||||
updateWorkspaceMeta,
|
||||
} = useTemporaryHelper();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Modal open={open} onClose={onClose}>
|
||||
<ModalWrapper
|
||||
width={820}
|
||||
style={{ padding: '10px', display: 'flex', flexDirection: 'column' }}
|
||||
>
|
||||
<Header>
|
||||
<ContentTitle>My Workspaces</ContentTitle>
|
||||
{/* <LanguageMenu /> */}
|
||||
<ModalCloseButton
|
||||
top={6}
|
||||
right={6}
|
||||
onClick={() => {
|
||||
onClose();
|
||||
}}
|
||||
/>
|
||||
</Header>
|
||||
<Content>
|
||||
<WorkspaceList>
|
||||
{workspaceMetaList.map((item, index) => {
|
||||
return (
|
||||
<WorkspaceItem
|
||||
onClick={() => {
|
||||
setActiveWorkspace(item);
|
||||
onClose();
|
||||
}}
|
||||
key={index}
|
||||
>
|
||||
<span style={{ width: '100px' }}>
|
||||
<div
|
||||
style={{
|
||||
float: 'left',
|
||||
marginTop: '6px',
|
||||
marginLeft: '10px',
|
||||
marginRight: '10px',
|
||||
}}
|
||||
>
|
||||
<WorkspaceAvatar size={50} name={item.name} />
|
||||
</div>
|
||||
|
||||
<span
|
||||
style={{
|
||||
width: '235px',
|
||||
fontSize: '16px',
|
||||
display: 'inline-block',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
top: '20px',
|
||||
}}
|
||||
>
|
||||
{item.name || 'AFFiNE'}
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
style={{
|
||||
position: 'relative',
|
||||
top: '20px',
|
||||
}}
|
||||
>
|
||||
{(item.type === 'local' || !item.type) && (
|
||||
<CloudUnsyncedIcon fontSize={24} />
|
||||
)}
|
||||
{item.type === 'cloud' && (
|
||||
<CloudInsyncIcon fontSize={24} />
|
||||
)}
|
||||
{item.isPublish && <UsersIcon fontSize={24} />}
|
||||
</span>
|
||||
{/* {item.isLocal ? 'isLocal' : ''}/ */}
|
||||
</WorkspaceItem>
|
||||
);
|
||||
})}
|
||||
<li>
|
||||
<Button
|
||||
style={{
|
||||
marginTop: '20px',
|
||||
}}
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
setCreateWorkspaceOpen(true);
|
||||
}}
|
||||
>
|
||||
<AddIcon
|
||||
style={{
|
||||
fontSize: '20px',
|
||||
top: '5px',
|
||||
position: 'relative',
|
||||
marginRight: '10px',
|
||||
}}
|
||||
/>
|
||||
Create Or Import
|
||||
</Button>
|
||||
</li>
|
||||
</WorkspaceList>
|
||||
<p style={{ fontSize: '14px', color: '#ccc', margin: '12px 0' }}>
|
||||
Tips:Workspace is your virtual space to capture, create and plan
|
||||
as just one person or together as a team.
|
||||
</p>
|
||||
</Content>
|
||||
<Footer>
|
||||
{!user ? (
|
||||
<Button
|
||||
onClick={() => {
|
||||
login();
|
||||
toast('login success');
|
||||
}}
|
||||
>
|
||||
Sign in AFFiNE Cloud
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
onClick={() => {
|
||||
SignOut();
|
||||
}}
|
||||
>
|
||||
Sign out of AFFiNE Cloud
|
||||
</Button>
|
||||
)}
|
||||
</Footer>
|
||||
<CreateWorkspaceModal
|
||||
open={createWorkspaceOpen}
|
||||
onClose={({ workspaceId }) => {
|
||||
setCreateWorkspaceOpen(false);
|
||||
onClose();
|
||||
confirm({
|
||||
title: 'Enable AFFiNE Cloud?',
|
||||
content: `If enabled, the data in this workspace will be backed up and synchronized via AFFiNE Cloud.`,
|
||||
confirmText: user ? 'Enable' : 'Sign in and Enable',
|
||||
cancelText: 'Skip',
|
||||
}).then(confirm => {
|
||||
if (confirm) {
|
||||
if (user) {
|
||||
workspaceId &&
|
||||
updateWorkspaceMeta(workspaceId, { isPublish: true });
|
||||
} else {
|
||||
login();
|
||||
workspaceId &&
|
||||
updateWorkspaceMeta(workspaceId, { isPublish: true });
|
||||
}
|
||||
}
|
||||
});
|
||||
}}
|
||||
></CreateWorkspaceModal>
|
||||
</ModalWrapper>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Header = styled('div')({
|
||||
position: 'relative',
|
||||
height: '44px',
|
||||
});
|
||||
|
||||
const Content = styled('div')({
|
||||
padding: '0 20px',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
gap: '16px',
|
||||
flex: 1,
|
||||
});
|
||||
|
||||
const ContentTitle = styled('span')({
|
||||
fontSize: '20px',
|
||||
lineHeight: '28px',
|
||||
fontWeight: 600,
|
||||
textAlign: 'left',
|
||||
paddingBottom: '16px',
|
||||
});
|
||||
|
||||
const Footer = styled('div')({
|
||||
height: '70px',
|
||||
paddingLeft: '24px',
|
||||
marginTop: '32px',
|
||||
textAlign: 'center',
|
||||
});
|
||||
|
||||
const WorkspaceList = styled('div')({
|
||||
display: 'grid',
|
||||
gridRowGap: '10px',
|
||||
gridColumnGap: '10px',
|
||||
fontSize: '16px',
|
||||
gridTemplateColumns: 'repeat(2, 1fr)',
|
||||
});
|
||||
|
||||
const WorkspaceItem = styled('div')({
|
||||
cursor: 'pointer',
|
||||
padding: '8px',
|
||||
border: '1px solid #eee',
|
||||
':hover': {
|
||||
background: '#eee',
|
||||
},
|
||||
});
|
||||
95
packages/app/src/components/workspace-modal/languageMenu.tsx
Normal file
95
packages/app/src/components/workspace-modal/languageMenu.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import { LOCALES } from '@/libs/i18n/resources/index';
|
||||
import UnfoldMoreIcon from '@mui/icons-material/UnfoldMore';
|
||||
import type { TooltipProps } from '@mui/material';
|
||||
import { styled } from '@/styles';
|
||||
import { Button, Tooltip } from '@mui/material';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const LanguageMenu = () => {
|
||||
const { i18n } = useTranslation();
|
||||
const changeLanguage = (event: string) => {
|
||||
i18n.changeLanguage(event);
|
||||
};
|
||||
const [show, setShow] = useState(false);
|
||||
const currentLanguage = LOCALES.find(item => item.tag === i18n.language);
|
||||
const [languageName, setLanguageName] = useState(
|
||||
currentLanguage?.originalName
|
||||
);
|
||||
return (
|
||||
<StyledTooltip
|
||||
title={
|
||||
<>
|
||||
{LOCALES.map(option => {
|
||||
return (
|
||||
<ListItem
|
||||
key={option.name}
|
||||
title={option.name}
|
||||
onClick={() => {
|
||||
changeLanguage(option.tag);
|
||||
setShow(false);
|
||||
setLanguageName(option.originalName);
|
||||
}}
|
||||
>
|
||||
{option.originalName}
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
}
|
||||
open={show}
|
||||
>
|
||||
<StyledTitleButton
|
||||
variant="text"
|
||||
onClick={() => {
|
||||
setShow(!show);
|
||||
}}
|
||||
>
|
||||
<StyledContainer>
|
||||
<StyledText>{languageName}</StyledText>
|
||||
<UnfoldMoreIcon />
|
||||
</StyledContainer>
|
||||
</StyledTitleButton>
|
||||
</StyledTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
const StyledContainer = styled('div')(() => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
}));
|
||||
|
||||
const StyledText = styled('span')(({ theme }) => ({
|
||||
marginRight: '4px',
|
||||
marginLeft: '16px',
|
||||
fontSize: theme.font.sm,
|
||||
fontWeight: '500',
|
||||
textTransform: 'capitalize',
|
||||
}));
|
||||
const StyledTooltip = styled(({ className, ...props }: TooltipProps) => (
|
||||
<Tooltip {...props} classes={{ popper: className }} />
|
||||
))(({ theme }) => ({
|
||||
zIndex: theme.zIndex.modal,
|
||||
'& .MuiTooltip-tooltip': {
|
||||
backgroundColor: theme.colors.popoverBackground,
|
||||
boxShadow: theme.shadow.modal,
|
||||
color: theme.colors.popoverColor,
|
||||
},
|
||||
}));
|
||||
|
||||
const ListItem = styled(Button)(({ theme }) => ({
|
||||
display: 'block',
|
||||
width: '100%',
|
||||
color: theme.colors.popoverColor,
|
||||
fontSize: theme.font.sm,
|
||||
textTransform: 'capitalize',
|
||||
}));
|
||||
|
||||
const StyledTitleButton = styled(Button)(({ theme }) => ({
|
||||
position: 'absolute',
|
||||
right: '50px',
|
||||
color: theme.colors.popoverColor,
|
||||
fontSize: theme.font.sm,
|
||||
}));
|
||||
38
packages/app/src/components/workspace-modal/styles.ts
Normal file
38
packages/app/src/components/workspace-modal/styles.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { displayFlex, styled } from '@/styles';
|
||||
|
||||
export const StyledTitle = styled.div(() => {
|
||||
return {
|
||||
...displayFlex('center', 'center'),
|
||||
fontSize: '20px',
|
||||
fontWeight: 500,
|
||||
marginTop: '60px',
|
||||
lineHeight: 1,
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledContent = styled.div(() => {
|
||||
return {
|
||||
padding: '0 40px',
|
||||
marginTop: '32px',
|
||||
fontSize: '18px',
|
||||
lineHeight: '25px',
|
||||
'p:not(last-of-type)': {
|
||||
marginBottom: '10px',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledButton = styled.div(({ theme }) => {
|
||||
return {
|
||||
width: '146px',
|
||||
height: '42px',
|
||||
background: theme.colors.primaryColor,
|
||||
color: '#FFFFFF',
|
||||
fontSize: '18px',
|
||||
fontWeight: 500,
|
||||
borderRadius: '21px',
|
||||
margin: '52px auto 0',
|
||||
cursor: 'pointer',
|
||||
...displayFlex('center', 'center'),
|
||||
};
|
||||
});
|
||||
19
packages/app/src/components/workspace-setting/ExportPage.tsx
Normal file
19
packages/app/src/components/workspace-setting/ExportPage.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Workspace } from '@/hooks/mock-data/mock';
|
||||
import { styled } from '@/styles';
|
||||
|
||||
export const ExportPageTitleContainer = styled('div')(() => {
|
||||
return {
|
||||
display: 'flex',
|
||||
marginTop: '60px',
|
||||
fontWeight: '500',
|
||||
flex: 1,
|
||||
};
|
||||
});
|
||||
export const ExportPage = ({ workspace }: { workspace: Workspace }) => {
|
||||
return (
|
||||
<ExportPageTitleContainer>
|
||||
Export Workspace{' '}
|
||||
<code style={{ margin: '0 10px' }}>{workspace.name}</code> Is Comming
|
||||
</ExportPageTitleContainer>
|
||||
);
|
||||
};
|
||||
206
packages/app/src/components/workspace-setting/MembersPage.tsx
Normal file
206
packages/app/src/components/workspace-setting/MembersPage.tsx
Normal file
@@ -0,0 +1,206 @@
|
||||
import {
|
||||
StyledMemberAvatar,
|
||||
StyledMemberButtonContainer,
|
||||
StyledMemberEmail,
|
||||
StyledMemberInfo,
|
||||
StyledMemberListContainer,
|
||||
StyledMemberListItem,
|
||||
StyledMemberName,
|
||||
StyledMemberNameContainer,
|
||||
StyledMemberRoleContainer,
|
||||
StyledMemberTitleContainer,
|
||||
StyledMoreVerticalButton,
|
||||
} from './style';
|
||||
import { MoreVerticalIcon, EmailIcon, TrashIcon } from '@blocksuite/icons';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Button, IconButton } from '@/ui/button';
|
||||
import { InviteMembers } from '../invite-members/index';
|
||||
import { Menu, MenuItem } from '@/ui/menu';
|
||||
import { Empty } from '@/ui/empty';
|
||||
import {
|
||||
deleteMember,
|
||||
getMembers,
|
||||
User,
|
||||
Workspace,
|
||||
} from '@/hooks/mock-data/mock';
|
||||
import { useTemporaryHelper } from '@/providers/temporary-helper-provider';
|
||||
import { StyledMemberWarp } from './general/style';
|
||||
import { useConfirm } from '@/providers/ConfirmProvider';
|
||||
|
||||
// import { useAppState } from '@/providers/app-state-provider';
|
||||
export const MembersPage = ({ workspace }: { workspace: Workspace }) => {
|
||||
const [isInviteModalShow, setIsInviteModalShow] = useState(false);
|
||||
const [members, setMembers] = useState<User[]>([]);
|
||||
const { user, login, updateWorkspaceMeta } = useTemporaryHelper();
|
||||
const { confirm } = useConfirm();
|
||||
// const refreshMembers = useCallback(() => {
|
||||
// getDataCenter()
|
||||
// .then(dc =>
|
||||
// dc.apis.getWorkspaceMembers({
|
||||
// id: workspace.id,
|
||||
// })
|
||||
// )
|
||||
// .then(data => {
|
||||
// setMembers(data);
|
||||
// })
|
||||
// .catch(err => {
|
||||
// console.log(err);
|
||||
// });
|
||||
// }, [workspace.id]);
|
||||
const setMembersList = () => {
|
||||
const members = getMembers(workspace.id);
|
||||
members && setMembers(members);
|
||||
};
|
||||
useEffect(() => {
|
||||
setMembersList();
|
||||
// refreshMembers();
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
{workspace.type === 'cloud' ? (
|
||||
<>
|
||||
<StyledMemberTitleContainer>
|
||||
<StyledMemberNameContainer>
|
||||
Users({members.length})
|
||||
</StyledMemberNameContainer>
|
||||
<StyledMemberRoleContainer>Access level</StyledMemberRoleContainer>
|
||||
</StyledMemberTitleContainer>
|
||||
<StyledMemberListContainer>
|
||||
{members.length === 0 && (
|
||||
<Empty
|
||||
width={648}
|
||||
sx={{ marginTop: '60px' }}
|
||||
height={300}
|
||||
></Empty>
|
||||
)}
|
||||
{members.length ? (
|
||||
members.map((member, index) => {
|
||||
return (
|
||||
<StyledMemberListItem key={index}>
|
||||
<StyledMemberNameContainer>
|
||||
<StyledMemberAvatar alt="member avatar">
|
||||
<EmailIcon></EmailIcon>
|
||||
</StyledMemberAvatar>
|
||||
|
||||
<StyledMemberInfo>
|
||||
<StyledMemberName>{member.name}</StyledMemberName>
|
||||
|
||||
<StyledMemberEmail>{member.email}</StyledMemberEmail>
|
||||
</StyledMemberInfo>
|
||||
</StyledMemberNameContainer>
|
||||
<StyledMemberRoleContainer>
|
||||
{/* {member.accepted
|
||||
? member.type !== 99
|
||||
? 'Member'
|
||||
: 'Workspace Owner'
|
||||
: 'Pending'} */}
|
||||
Pending
|
||||
</StyledMemberRoleContainer>
|
||||
<StyledMoreVerticalButton>
|
||||
<Menu
|
||||
content={
|
||||
<>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
deleteMember(workspace.id, 0);
|
||||
setMembersList();
|
||||
// confirm({
|
||||
// title: 'Delete Member?',
|
||||
// content: `will delete member`,
|
||||
// confirmText: 'Delete',
|
||||
// confirmType: 'danger',
|
||||
// }).then(confirm => {
|
||||
// getDataCenter()
|
||||
// .then(dc =>
|
||||
// dc.apis.removeMember({
|
||||
// permissionId: member.id,
|
||||
// })
|
||||
// )
|
||||
// .then(() => {
|
||||
// // console.log('data: ', data);
|
||||
// toast('Moved to Trash');
|
||||
// // refreshMembers();
|
||||
// });
|
||||
// });
|
||||
}}
|
||||
icon={<TrashIcon />}
|
||||
>
|
||||
Delete
|
||||
</MenuItem>
|
||||
</>
|
||||
}
|
||||
placement="bottom-end"
|
||||
disablePortal={true}
|
||||
>
|
||||
<IconButton>
|
||||
<MoreVerticalIcon />
|
||||
</IconButton>
|
||||
</Menu>
|
||||
</StyledMoreVerticalButton>
|
||||
</StyledMemberListItem>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</StyledMemberListContainer>
|
||||
<StyledMemberButtonContainer>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setIsInviteModalShow(true);
|
||||
}}
|
||||
type="primary"
|
||||
shape="circle"
|
||||
>
|
||||
Invite Members
|
||||
</Button>
|
||||
<InviteMembers
|
||||
onClose={() => {
|
||||
setIsInviteModalShow(false);
|
||||
}}
|
||||
onInviteSuccess={() => {
|
||||
setMembersList();
|
||||
setIsInviteModalShow(false);
|
||||
// refreshMembers();
|
||||
}}
|
||||
workspaceId={workspace.id}
|
||||
open={isInviteModalShow}
|
||||
></InviteMembers>
|
||||
</StyledMemberButtonContainer>
|
||||
</>
|
||||
) : (
|
||||
<StyledMemberWarp>
|
||||
<div style={{ flex: 1 }}>
|
||||
Collaborating with other members requires AFFiNE Cloud service.
|
||||
</div>
|
||||
<div style={{ height: '40px' }}>
|
||||
<Button
|
||||
type="primary"
|
||||
shape="circle"
|
||||
onClick={() => {
|
||||
confirm({
|
||||
title: 'Enable AFFiNE Cloud?',
|
||||
content: `If enabled, the data in this workspace will be backed up and synchronized via AFFiNE Cloud.`,
|
||||
confirmText: user ? 'Enable' : 'Sign in and Enable',
|
||||
cancelText: 'Skip',
|
||||
}).then(confirm => {
|
||||
if (confirm) {
|
||||
if (user) {
|
||||
updateWorkspaceMeta(workspace.id, { isPublish: true });
|
||||
} else {
|
||||
login();
|
||||
updateWorkspaceMeta(workspace.id, { isPublish: true });
|
||||
}
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
Enable AFFiNE Cloud
|
||||
</Button>
|
||||
</div>
|
||||
</StyledMemberWarp>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
122
packages/app/src/components/workspace-setting/PublishPage.tsx
Normal file
122
packages/app/src/components/workspace-setting/PublishPage.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
import {
|
||||
StyledCopyButtonContainer,
|
||||
StyledPublishContent,
|
||||
StyledPublishCopyContainer,
|
||||
StyledPublishExplanation,
|
||||
StyledSettingH2,
|
||||
} from './style';
|
||||
|
||||
import { Button } from '@/ui/button';
|
||||
import Input from '@/ui/input';
|
||||
import { toast } from '@/ui/toast';
|
||||
import { Workspace } from '@/hooks/mock-data/mock';
|
||||
import { useTemporaryHelper } from '@/providers/temporary-helper-provider';
|
||||
import { useConfirm } from '@/providers/ConfirmProvider';
|
||||
|
||||
export const PublishPage = ({ workspace }: { workspace: Workspace }) => {
|
||||
console.log('workspace: ', workspace);
|
||||
const shareUrl =
|
||||
window.location.host + '/workspace/' + workspace.id + '?share=true';
|
||||
|
||||
const { login, updateWorkspaceMeta, user } = useTemporaryHelper();
|
||||
const { confirm } = useConfirm();
|
||||
|
||||
const togglePublic = (flag: boolean) => {
|
||||
updateWorkspaceMeta(workspace.id, { isPublish: flag });
|
||||
};
|
||||
|
||||
const copyUrl = () => {
|
||||
navigator.clipboard.writeText(shareUrl);
|
||||
toast('Copied url to clipboard');
|
||||
};
|
||||
|
||||
const enableAffineCloud = () => {
|
||||
confirm({
|
||||
title: 'Enable AFFiNE Cloud?',
|
||||
content: `If enabled, the data in this workspace will be backed up and synchronized via AFFiNE Cloud.`,
|
||||
confirmText: user ? 'Enable' : 'Sign in and Enable',
|
||||
cancelText: 'Skip',
|
||||
}).then(confirm => {
|
||||
if (confirm) {
|
||||
if (user) {
|
||||
updateWorkspaceMeta(workspace.id, { type: 'cloud' });
|
||||
} else {
|
||||
login();
|
||||
updateWorkspaceMeta(workspace.id, { type: 'cloud' });
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{workspace.type === 'cloud' ? (
|
||||
<div>
|
||||
<StyledPublishContent>
|
||||
{workspace?.isPublish ? (
|
||||
<>
|
||||
<StyledPublishExplanation>
|
||||
Publishing to web requires AFFiNE Cloud service .
|
||||
</StyledPublishExplanation>
|
||||
<StyledSettingH2>Share with link</StyledSettingH2>
|
||||
<StyledPublishCopyContainer>
|
||||
<Input width={500} value={shareUrl} disabled={true}></Input>
|
||||
<StyledCopyButtonContainer>
|
||||
<Button onClick={copyUrl} type="primary" shape="circle">
|
||||
Copy Link
|
||||
</Button>
|
||||
</StyledCopyButtonContainer>
|
||||
</StyledPublishCopyContainer>
|
||||
</>
|
||||
) : (
|
||||
<StyledPublishExplanation>
|
||||
After publishing to the web, everyone can view the content of
|
||||
this workspace through the link.
|
||||
</StyledPublishExplanation>
|
||||
)}
|
||||
</StyledPublishContent>
|
||||
{workspace.isPublish ? (
|
||||
<Button
|
||||
onClick={() => {
|
||||
togglePublic(false);
|
||||
}}
|
||||
type="primary"
|
||||
shape="circle"
|
||||
>
|
||||
Stop publishing
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
onClick={() => {
|
||||
togglePublic(true);
|
||||
}}
|
||||
type="primary"
|
||||
shape="circle"
|
||||
>
|
||||
Publish to web
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<StyledPublishContent>
|
||||
<>
|
||||
<StyledPublishExplanation>
|
||||
Publishing to web requires AFFiNE Cloud service.
|
||||
</StyledPublishExplanation>
|
||||
|
||||
<StyledPublishCopyContainer>
|
||||
<Button
|
||||
onClick={() => {
|
||||
enableAffineCloud();
|
||||
}}
|
||||
type="primary"
|
||||
shape="circle"
|
||||
>
|
||||
Enable AFFiNE Cloud
|
||||
</Button>
|
||||
</StyledPublishCopyContainer>
|
||||
</>
|
||||
</StyledPublishContent>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
79
packages/app/src/components/workspace-setting/SyncPage.tsx
Normal file
79
packages/app/src/components/workspace-setting/SyncPage.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import {
|
||||
StyledPublishContent,
|
||||
StyledPublishCopyContainer,
|
||||
StyledPublishExplanation,
|
||||
} from './style';
|
||||
import { DownloadIcon } from '@blocksuite/icons';
|
||||
import { Button } from '@/ui/button';
|
||||
import { Menu, MenuItem } from '@/ui/menu';
|
||||
import { deleteMember, Workspace } from '@/hooks/mock-data/mock';
|
||||
import { useTemporaryHelper } from '@/providers/temporary-helper-provider';
|
||||
|
||||
export const SyncPage = ({ workspace }: { workspace: Workspace }) => {
|
||||
const { currentWorkspace, updateWorkspaceMeta } = useTemporaryHelper();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<StyledPublishContent>
|
||||
{currentWorkspace?.type === 'local' ? (
|
||||
<>
|
||||
<StyledPublishExplanation>
|
||||
{currentWorkspace.name} is Local Workspace. All data is stored on
|
||||
the current device. You can enable AFFiNE Cloud for this workspace
|
||||
to keep data in sync with the cloud.
|
||||
</StyledPublishExplanation>
|
||||
|
||||
<StyledPublishCopyContainer>
|
||||
<Button
|
||||
onClick={() => {
|
||||
updateWorkspaceMeta(currentWorkspace.id, {
|
||||
type: 'cloud',
|
||||
});
|
||||
}}
|
||||
type="primary"
|
||||
shape="circle"
|
||||
>
|
||||
Enable AFFiNE Cloud
|
||||
</Button>
|
||||
</StyledPublishCopyContainer>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<StyledPublishExplanation>
|
||||
<code>{currentWorkspace && currentWorkspace.name}</code> is Cloud
|
||||
Workspace. All data will be synchronized and saved to the AFFiNE
|
||||
</StyledPublishExplanation>
|
||||
<StyledPublishCopyContainer>
|
||||
<Menu
|
||||
content={
|
||||
<>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
deleteMember(workspace.id, 0);
|
||||
}}
|
||||
icon={<DownloadIcon />}
|
||||
>
|
||||
Download core data to device
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
deleteMember(workspace.id, 0);
|
||||
}}
|
||||
icon={<DownloadIcon />}
|
||||
>
|
||||
Download all data to device
|
||||
</MenuItem>
|
||||
</>
|
||||
}
|
||||
placement="bottom-end"
|
||||
disablePortal={true}
|
||||
>
|
||||
<Button>Download all data to device</Button>
|
||||
</Menu>
|
||||
</StyledPublishCopyContainer>
|
||||
</>
|
||||
)}
|
||||
</StyledPublishContent>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,164 @@
|
||||
import Modal, { ModalCloseButton } from '@/ui/modal';
|
||||
import {
|
||||
StyledSettingContainer,
|
||||
StyledSettingContent,
|
||||
StyledSettingSidebar,
|
||||
StyledSettingSidebarHeader,
|
||||
StyledSettingTabContainer,
|
||||
StyledSettingTagIconContainer,
|
||||
WorkspaceSettingTagItem,
|
||||
} from './style';
|
||||
import {
|
||||
EditIcon,
|
||||
UsersIcon,
|
||||
PublishIcon,
|
||||
CloudInsyncIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { GeneralPage } from './general';
|
||||
import { MembersPage } from './MembersPage';
|
||||
import { PublishPage } from './PublishPage';
|
||||
import { ExportPage } from './ExportPage';
|
||||
import { SyncPage } from './SyncPage';
|
||||
import { useTemporaryHelper } from '@/providers/temporary-helper-provider';
|
||||
|
||||
enum ActiveTab {
|
||||
'general' = 'general',
|
||||
'members' = 'members',
|
||||
'publish' = 'publish',
|
||||
'sync' = 'sync',
|
||||
'export' = 'export',
|
||||
}
|
||||
|
||||
type SettingTabProps = {
|
||||
activeTab: ActiveTab;
|
||||
onTabChange?: (tab: ActiveTab) => void;
|
||||
};
|
||||
|
||||
type WorkspaceSettingProps = {
|
||||
isShow: boolean;
|
||||
onClose?: () => void;
|
||||
};
|
||||
|
||||
const WorkspaceSettingTab = ({ activeTab, onTabChange }: SettingTabProps) => {
|
||||
return (
|
||||
<StyledSettingTabContainer>
|
||||
<WorkspaceSettingTagItem
|
||||
isActive={activeTab === ActiveTab.general}
|
||||
onClick={() => {
|
||||
onTabChange && onTabChange(ActiveTab.general);
|
||||
}}
|
||||
>
|
||||
<StyledSettingTagIconContainer>
|
||||
<EditIcon />
|
||||
</StyledSettingTagIconContainer>
|
||||
General
|
||||
</WorkspaceSettingTagItem>
|
||||
|
||||
<WorkspaceSettingTagItem
|
||||
isActive={activeTab === ActiveTab.sync}
|
||||
onClick={() => {
|
||||
onTabChange && onTabChange(ActiveTab.sync);
|
||||
}}
|
||||
>
|
||||
<StyledSettingTagIconContainer>
|
||||
<CloudInsyncIcon />
|
||||
</StyledSettingTagIconContainer>
|
||||
Sync
|
||||
</WorkspaceSettingTagItem>
|
||||
|
||||
<WorkspaceSettingTagItem
|
||||
isActive={activeTab === ActiveTab.members}
|
||||
onClick={() => {
|
||||
onTabChange && onTabChange(ActiveTab.members);
|
||||
}}
|
||||
>
|
||||
<StyledSettingTagIconContainer>
|
||||
<UsersIcon />
|
||||
</StyledSettingTagIconContainer>
|
||||
Collaboration
|
||||
</WorkspaceSettingTagItem>
|
||||
<WorkspaceSettingTagItem
|
||||
isActive={activeTab === ActiveTab.publish}
|
||||
onClick={() => {
|
||||
onTabChange && onTabChange(ActiveTab.publish);
|
||||
}}
|
||||
>
|
||||
<StyledSettingTagIconContainer>
|
||||
<PublishIcon />
|
||||
</StyledSettingTagIconContainer>
|
||||
Publish
|
||||
</WorkspaceSettingTagItem>
|
||||
|
||||
<WorkspaceSettingTagItem
|
||||
isActive={activeTab === ActiveTab.export}
|
||||
onClick={() => {
|
||||
onTabChange && onTabChange(ActiveTab.export);
|
||||
}}
|
||||
>
|
||||
<StyledSettingTagIconContainer>
|
||||
<PublishIcon />
|
||||
</StyledSettingTagIconContainer>
|
||||
Export
|
||||
</WorkspaceSettingTagItem>
|
||||
</StyledSettingTabContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export const WorkspaceSetting = ({
|
||||
isShow,
|
||||
onClose,
|
||||
}: WorkspaceSettingProps) => {
|
||||
// const { workspaces } = useAppState();
|
||||
const [activeTab, setActiveTab] = useState<ActiveTab>(ActiveTab.general);
|
||||
const handleTabChange = (tab: ActiveTab) => {
|
||||
setActiveTab(tab);
|
||||
};
|
||||
|
||||
const { currentWorkspace } = useTemporaryHelper();
|
||||
const handleClickClose = () => {
|
||||
onClose && onClose();
|
||||
};
|
||||
const isOwner = true;
|
||||
useEffect(() => {
|
||||
// reset tab when modal is closed
|
||||
if (!isShow) {
|
||||
setActiveTab(ActiveTab.general);
|
||||
}
|
||||
}, [isShow]);
|
||||
return (
|
||||
<Modal open={isShow}>
|
||||
<StyledSettingContainer>
|
||||
<ModalCloseButton onClick={handleClickClose} />
|
||||
{isOwner ? (
|
||||
<StyledSettingSidebar>
|
||||
<StyledSettingSidebarHeader>
|
||||
Workspace Settings
|
||||
</StyledSettingSidebarHeader>
|
||||
<WorkspaceSettingTab
|
||||
activeTab={activeTab}
|
||||
onTabChange={handleTabChange}
|
||||
/>
|
||||
</StyledSettingSidebar>
|
||||
) : null}
|
||||
<StyledSettingContent>
|
||||
{activeTab === ActiveTab.general && currentWorkspace && (
|
||||
<GeneralPage workspace={currentWorkspace} />
|
||||
)}
|
||||
{activeTab === ActiveTab.sync && currentWorkspace && (
|
||||
<SyncPage workspace={currentWorkspace} />
|
||||
)}
|
||||
{activeTab === ActiveTab.members && currentWorkspace && (
|
||||
<MembersPage workspace={currentWorkspace} />
|
||||
)}
|
||||
{activeTab === ActiveTab.publish && currentWorkspace && (
|
||||
<PublishPage workspace={currentWorkspace} />
|
||||
)}
|
||||
{activeTab === ActiveTab.export && currentWorkspace && (
|
||||
<ExportPage workspace={currentWorkspace} />
|
||||
)}
|
||||
</StyledSettingContent>
|
||||
</StyledSettingContainer>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -1,62 +1,37 @@
|
||||
import {
|
||||
StyledDeleteButtonContainer,
|
||||
StyledSettingAvatar,
|
||||
// StyledSettingAvatar,
|
||||
StyledSettingAvatarContent,
|
||||
StyledSettingInputContainer,
|
||||
} from './style';
|
||||
import { StyledSettingH2 } from '../style';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Button } from '@/ui/button';
|
||||
import { Button, TextButton } from '@/ui/button';
|
||||
import Input from '@/ui/input';
|
||||
import { getDataCenter, Workspace, WorkspaceType } from '@affine/datacenter';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { WorkspaceDetails } from '@/components/workspace-slider-bar/WorkspaceSelector/SelectorPopperContent';
|
||||
// import { useAppState } from '@/providers/app-state-provider';
|
||||
import { WorkspaceDelete } from './delete';
|
||||
import { Workspace as StoreWorkspace } from '@blocksuite/store';
|
||||
import { debounce } from '@/utils';
|
||||
// import { debounce } from '@/utils';
|
||||
import { WorkspaceLeave } from './leave';
|
||||
import { Upload } from '@/components/file-upload';
|
||||
|
||||
export const GeneralPage = ({
|
||||
workspace,
|
||||
owner,
|
||||
}: {
|
||||
workspace: Workspace;
|
||||
owner: WorkspaceDetails[string]['owner'];
|
||||
workspaces: Record<string, StoreWorkspace | null>;
|
||||
}) => {
|
||||
const {
|
||||
user,
|
||||
currentWorkspace,
|
||||
workspacesMeta,
|
||||
workspaces,
|
||||
refreshWorkspacesMeta,
|
||||
} = useAppState();
|
||||
import { Workspace } from '@/hooks/mock-data/mock';
|
||||
import { WorkspaceAvatar } from '@/components/workspace-avatar';
|
||||
import { useTemporaryHelper } from '@/providers/temporary-helper-provider';
|
||||
export const GeneralPage = ({ workspace }: { workspace: Workspace }) => {
|
||||
// const { refreshWorkspacesMeta } = useAppState();
|
||||
const { updateWorkspaceMeta } = useTemporaryHelper();
|
||||
const [showDelete, setShowDelete] = useState<boolean>(false);
|
||||
const [showLeave, setShowLeave] = useState<boolean>(false);
|
||||
const [uploading, setUploading] = useState<boolean>(false);
|
||||
const [workspaceName, setWorkspaceName] = useState<string>(
|
||||
workspaces[workspace.id]?.meta.name ||
|
||||
(workspace.type === WorkspaceType.Private && user ? user.name : '')
|
||||
);
|
||||
const debouncedRefreshWorkspacesMeta = debounce(() => {
|
||||
refreshWorkspacesMeta();
|
||||
}, 100);
|
||||
const isOwner = user && owner.id === user.id;
|
||||
const [workspaceName, setWorkspaceName] = useState<string>('');
|
||||
// const debouncedRefreshWorkspacesMeta = debounce(() => {
|
||||
// refreshWorkspacesMeta();
|
||||
// }, 100);
|
||||
const isOwner = true;
|
||||
|
||||
const handleChangeWorkSpaceName = (newName: string) => {
|
||||
setWorkspaceName(newName);
|
||||
currentWorkspace?.meta.setName(newName);
|
||||
workspaces[workspace.id]?.meta.setName(newName);
|
||||
debouncedRefreshWorkspacesMeta();
|
||||
};
|
||||
const currentWorkspaceIndex = workspacesMeta.findIndex(
|
||||
meta => meta.id === workspace.id
|
||||
);
|
||||
const nextWorkSpaceId =
|
||||
currentWorkspaceIndex === workspacesMeta.length - 1
|
||||
? workspacesMeta[currentWorkspaceIndex - 1]?.id
|
||||
: workspacesMeta[currentWorkspaceIndex + 1]?.id;
|
||||
const handleClickDelete = () => {
|
||||
setShowDelete(true);
|
||||
};
|
||||
@@ -70,9 +45,13 @@ export const GeneralPage = ({
|
||||
const handleCloseLeave = () => {
|
||||
setShowLeave(false);
|
||||
};
|
||||
const handleUpdateWorkspaceName = () => {
|
||||
workspace && updateWorkspaceMeta(workspace.id, { name: workspaceName });
|
||||
};
|
||||
|
||||
const fileChange = async (file: File) => {
|
||||
// setUploading(true);
|
||||
console.log('file: ', file);
|
||||
setUploading(true);
|
||||
// const blob = new Blob([file], { type: file.type });
|
||||
// const blobId = await getDataCenter()
|
||||
// .then(dc => dc.apis.uploadBlob({ blob }))
|
||||
@@ -81,7 +60,7 @@ export const GeneralPage = ({
|
||||
// });
|
||||
// if (blobId) {
|
||||
// currentWorkspace?.meta.setAvatar(blobId);
|
||||
// workspaces[workspace.id]?.meta.setAvatar(blobId);
|
||||
// // workspaces[workspace.id]?.meta.setAvatar(blobId);
|
||||
// setUploading(false);
|
||||
// debouncedRefreshWorkspacesMeta();
|
||||
// }
|
||||
@@ -89,18 +68,17 @@ export const GeneralPage = ({
|
||||
|
||||
return workspace ? (
|
||||
<div>
|
||||
<StyledSettingH2 marginTop={56}>Workspace Avatar</StyledSettingH2>
|
||||
<StyledSettingH2 marginTop={56}>Workspace Icon</StyledSettingH2>
|
||||
<StyledSettingAvatarContent>
|
||||
<StyledSettingAvatar
|
||||
alt="workspace avatar"
|
||||
src={
|
||||
workspaces[workspace.id]?.meta.avatar
|
||||
? '/api/blob/' + workspaces[workspace.id]?.meta.avatar
|
||||
: ''
|
||||
}
|
||||
<div
|
||||
style={{
|
||||
float: 'left',
|
||||
|
||||
marginRight: '5px',
|
||||
}}
|
||||
>
|
||||
{workspaces[workspace.id]?.meta.name[0]}
|
||||
</StyledSettingAvatar>
|
||||
<WorkspaceAvatar size={60} name={workspace.name} />
|
||||
</div>
|
||||
<Upload
|
||||
accept="image/gif,image/jpeg,image/jpg,image/png,image/svg"
|
||||
fileChange={fileChange}
|
||||
@@ -108,31 +86,45 @@ export const GeneralPage = ({
|
||||
<Button loading={uploading}>Upload</Button>
|
||||
</Upload>
|
||||
{/* TODO: add upload logic */}
|
||||
{/* {isOwner ? (
|
||||
<StyledAvatarUploadBtn shape="round">upload</StyledAvatarUploadBtn>
|
||||
) : null} */}
|
||||
{/* <Button shape="round">remove</Button> */}
|
||||
</StyledSettingAvatarContent>
|
||||
<StyledSettingH2 marginTop={36}>Workspace Name</StyledSettingH2>
|
||||
<StyledSettingH2 marginTop={20}>Workspace Name</StyledSettingH2>
|
||||
<StyledSettingInputContainer>
|
||||
<Input
|
||||
width={327}
|
||||
value={workspaceName}
|
||||
placeholder="Workspace Name"
|
||||
disabled={!isOwner}
|
||||
maxLength={14}
|
||||
minLength={1}
|
||||
onChange={handleChangeWorkSpaceName}
|
||||
></Input>
|
||||
<TextButton
|
||||
onClick={() => {
|
||||
handleUpdateWorkspaceName();
|
||||
}}
|
||||
style={{ marginLeft: '10px' }}
|
||||
>
|
||||
✔️
|
||||
</TextButton>
|
||||
</StyledSettingInputContainer>
|
||||
<StyledSettingH2 marginTop={36}>Workspace Owner</StyledSettingH2>
|
||||
{/* {userInfo ? (
|
||||
<div>
|
||||
<StyledSettingH2 marginTop={20}>Workspace Owner</StyledSettingH2>
|
||||
<StyledSettingInputContainer>
|
||||
<Input
|
||||
width={327}
|
||||
disabled
|
||||
value={userInfo?.name}
|
||||
placeholder="Workspace Owner"
|
||||
></Input>
|
||||
</StyledSettingInputContainer>
|
||||
</div>
|
||||
) : (
|
||||
''
|
||||
)} */}
|
||||
|
||||
<StyledSettingH2 marginTop={20}>Workspace Type</StyledSettingH2>
|
||||
<StyledSettingInputContainer>
|
||||
<Input
|
||||
width={327}
|
||||
disabled
|
||||
value={owner.name}
|
||||
placeholder="Workspace Owner"
|
||||
></Input>
|
||||
<code>{workspace.type} </code>
|
||||
</StyledSettingInputContainer>
|
||||
<StyledDeleteButtonContainer>
|
||||
{isOwner ? (
|
||||
@@ -143,9 +135,7 @@ export const GeneralPage = ({
|
||||
<WorkspaceDelete
|
||||
open={showDelete}
|
||||
onClose={handleCloseDelete}
|
||||
workspaceName={workspaceName}
|
||||
workspaceId={workspace.id}
|
||||
nextWorkSpaceId={nextWorkSpaceId}
|
||||
workspace={workspace}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
@@ -158,7 +148,6 @@ export const GeneralPage = ({
|
||||
onClose={handleCloseLeave}
|
||||
workspaceName={workspaceName}
|
||||
workspaceId={workspace.id}
|
||||
nextWorkSpaceId={nextWorkSpaceId}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
@@ -11,27 +11,25 @@ import {
|
||||
import { useState } from 'react';
|
||||
import { ModalCloseButton } from '@/ui/modal';
|
||||
import { Button } from '@/ui/button';
|
||||
import { getDataCenter } from '@affine/datacenter';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import {
|
||||
deleteWorkspace,
|
||||
getWorkspaces,
|
||||
Workspace,
|
||||
} from '@/hooks/mock-data/mock';
|
||||
|
||||
interface WorkspaceDeleteProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
workspaceName: string;
|
||||
workspaceId: string;
|
||||
nextWorkSpaceId: string;
|
||||
workspace: Workspace;
|
||||
}
|
||||
|
||||
export const WorkspaceDelete = ({
|
||||
open,
|
||||
onClose,
|
||||
workspaceId,
|
||||
workspaceName,
|
||||
nextWorkSpaceId,
|
||||
workspace,
|
||||
}: WorkspaceDeleteProps) => {
|
||||
const [deleteStr, setDeleteStr] = useState<string>('');
|
||||
const { refreshWorkspacesMeta } = useAppState();
|
||||
const router = useRouter();
|
||||
|
||||
const handlerInputChange = (workspaceName: string) => {
|
||||
@@ -42,8 +40,14 @@ export const WorkspaceDelete = ({
|
||||
// const dc = await getDataCenter();
|
||||
// await dc.apis.deleteWorkspace({ id: workspaceId });
|
||||
// router.push(`/workspace/${nextWorkSpaceId}`);
|
||||
// refreshWorkspacesMeta();
|
||||
// onClose();
|
||||
deleteWorkspace(workspace.id);
|
||||
const workspaceList = getWorkspaces();
|
||||
if (workspaceList.length) {
|
||||
router.push(`/workspace/${workspaceList[0].id}`);
|
||||
} else {
|
||||
router.push(`/workspace`);
|
||||
}
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -51,11 +55,20 @@ export const WorkspaceDelete = ({
|
||||
<StyledModalWrapper>
|
||||
<ModalCloseButton onClick={onClose} />
|
||||
<StyledModalHeader>Delete Workspace</StyledModalHeader>
|
||||
<StyledTextContent>
|
||||
This action cannot be undone. This will permanently delete (
|
||||
<StyledWorkspaceName>{workspaceName}</StyledWorkspaceName>) along with
|
||||
all its content.
|
||||
</StyledTextContent>
|
||||
{workspace.type === 'local' ? (
|
||||
<StyledTextContent>
|
||||
Deleting (
|
||||
<StyledWorkspaceName>{workspace.name}</StyledWorkspaceName>) cannot
|
||||
be undone, please proceed with caution. along with all its content.
|
||||
</StyledTextContent>
|
||||
) : (
|
||||
<StyledTextContent>
|
||||
Deleting (
|
||||
<StyledWorkspaceName>{workspace.name}</StyledWorkspaceName>) will
|
||||
delete both local and cloud data, this operation cannot be undone,
|
||||
please proceed with caution.
|
||||
</StyledTextContent>
|
||||
)}
|
||||
<StyledInputContent>
|
||||
<Input
|
||||
onChange={handlerInputChange}
|
||||
@@ -1 +1 @@
|
||||
export * from './delete';
|
||||
export * from './Delete';
|
||||
|
||||
@@ -16,7 +16,8 @@ export const StyledModalHeader = styled('div')(({ theme }) => {
|
||||
width: '460px',
|
||||
fontWeight: '600',
|
||||
fontSize: '20px;',
|
||||
textAlign: 'center',
|
||||
textAlign: 'left',
|
||||
paddingLeft: '20px',
|
||||
color: theme.colors.popoverColor,
|
||||
};
|
||||
});
|
||||
@@ -32,7 +33,7 @@ export const StyledTextContent = styled('div')(() => {
|
||||
fontWeight: '400',
|
||||
fontSize: '18px',
|
||||
lineHeight: '26px',
|
||||
textAlign: 'center',
|
||||
textAlign: 'left',
|
||||
};
|
||||
});
|
||||
|
||||
@@ -41,7 +42,7 @@ export const StyledInputContent = styled('div')(() => {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
margin: '40px 0 24px 0',
|
||||
margin: '20px 0 24px 0',
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
export * from './general';
|
||||
export * from './General';
|
||||
|
||||
@@ -7,8 +7,7 @@ import {
|
||||
} from './style';
|
||||
import { ModalCloseButton } from '@/ui/modal';
|
||||
import { Button } from '@/ui/button';
|
||||
import { getDataCenter } from '@affine/datacenter';
|
||||
import { useRouter } from 'next/router';
|
||||
// import { getDataCenter } from '@affine/datacenter';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
|
||||
interface WorkspaceDeleteProps {
|
||||
@@ -16,22 +15,20 @@ interface WorkspaceDeleteProps {
|
||||
onClose: () => void;
|
||||
workspaceName: string;
|
||||
workspaceId: string;
|
||||
nextWorkSpaceId: string;
|
||||
}
|
||||
|
||||
export const WorkspaceLeave = ({
|
||||
open,
|
||||
onClose,
|
||||
nextWorkSpaceId,
|
||||
workspaceId,
|
||||
}: WorkspaceDeleteProps) => {
|
||||
const router = useRouter();
|
||||
// const router = useRouter();
|
||||
const { refreshWorkspacesMeta } = useAppState();
|
||||
const handleLeave = async () => {
|
||||
// const dc = await getDataCenter();
|
||||
// await dc.apis.leaveWorkspace({ id: workspaceId });
|
||||
router.push(`/workspace/${nextWorkSpaceId}`);
|
||||
refreshWorkspacesMeta();
|
||||
// // router.push(`/workspace/${nextWorkSpaceId}`);
|
||||
// refreshWorkspacesMeta();
|
||||
onClose();
|
||||
};
|
||||
|
||||
@@ -1 +1 @@
|
||||
export * from './leave';
|
||||
export * from './Leave';
|
||||
|
||||
@@ -9,7 +9,7 @@ export const StyledSettingInputContainer = styled('div')(() => {
|
||||
|
||||
export const StyledDeleteButtonContainer = styled('div')(() => {
|
||||
return {
|
||||
marginTop: '154px',
|
||||
marginTop: '30px',
|
||||
};
|
||||
});
|
||||
|
||||
@@ -26,3 +26,12 @@ export const StyledSettingAvatarContent = styled('div')(() => {
|
||||
export const StyledSettingAvatar = styled(MuiAvatar)(() => {
|
||||
return { height: '72px', width: '72px', marginRight: '24px' };
|
||||
});
|
||||
|
||||
export const StyledMemberWarp = styled('div')(() => {
|
||||
return {
|
||||
display: 'flex',
|
||||
height: '500px',
|
||||
flexDirection: 'column',
|
||||
padding: '60px 0',
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1 +1 @@
|
||||
export * from './workspace-setting';
|
||||
export * from './WorkspaceSetting';
|
||||
|
||||
@@ -117,6 +117,7 @@ export const StyledMemberTitleContainer = styled('div')(() => {
|
||||
display: 'flex',
|
||||
marginTop: '60px',
|
||||
fontWeight: '500',
|
||||
flex: 1,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -199,11 +200,12 @@ export const StyledMoreVerticalButton = styled('button')(() => {
|
||||
|
||||
export const StyledPublishExplanation = styled('div')(() => {
|
||||
return {
|
||||
marginTop: '56px',
|
||||
paddingRight: '48px',
|
||||
fontWeight: '500',
|
||||
fontSize: '18px',
|
||||
lineHeight: '26px',
|
||||
flex: 1,
|
||||
marginTop: '60px',
|
||||
};
|
||||
});
|
||||
|
||||
@@ -213,7 +215,8 @@ export const StyledPublishCopyContainer = styled('div')(() => {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
height: '38px',
|
||||
marginBottom: '20px',
|
||||
paddingTop: '20px',
|
||||
};
|
||||
});
|
||||
|
||||
@@ -226,5 +229,7 @@ export const StyledCopyButtonContainer = styled('div')(() => {
|
||||
export const StyledPublishContent = styled('div')(() => {
|
||||
return {
|
||||
height: '494px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,366 +0,0 @@
|
||||
import Modal, { ModalCloseButton } from '@/ui/modal';
|
||||
import {
|
||||
StyledCopyButtonContainer,
|
||||
StyledMemberAvatar,
|
||||
StyledMemberButtonContainer,
|
||||
StyledMemberEmail,
|
||||
StyledMemberInfo,
|
||||
StyledMemberListContainer,
|
||||
StyledMemberListItem,
|
||||
StyledMemberName,
|
||||
StyledMemberNameContainer,
|
||||
StyledMemberRoleContainer,
|
||||
StyledMemberTitleContainer,
|
||||
StyledMoreVerticalButton,
|
||||
StyledPublishContent,
|
||||
StyledPublishCopyContainer,
|
||||
StyledPublishExplanation,
|
||||
StyledSettingContainer,
|
||||
StyledSettingContent,
|
||||
StyledSettingH2,
|
||||
StyledSettingSidebar,
|
||||
StyledSettingSidebarHeader,
|
||||
StyledSettingTabContainer,
|
||||
StyledSettingTagIconContainer,
|
||||
WorkspaceSettingTagItem,
|
||||
} from './style';
|
||||
import {
|
||||
EditIcon,
|
||||
UsersIcon,
|
||||
PublishIcon,
|
||||
MoreVerticalIcon,
|
||||
EmailIcon,
|
||||
TrashIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { Button, IconButton } from '@/ui/button';
|
||||
import Input from '@/ui/input';
|
||||
import { InviteMembers } from '../invite-members/index';
|
||||
import { Workspace, Member, getDataCenter } from '@affine/datacenter';
|
||||
import { Avatar } from '@mui/material';
|
||||
import { Menu, MenuItem } from '@/ui/menu';
|
||||
import { toast } from '@/ui/toast';
|
||||
import { Empty } from '@/ui/empty';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { WorkspaceDetails } from '../workspace-slider-bar/WorkspaceSelector/SelectorPopperContent';
|
||||
import { GeneralPage } from './general';
|
||||
|
||||
enum ActiveTab {
|
||||
'general' = 'general',
|
||||
'members' = 'members',
|
||||
'publish' = 'publish',
|
||||
}
|
||||
|
||||
type SettingTabProps = {
|
||||
activeTab: ActiveTab;
|
||||
onTabChange?: (tab: ActiveTab) => void;
|
||||
};
|
||||
|
||||
type WorkspaceSettingProps = {
|
||||
isShow: boolean;
|
||||
onClose?: () => void;
|
||||
workspace: Workspace;
|
||||
owner: WorkspaceDetails[string]['owner'];
|
||||
};
|
||||
|
||||
const WorkspaceSettingTab = ({ activeTab, onTabChange }: SettingTabProps) => {
|
||||
return (
|
||||
<StyledSettingTabContainer>
|
||||
<WorkspaceSettingTagItem
|
||||
isActive={activeTab === ActiveTab.general}
|
||||
onClick={() => {
|
||||
onTabChange && onTabChange(ActiveTab.general);
|
||||
}}
|
||||
>
|
||||
<StyledSettingTagIconContainer>
|
||||
<EditIcon />
|
||||
</StyledSettingTagIconContainer>
|
||||
General
|
||||
</WorkspaceSettingTagItem>
|
||||
<WorkspaceSettingTagItem
|
||||
isActive={activeTab === ActiveTab.members}
|
||||
onClick={() => {
|
||||
onTabChange && onTabChange(ActiveTab.members);
|
||||
}}
|
||||
>
|
||||
<StyledSettingTagIconContainer>
|
||||
<UsersIcon />
|
||||
</StyledSettingTagIconContainer>
|
||||
Members
|
||||
</WorkspaceSettingTagItem>
|
||||
<WorkspaceSettingTagItem
|
||||
isActive={activeTab === ActiveTab.publish}
|
||||
onClick={() => {
|
||||
onTabChange && onTabChange(ActiveTab.publish);
|
||||
}}
|
||||
>
|
||||
<StyledSettingTagIconContainer>
|
||||
<PublishIcon />
|
||||
</StyledSettingTagIconContainer>
|
||||
Publish
|
||||
</WorkspaceSettingTagItem>
|
||||
</StyledSettingTabContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export const WorkspaceSetting = ({
|
||||
isShow,
|
||||
onClose,
|
||||
workspace,
|
||||
owner,
|
||||
}: WorkspaceSettingProps) => {
|
||||
const { user, workspaces } = useAppState();
|
||||
const [activeTab, setActiveTab] = useState<ActiveTab>(ActiveTab.general);
|
||||
const handleTabChange = (tab: ActiveTab) => {
|
||||
setActiveTab(tab);
|
||||
};
|
||||
const handleClickClose = () => {
|
||||
onClose && onClose();
|
||||
};
|
||||
const isOwner = user && owner.id === user.id;
|
||||
useEffect(() => {
|
||||
// reset tab when modal is closed
|
||||
if (!isShow) {
|
||||
setActiveTab(ActiveTab.general);
|
||||
}
|
||||
}, [isShow]);
|
||||
return (
|
||||
<Modal open={isShow}>
|
||||
<StyledSettingContainer>
|
||||
<ModalCloseButton onClick={handleClickClose} />
|
||||
{isOwner ? (
|
||||
<StyledSettingSidebar>
|
||||
<StyledSettingSidebarHeader>
|
||||
Workspace Settings
|
||||
</StyledSettingSidebarHeader>
|
||||
<WorkspaceSettingTab
|
||||
activeTab={activeTab}
|
||||
onTabChange={handleTabChange}
|
||||
/>
|
||||
</StyledSettingSidebar>
|
||||
) : null}
|
||||
<StyledSettingContent>
|
||||
{activeTab === ActiveTab.general && (
|
||||
<GeneralPage
|
||||
workspace={workspace}
|
||||
owner={owner}
|
||||
workspaces={workspaces}
|
||||
/>
|
||||
)}
|
||||
{activeTab === ActiveTab.members && workspace && (
|
||||
<MembersPage workspace={workspace} />
|
||||
)}
|
||||
{activeTab === ActiveTab.publish && (
|
||||
<PublishPage workspace={workspace} />
|
||||
)}
|
||||
</StyledSettingContent>
|
||||
</StyledSettingContainer>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const MembersPage = ({ workspace }: { workspace: Workspace }) => {
|
||||
const [isInviteModalShow, setIsInviteModalShow] = useState(false);
|
||||
const [members, setMembers] = useState<Member[]>([]);
|
||||
const refreshMembers = useCallback(() => {
|
||||
// getDataCenter()
|
||||
// .then(dc =>
|
||||
// dc.apis.getWorkspaceMembers({
|
||||
// id: workspace.id,
|
||||
// })
|
||||
// )
|
||||
// .then(data => {
|
||||
// setMembers(data);
|
||||
// })
|
||||
// .catch(err => {
|
||||
// console.log(err);
|
||||
// });
|
||||
}, [workspace.id]);
|
||||
useEffect(() => {
|
||||
refreshMembers();
|
||||
}, [refreshMembers]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<StyledMemberTitleContainer>
|
||||
<StyledMemberNameContainer>
|
||||
Users({members.length})
|
||||
</StyledMemberNameContainer>
|
||||
<StyledMemberRoleContainer>Access level</StyledMemberRoleContainer>
|
||||
</StyledMemberTitleContainer>
|
||||
<StyledMemberListContainer>
|
||||
{members.length === 0 && (
|
||||
<Empty width={648} sx={{ marginTop: '60px' }} height={300}></Empty>
|
||||
)}
|
||||
{members.length ? (
|
||||
members.map(member => {
|
||||
return (
|
||||
<StyledMemberListItem key={member.id}>
|
||||
<StyledMemberNameContainer>
|
||||
{member.user.type === 'Registered' ? (
|
||||
<Avatar src={member.user.avatar_url}></Avatar>
|
||||
) : (
|
||||
<StyledMemberAvatar alt="member avatar">
|
||||
<EmailIcon></EmailIcon>
|
||||
</StyledMemberAvatar>
|
||||
)}
|
||||
|
||||
<StyledMemberInfo>
|
||||
{member.user.type === 'Registered' ? (
|
||||
<StyledMemberName>{member.user.name}</StyledMemberName>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<StyledMemberEmail>{member.user.email}</StyledMemberEmail>
|
||||
</StyledMemberInfo>
|
||||
</StyledMemberNameContainer>
|
||||
<StyledMemberRoleContainer>
|
||||
{member.accepted
|
||||
? member.type !== 99
|
||||
? 'Member'
|
||||
: 'Workspace Owner'
|
||||
: 'Pending'}
|
||||
</StyledMemberRoleContainer>
|
||||
<StyledMoreVerticalButton>
|
||||
<Menu
|
||||
content={
|
||||
<>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
// confirm({
|
||||
// title: 'Delete Member?',
|
||||
// content: `will delete member`,
|
||||
// confirmText: 'Delete',
|
||||
// confirmType: 'danger',
|
||||
// }).then(confirm => {
|
||||
// getDataCenter()
|
||||
// .then(dc =>
|
||||
// dc.apis.removeMember({
|
||||
// permissionId: member.id,
|
||||
// })
|
||||
// )
|
||||
// .then(() => {
|
||||
// // console.log('data: ', data);
|
||||
// toast('Moved to Trash');
|
||||
// refreshMembers();
|
||||
// });
|
||||
// });
|
||||
}}
|
||||
icon={<TrashIcon />}
|
||||
>
|
||||
Delete
|
||||
</MenuItem>
|
||||
</>
|
||||
}
|
||||
placement="bottom-end"
|
||||
disablePortal={true}
|
||||
>
|
||||
<IconButton>
|
||||
<MoreVerticalIcon />
|
||||
</IconButton>
|
||||
</Menu>
|
||||
</StyledMoreVerticalButton>
|
||||
</StyledMemberListItem>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</StyledMemberListContainer>
|
||||
<StyledMemberButtonContainer>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setIsInviteModalShow(true);
|
||||
}}
|
||||
type="primary"
|
||||
shape="circle"
|
||||
>
|
||||
Invite Members
|
||||
</Button>
|
||||
<InviteMembers
|
||||
onClose={() => {
|
||||
setIsInviteModalShow(false);
|
||||
}}
|
||||
onInviteSuccess={() => {
|
||||
refreshMembers();
|
||||
}}
|
||||
workspaceId={workspace.id}
|
||||
open={isInviteModalShow}
|
||||
></InviteMembers>
|
||||
</StyledMemberButtonContainer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const PublishPage = ({ workspace }: { workspace: Workspace }) => {
|
||||
const shareUrl = window.location.host + '/workspace/' + workspace.id;
|
||||
const [publicStatus, setPublicStatus] = useState<boolean | null>(
|
||||
workspace.public
|
||||
);
|
||||
const togglePublic = (flag: boolean) => {
|
||||
// getDataCenter()
|
||||
// .then(dc =>
|
||||
// dc.apis.updateWorkspace({
|
||||
// id: workspace.id,
|
||||
// public: flag,
|
||||
// })
|
||||
// )
|
||||
// .then(data => {
|
||||
// setPublicStatus(data?.public);
|
||||
// toast('Updated Public Status Success');
|
||||
// });
|
||||
};
|
||||
const copyUrl = () => {
|
||||
navigator.clipboard.writeText(shareUrl);
|
||||
toast('Copied url to clipboard');
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<StyledPublishContent>
|
||||
{publicStatus ? (
|
||||
<>
|
||||
<StyledPublishExplanation>
|
||||
The current workspace has been published to the web, everyone can
|
||||
view the contents of this workspace through the link.
|
||||
</StyledPublishExplanation>
|
||||
<StyledSettingH2 marginTop={48}>Share with link</StyledSettingH2>
|
||||
<StyledPublishCopyContainer>
|
||||
<Input width={500} value={shareUrl} disabled={true}></Input>
|
||||
<StyledCopyButtonContainer>
|
||||
<Button onClick={copyUrl} type="primary" shape="circle">
|
||||
Copy Link
|
||||
</Button>
|
||||
</StyledCopyButtonContainer>
|
||||
</StyledPublishCopyContainer>
|
||||
</>
|
||||
) : (
|
||||
<StyledPublishExplanation>
|
||||
After publishing to the web, everyone can view the content of this
|
||||
workspace through the link.
|
||||
</StyledPublishExplanation>
|
||||
)}
|
||||
</StyledPublishContent>
|
||||
{!publicStatus ? (
|
||||
<Button
|
||||
onClick={() => {
|
||||
togglePublic(true);
|
||||
}}
|
||||
type="primary"
|
||||
shape="circle"
|
||||
>
|
||||
Publish to web
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
onClick={() => {
|
||||
togglePublic(false);
|
||||
}}
|
||||
type="primary"
|
||||
shape="circle"
|
||||
>
|
||||
Stop publishing
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
import { WorkspaceSetting } from '@/components/workspace-setting';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { getDataCenter, WorkspaceType } from '@affine/datacenter';
|
||||
import { useModal } from '@/providers/global-modal-provider';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
|
||||
export type WorkspaceDetails = Record<
|
||||
string,
|
||||
@@ -27,8 +27,7 @@ type SelectorPopperContentProps = {
|
||||
export const SelectorPopperContent = ({
|
||||
isShow,
|
||||
}: SelectorPopperContentProps) => {
|
||||
const { user, workspacesMeta, workspaces, refreshWorkspacesMeta } =
|
||||
useAppState();
|
||||
const { user, workspacesMeta, refreshWorkspacesMeta } = useAppState();
|
||||
const [settingWorkspaceId, setSettingWorkspaceId] = useState<string | null>(
|
||||
null
|
||||
);
|
||||
@@ -115,13 +114,9 @@ export const SelectorPopperContent = ({
|
||||
type={workspace.type}
|
||||
key={workspace.id}
|
||||
id={workspace.id}
|
||||
icon={
|
||||
(workspaces[workspace.id]?.meta.avatar &&
|
||||
`/api/blob/${workspaces[workspace.id]?.meta.avatar}`) ||
|
||||
`loading...`
|
||||
}
|
||||
icon={`loading...`}
|
||||
onClickSetting={handleClickSettingWorkspace}
|
||||
name={workspaces[workspace.id]?.meta.name || `loading...`}
|
||||
name={`loading...`}
|
||||
memberCount={workSpaceDetails[workspace.id]?.memberCount || 1}
|
||||
/>
|
||||
) : null;
|
||||
@@ -130,16 +125,16 @@ export const SelectorPopperContent = ({
|
||||
<CreateWorkspaceItem />
|
||||
{settingWorkspace ? (
|
||||
<WorkspaceSetting
|
||||
isShow={Boolean(settingWorkspaceId)}
|
||||
isShow={false}
|
||||
onClose={handleCloseWorkSpace}
|
||||
workspace={settingWorkspace}
|
||||
owner={
|
||||
(settingWorkspaceId &&
|
||||
workSpaceDetails[settingWorkspaceId]?.owner) || {
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
}
|
||||
}
|
||||
// workspace={settingWorkspace}
|
||||
// owner={
|
||||
// (settingWorkspaceId &&
|
||||
// workSpaceDetails[settingWorkspaceId]?.owner) || {
|
||||
// id: user.id,
|
||||
// name: user.name,
|
||||
// }
|
||||
// }
|
||||
/>
|
||||
) : null}
|
||||
<StyledDivider />
|
||||
|
||||
@@ -1 +1 @@
|
||||
export * from './workspace-create';
|
||||
export * from './WorkspaceCreate';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useModal } from '@/providers/global-modal-provider';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
import { styled } from '@/styles';
|
||||
import { AffineIcon } from '../../icons/icons';
|
||||
import { AffineIcon } from '../../icons/Icons';
|
||||
import {
|
||||
WorkspaceItemAvatar,
|
||||
LoginItemWrapper,
|
||||
|
||||
@@ -1,53 +1,52 @@
|
||||
import { Popper } from '@/ui/popper';
|
||||
import { Avatar, WorkspaceName, SelectorWrapper } from './styles';
|
||||
import { SelectorPopperContent } from './SelectorPopperContent';
|
||||
import { useState } from 'react';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { WorkspaceType } from '@affine/datacenter';
|
||||
import { AffineIcon } from '../icons/icons';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { WorkspaceModal } from '@/components/workspace-modal';
|
||||
import { WorkspaceAvatar } from '@/components/workspace-avatar';
|
||||
import { useTemporaryHelper } from '@/providers/temporary-helper-provider';
|
||||
export const WorkspaceSelector = () => {
|
||||
const [isShow, setIsShow] = useState(false);
|
||||
const { currentWorkspace, workspacesMeta, currentWorkspaceId, user } =
|
||||
useAppState();
|
||||
const workspaceMeta = workspacesMeta.find(
|
||||
meta => String(meta.id) === String(currentWorkspaceId)
|
||||
);
|
||||
const [workspaceListShow, setWorkspaceListShow] = useState(false);
|
||||
const { currentWorkspace, workspaceMetaList } = useTemporaryHelper();
|
||||
|
||||
useEffect(() => {
|
||||
if (workspaceMetaList.length === 0) {
|
||||
setWorkspaceListShow(true);
|
||||
}
|
||||
}, [workspaceMetaList]);
|
||||
|
||||
return (
|
||||
<Popper
|
||||
content={<SelectorPopperContent isShow={isShow} />}
|
||||
zIndex={1000}
|
||||
placement="bottom-start"
|
||||
trigger="click"
|
||||
onVisibleChange={setIsShow}
|
||||
>
|
||||
<SelectorWrapper data-testid="current-workspace">
|
||||
<>
|
||||
<SelectorWrapper
|
||||
onClick={() => {
|
||||
setWorkspaceListShow(true);
|
||||
}}
|
||||
data-testid="current-workspace"
|
||||
>
|
||||
<Avatar
|
||||
alt="Affine"
|
||||
data-testid="workspace-avatar"
|
||||
src={
|
||||
workspaceMeta?.type === WorkspaceType.Private
|
||||
? user
|
||||
? user.avatar_url
|
||||
: ''
|
||||
: currentWorkspace?.meta.avatar &&
|
||||
`/api/blob/${currentWorkspace?.meta.avatar}`
|
||||
}
|
||||
src={currentWorkspace?.avatar}
|
||||
>
|
||||
{workspaceMeta?.type === WorkspaceType.Private && user ? (
|
||||
user?.name[0]
|
||||
) : (
|
||||
<AffineIcon />
|
||||
)}
|
||||
<div
|
||||
style={{
|
||||
float: 'left',
|
||||
}}
|
||||
>
|
||||
<WorkspaceAvatar
|
||||
size={28}
|
||||
name={currentWorkspace?.name ?? 'AFFiNE'}
|
||||
/>
|
||||
</div>
|
||||
</Avatar>
|
||||
<WorkspaceName data-testid="workspace-name">
|
||||
{workspaceMeta?.type === WorkspaceType.Private
|
||||
? user
|
||||
? user.name
|
||||
: 'AFFiNE'
|
||||
: currentWorkspace?.meta.name || 'AFFiNE'}
|
||||
{currentWorkspace?.name ?? 'AFFiNE'}
|
||||
</WorkspaceName>
|
||||
</SelectorWrapper>
|
||||
</Popper>
|
||||
<WorkspaceModal
|
||||
open={workspaceListShow}
|
||||
onClose={() => {
|
||||
setWorkspaceListShow(false);
|
||||
}}
|
||||
></WorkspaceModal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -21,15 +21,17 @@ import {
|
||||
ImportIcon,
|
||||
TrashIcon,
|
||||
AddIcon,
|
||||
SettingsIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import Link from 'next/link';
|
||||
import { Tooltip } from '@/ui/tooltip';
|
||||
import { useModal } from '@/providers/global-modal-provider';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
import { useAppState } from '@/providers/app-state-provider/context';
|
||||
import { IconButton } from '@/ui/button';
|
||||
import useLocalStorage from '@/hooks/use-local-storage';
|
||||
import usePageMetaList from '@/hooks/use-page-meta-list';
|
||||
import { usePageHelper } from '@/hooks/use-page-helper';
|
||||
import { WorkspaceSetting } from '@/components/workspace-setting';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const FavoriteList = ({ showList }: { showList: boolean }) => {
|
||||
@@ -74,6 +76,8 @@ export const WorkSpaceSliderBar = () => {
|
||||
const [showTip, setShowTip] = useState(false);
|
||||
const [show, setShow] = useLocalStorage('AFFiNE_SLIDE_BAR', false, true);
|
||||
|
||||
const [showWorkspaceSetting, setShowWorkspaceSetting] = useState(false);
|
||||
|
||||
const paths = {
|
||||
all: currentWorkspaceId ? `/workspace/${currentWorkspaceId}/all` : '',
|
||||
favorite: currentWorkspaceId
|
||||
@@ -146,6 +150,20 @@ export const WorkSpaceSliderBar = () => {
|
||||
</IconButton>
|
||||
</StyledListItem>
|
||||
<FavoriteList showList={showSubFavorite} />
|
||||
<StyledListItem
|
||||
onClick={() => {
|
||||
setShowWorkspaceSetting(true);
|
||||
}}
|
||||
>
|
||||
<SettingsIcon /> Setting
|
||||
</StyledListItem>
|
||||
|
||||
<WorkspaceSetting
|
||||
isShow={showWorkspaceSetting}
|
||||
onClose={() => {
|
||||
setShowWorkspaceSetting(false);
|
||||
}}
|
||||
/>
|
||||
|
||||
<StyledListItem
|
||||
onClick={() => {
|
||||
|
||||
@@ -62,6 +62,7 @@ export const StyledListItem = styled.div<{
|
||||
color: active ? theme.colors.primaryColor : theme.colors.popoverColor,
|
||||
paddingLeft: '12px',
|
||||
borderRadius: '5px',
|
||||
cursor: 'pointer',
|
||||
...displayFlex('flex-start', 'center'),
|
||||
...(disabled
|
||||
? {
|
||||
|
||||
160
packages/app/src/hooks/mock-data/mock.ts
Normal file
160
packages/app/src/hooks/mock-data/mock.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
export interface Workspace {
|
||||
name: string; // 名称
|
||||
id: string; //唯一标识
|
||||
isPublish?: boolean; // 是否公开
|
||||
isLocal?: boolean; // 是否全部数据都在本地
|
||||
avatar?: string; // 封面
|
||||
type: 'local' | 'cloud' | 'join'; // cloud: 云端(本次暂不支持),local: 本地,join : 加入别人的
|
||||
workspaceOwner?: User; // 本地工作空间的拥有者
|
||||
}
|
||||
|
||||
export interface User {
|
||||
name: string;
|
||||
id: string;
|
||||
email: string;
|
||||
avatar: string;
|
||||
}
|
||||
|
||||
export function updateWorkspaceMeta(
|
||||
workspaceId: string,
|
||||
workspaceData: {
|
||||
name?: string;
|
||||
avatar?: string;
|
||||
type?: 'local' | 'cloud' | 'join';
|
||||
}
|
||||
) {
|
||||
const workspacesMeta = getWorkspaces();
|
||||
const newWorkspacesMeta = workspacesMeta.map((workspace: Workspace) => {
|
||||
if (workspace.id === workspaceId) {
|
||||
workspaceData.name && (workspace.name = workspaceData.name);
|
||||
workspaceData.avatar && (workspace.avatar = workspaceData.avatar);
|
||||
workspaceData.type && (workspace.type = workspaceData.type);
|
||||
return workspaceData;
|
||||
}
|
||||
return workspace;
|
||||
});
|
||||
localStorage.setItem('affine-workspace', JSON.stringify(newWorkspacesMeta));
|
||||
const activeWorkspace = getActiveWorkspace();
|
||||
workspaceData.name && (activeWorkspace.name = workspaceData.name);
|
||||
workspaceData.avatar && (activeWorkspace.avatar = workspaceData.avatar);
|
||||
|
||||
workspaceData.type && (activeWorkspace.type = workspaceData.type);
|
||||
console.log(workspaceData);
|
||||
setActiveWorkspace(activeWorkspace);
|
||||
}
|
||||
export function createWorkspace(workspaceName: string) {
|
||||
const workspaceId = 'workspace-' + Date.now();
|
||||
const workspaceData = {
|
||||
name: workspaceName,
|
||||
id: workspaceId,
|
||||
isPublish: false,
|
||||
isLocal: true,
|
||||
avatar: '',
|
||||
type: 'local',
|
||||
} as Workspace;
|
||||
const workspacesMeta = getWorkspaces();
|
||||
workspacesMeta.push(workspaceData);
|
||||
localStorage.setItem('affine-workspace', JSON.stringify(workspacesMeta));
|
||||
setActiveWorkspace(workspaceData);
|
||||
return { workspaceId };
|
||||
}
|
||||
|
||||
export function getWorkspaces(): Workspace[] {
|
||||
const workspacesMeta = JSON.parse(
|
||||
localStorage.getItem('affine-workspace') ?? '[]'
|
||||
);
|
||||
return workspacesMeta;
|
||||
}
|
||||
|
||||
export function deleteWorkspace(workspaceId: string) {
|
||||
const workspacesMeta = getWorkspaces();
|
||||
const newWorkspacesMeta = workspacesMeta.filter(() => {
|
||||
return workspaceId !== workspaceId;
|
||||
});
|
||||
localStorage.setItem('affine-workspace', JSON.stringify(newWorkspacesMeta));
|
||||
}
|
||||
|
||||
export function getMembers(id: string): User[] {
|
||||
const memberMap = JSON.parse(localStorage.getItem('affine-member') ?? '{}');
|
||||
return memberMap[id] || [];
|
||||
}
|
||||
|
||||
export function setMember(workspaceId: string, member: User) {
|
||||
const memberMap = JSON.parse(localStorage.getItem('affine-member') ?? '{}');
|
||||
memberMap[workspaceId] = memberMap[workspaceId] || [];
|
||||
memberMap[workspaceId].push(member);
|
||||
localStorage.setItem('affine-member', JSON.stringify(memberMap));
|
||||
}
|
||||
|
||||
export function deleteMember(workspaceId: string, index: number) {
|
||||
const memberMap = JSON.parse(localStorage.getItem('affine-member') ?? '{}');
|
||||
const memberList = memberMap[workspaceId];
|
||||
memberList.splice(index, 1);
|
||||
memberMap[workspaceId] = memberList;
|
||||
localStorage.setItem('affine-member', JSON.stringify(memberMap));
|
||||
}
|
||||
export function leaveWorkspace() {
|
||||
return true;
|
||||
}
|
||||
|
||||
export function setWorkspacePublish(id: string, isPublish: boolean): boolean {
|
||||
const workspacesMeta = getWorkspaces();
|
||||
const newWorkspacesMeta = workspacesMeta.map((workspace: Workspace) => {
|
||||
if (workspace.id === id) {
|
||||
workspace.isPublish = isPublish;
|
||||
}
|
||||
return workspace;
|
||||
});
|
||||
localStorage.setItem('affine-workspace', JSON.stringify(newWorkspacesMeta));
|
||||
return isPublish;
|
||||
}
|
||||
|
||||
export function getWorkspaceById(id: string) {
|
||||
const workspacesMeta = getWorkspaces();
|
||||
return workspacesMeta.find((workspace: Workspace) => {
|
||||
return workspace.id === id;
|
||||
});
|
||||
}
|
||||
|
||||
export function getPagesByWorkspaceId(workspaceId: string) {
|
||||
if (!workspaceId) return [];
|
||||
const workspacesMeta = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
workspacesMeta.push({
|
||||
id: 'page-' + i,
|
||||
name: 'page ' + i,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function getActiveWorkspace(): Workspace {
|
||||
return JSON.parse(localStorage.getItem('affine-active-workspace') ?? '{}');
|
||||
}
|
||||
|
||||
export function setActiveWorkspace(workspaceData: Workspace) {
|
||||
console.log('workspaceData: ', workspaceData);
|
||||
localStorage.setItem(
|
||||
'affine-active-workspace',
|
||||
JSON.stringify(workspaceData)
|
||||
);
|
||||
}
|
||||
|
||||
export function getUserInfo(): User {
|
||||
return JSON.parse(localStorage.getItem('affine-user') ?? 'null');
|
||||
}
|
||||
|
||||
export function Login(): void {
|
||||
localStorage.setItem(
|
||||
'affine-user',
|
||||
JSON.stringify({
|
||||
name: 'Diamond',
|
||||
id: 'ttt',
|
||||
email: 'diamond.shx@gmail.com',
|
||||
avatar: 'string',
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function SignOut(): void {
|
||||
localStorage.removeItem('affine-user');
|
||||
}
|
||||
@@ -1,22 +1 @@
|
||||
{
|
||||
"// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.": "",
|
||||
"Add A Below Block": "নীচে একটি ব্লক যোগ করুন",
|
||||
"WarningTips": {
|
||||
"IsNotfsApiSupported": "অ্যাফাইন ডেমোতে স্বাগতম। পরিবর্তনগুলি সংরক্ষণ করা শুরু করতে আপনি Chrome/Edge এর মতো ক্রোমিয়াম ভিত্তিক ব্রাউজারের সর্বশেষ সংস্করণের মাধ্যমে ডিস্কে ডেটা সিঙ্ক করতে পারেন",
|
||||
"DoNotStore": "অ্যাফাইন সক্রিয় ডেভেলপমেন্ট এর অধীনে এবং বর্তমান সংস্করণটি অস্থিতিশীল। দয়া করে কোন তথ্য বা ডেটা সঞ্চয় করবেন না"
|
||||
},
|
||||
"Language": "ভাষা",
|
||||
"Settings": "সেটিংস",
|
||||
"Share": "শেয়ার করুন",
|
||||
"Comment": "মন্তব্য",
|
||||
"Delete": "মুছে ফেলুন",
|
||||
"Copy Page Link": "পেজ লিংক কপি করুন",
|
||||
"Duplicate Page": "সদৃশ পৃষ্ঠা তৈরি করুন",
|
||||
"Logout": "লগআউট",
|
||||
"Divide Here As A New Group": "একটি নতুন গ্রুপ হিসেবে বিভক্ত করুন",
|
||||
"ComingSoon": "লেআউট সেটিংস শীঘ্রই আসছে...",
|
||||
"Clear Workspace": "ওয়ার্কস্পেস পরিষ্কার করুন",
|
||||
"Layout": "লেআউট",
|
||||
"Turn into": "রূপান্তর করুন",
|
||||
"Sync to Disk": "ডিস্ক এ সিঙ্ক করুন"
|
||||
}
|
||||
{}
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"Delete page?": "Delete page?",
|
||||
"Delete permanently?": "Delete permanently?",
|
||||
"will be moved to Trash": "{{title}} will be moved to Trash",
|
||||
"Once deleted, you can't undo this action.": "Once deleted,you can't undo this action.",
|
||||
"Once deleted, you can't undo this action.": "Once deleted, you can't undo this action.",
|
||||
"Moved to Trash": "Moved to Trash",
|
||||
"Permanently deleted": "Permanently deleted",
|
||||
"restored": "{{title}} restored",
|
||||
@@ -54,12 +54,26 @@
|
||||
"Strikethrough": "Strikethrough",
|
||||
"Inline code": "Inline code",
|
||||
"Code block": "Code block",
|
||||
"Link": "Link",
|
||||
"Hyperlink(with selected text)": "Hyperlink(with selected text)",
|
||||
"Body text": "Body text",
|
||||
"Heading": "Heading {{number}}",
|
||||
"Increase indent": "Increase indent",
|
||||
"Reduce indent": "Reduce indent",
|
||||
"Markdown Syntax": "Markdown Syntax",
|
||||
"Divider": "Divider",
|
||||
"404 - Page Not Found": "404 - Page Not Found"
|
||||
"404 - Page Not Found": "404 - Page Not Found",
|
||||
"New Workspace": "New Workspace",
|
||||
"Workspace description": "Workspace is your virtual space to capture, create and plan as just one person or together as a team.",
|
||||
"Create": "Create",
|
||||
"Select": "Select",
|
||||
"Text": "Text (coming soon)",
|
||||
"Shape": "Shape",
|
||||
"Sticky": "Sticky (coming soon)",
|
||||
"Pen": "Pen (coming soon)",
|
||||
"Connector": "Connector (coming soon)",
|
||||
"Upload": "Upload",
|
||||
"Restore it": "Restore it",
|
||||
"TrashButtonGroupTitle": "Permanently delete",
|
||||
"TrashButtonGroupDescription": "Once deleted, you can't undo this action. Do you confirm?",
|
||||
"Delete permanently": "Delete permanently"
|
||||
}
|
||||
|
||||
@@ -1,29 +1 @@
|
||||
{
|
||||
"// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.": "",
|
||||
"ComingSoon": "Bientôt disponible",
|
||||
"Duplicate Page": "Dupliquer la page",
|
||||
"Copy Page Link": "Copier le lien de la page",
|
||||
"Delete": "Supprimer",
|
||||
"Comment": "Commentaire",
|
||||
"Export As HTML": "Exporter en HTML",
|
||||
"Export As Markdown": "Exporter en Markdown",
|
||||
"Export As PDF (Unsupported)": "exporter en PDF (non supporté)",
|
||||
"Logout": "Déconnexion",
|
||||
"Export Workspace": "Exporter l'espace de travail",
|
||||
"Import Workspace": "Importer l'espace de travail",
|
||||
"Language": "Langue",
|
||||
"Last edited by": "Dernière édition par {{name}}",
|
||||
"Layout": "Mise en forme",
|
||||
"Settings": "Réglages",
|
||||
"Share": "Partager",
|
||||
"Sync to Disk": "Synchroniser sur le disque",
|
||||
"Turn into": "Transformer en",
|
||||
"WarningTips": {
|
||||
"DoNotStore": "Affine est en développement actif ; la version actuelle est INSTABLE. Veuillez NE PAS stocker d'informations ou de données",
|
||||
"IsNotLocalWorkspace": "Bienvenue sur la démo d'AFFiNE. Pour commencer à sauvegarder vos modifications, vous pouvez SYNCHRONISER SUR LE DISQUE",
|
||||
"IsNotfsApiSupported": "Bienvenue sur la démo d'AFFiNE. Pour commencer à sauvegarder vos modifications, vous pouvez SYNCHRONISER SUR LE DISQUE\navec la dernière version d'un navigateur basé sur Chromium tel que Chrome ou Edge."
|
||||
},
|
||||
"Add A Below Block": "Ajouter un bloc en-dessous",
|
||||
"Divide Here As A New Group": "Séparer ici en un nouveau groupe",
|
||||
"Clear Workspace": "Vider l'espace de travail"
|
||||
}
|
||||
{}
|
||||
|
||||
@@ -1,27 +1 @@
|
||||
{
|
||||
"// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.": "",
|
||||
"Clear Workspace": "Očisti radni prostor",
|
||||
"ComingSoon": "Podešavanja za izgled dolaze",
|
||||
"Comment": "Komentar",
|
||||
"Copy Page Link": "Kopiraj link stranice",
|
||||
"Delete": "Obriši",
|
||||
"Duplicate Page": "Dupliraj stranicu",
|
||||
"Export As HTML": "Izvezi kao HTML",
|
||||
"Export As Markdown": "Izvezi kao Markdown",
|
||||
"Export As PDF (Unsupported)": "Izvezi kao PDF (nepodržano)",
|
||||
"Export Workspace": "Izvezi radnu površinu",
|
||||
"Import Workspace": "Poboljšaj radnu površinu",
|
||||
"Language": "Jezik",
|
||||
"Last edited by": "Zadnju promenu uradio {{ime}}",
|
||||
"Layout": "Izgled",
|
||||
"Logout": "Odjava",
|
||||
"Settings": "Podešavanja",
|
||||
"Share": "Podeli",
|
||||
"Sync to Disk": "Sinhroniziraj sa diskom",
|
||||
"Turn into": "Promeni u",
|
||||
"WarningTips": {
|
||||
"DoNotStore": "AFFiNE je u stanju aktivnog razvoja i trenutna verzija je NESTABILNA. Molimo vas, NEMOJTE čuvati informacije ili podatke.",
|
||||
"IsNotLocalWorkspace": "Dobrodošli u AFFiNE demo. Da bi započeli proces čuvanja promena možete kliknuti SINHRONIZUJ SA DISKOM.",
|
||||
"IsNotfsApiSupported": "Dobrodošli u AFFiNE demo. Da bi započeli proces čuvanja promena možete SINHRONIZOVATI NA DISK sa poslednjom verzijom pretraživača tipa Chromium, kao što su Chrome/Edge."
|
||||
}
|
||||
}
|
||||
{}
|
||||
|
||||
@@ -1,29 +1,65 @@
|
||||
{
|
||||
"// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.": "",
|
||||
"Sync to Disk": "同步到磁盘",
|
||||
"Share": "分享",
|
||||
"WarningTips": {
|
||||
"IsNotfsApiSupported": "欢迎来到AFFiNE 的演示界面。您可以使用最新版本的基于Chrome的浏览器(如Chrome/Edge)将数据同步到磁盘来进行保存",
|
||||
"IsNotLocalWorkspace": "欢迎来到AFFiNE 的演示界面,您可以同步到磁盘来进行保存操作。",
|
||||
"DoNotStore": "AFFiNE 正在积极开发中,当前版本不稳定。请不要存储信息或数据。"
|
||||
},
|
||||
"ComingSoon": "布局设置即将到来",
|
||||
"Layout": "布局",
|
||||
"Comment": "评论",
|
||||
"Settings": "设置",
|
||||
"Duplicate Page": "复制页面",
|
||||
"Copy Page Link": "复制页面链接",
|
||||
"Language": "当前语言",
|
||||
"Clear Workspace": "清空工作区域",
|
||||
"Export As Markdown": "导出 markdown",
|
||||
"Export As HTML": "导出 HTML",
|
||||
"Export As PDF (Unsupported)": "导出 PDF (暂不支持)",
|
||||
"Import Workspace": "导入 Workspace",
|
||||
"Export Workspace": "导出 Workspace",
|
||||
"Last edited by": "最后编辑者为 {{name}}",
|
||||
"Logout": "退出登录",
|
||||
"Quick search": "快速搜索",
|
||||
"All pages": "全部页面",
|
||||
"Favourites": "收藏夹",
|
||||
"No item": "没有项目",
|
||||
"Import": "导入",
|
||||
"Trash": "回收站",
|
||||
"New Page": "新建文章",
|
||||
"New Keyword Page": "新建 '{{query}}' 为标题的文章",
|
||||
"Find 0 result": "找到 0 个结果",
|
||||
"Find results": "找到 {{number}} 个结果",
|
||||
"Collapse sidebar": "关闭侧边栏",
|
||||
"Expand sidebar": "展开侧边栏",
|
||||
"Removed from Favourites": "已从收藏中移除",
|
||||
"Remove from favourites": "从收藏中移除",
|
||||
"Added to Favourites": "已添加到收藏",
|
||||
"Add to favourites": "添加到收藏",
|
||||
"Paper": "文章",
|
||||
"Edgeless": "无边模式",
|
||||
"Switch to": "跳转到",
|
||||
"Convert to ": "转换成 ",
|
||||
"Page": "文章",
|
||||
"Export": "导出",
|
||||
"Export to HTML": "导出到 HTML",
|
||||
"Export to Markdown": "导出到 Markdown",
|
||||
"Delete": "删除",
|
||||
"Turn into": "转换为",
|
||||
"Add A Below Block": "在下方添加一个新块",
|
||||
"Divide Here As A New Group": "从这里划分一个新组"
|
||||
"Title": "标题",
|
||||
"Untitled": "无标题",
|
||||
"Created": "创建时间",
|
||||
"Updated": "更新时间",
|
||||
"Open in new tab": "在新页面打开",
|
||||
"Favourite": "收藏",
|
||||
"Favourited": "已收藏",
|
||||
"Delete page?": "删除文章?",
|
||||
"Delete permanently?": "永久删除?",
|
||||
"will be moved to Trash": "{{title}} 将被移动到回收站",
|
||||
"Once deleted, you can't undo this action.": "一次性删除,无法恢复。",
|
||||
"Moved to Trash": "已移动到回收站",
|
||||
"Permanently deleted": "已永久删除",
|
||||
"restored": "{{title}} 已恢复",
|
||||
"Cancel": "取消",
|
||||
"Keyboard Shortcuts": "快捷键",
|
||||
"Contact Us": "联系我们",
|
||||
"Official Website": "官网",
|
||||
"Get in touch!": "Get in touch!",
|
||||
"AFFiNE Community": "AFFiNE Community",
|
||||
"How is AFFiNE Alpha different?": "How is AFFiNE Alpha different?",
|
||||
"Shortcuts": "Shortcuts",
|
||||
"Undo": "Undo",
|
||||
"Redo": "Redo",
|
||||
"Bold": "Bold",
|
||||
"Italic": "Italic",
|
||||
"Underline": "Underline",
|
||||
"Strikethrough": "Strikethrough",
|
||||
"Inline code": "Inline code",
|
||||
"Code block": "Code block",
|
||||
"Link": "Link",
|
||||
"Body text": "Body text",
|
||||
"Heading": "Heading {{number}}",
|
||||
"Increase indent": "Increase indent",
|
||||
"Reduce indent": "Reduce indent",
|
||||
"Markdown Syntax": "Markdown Syntax",
|
||||
"Divider": "Divider",
|
||||
"404 - Page Not Found": "404 - Page Not Found"
|
||||
}
|
||||
|
||||
@@ -1,29 +1 @@
|
||||
{
|
||||
"// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.": "",
|
||||
"Add A Below Block": "在下方新添塊",
|
||||
"Clear Workspace": "清空工作區",
|
||||
"ComingSoon": "自定義佈局功能即將與您見面",
|
||||
"Comment": "評論",
|
||||
"Copy Page Link": "拷貝頁面鏈接",
|
||||
"Delete": "刪除",
|
||||
"Divide Here As A New Group": "從此地劃分成新組",
|
||||
"Duplicate Page": "複製界面",
|
||||
"Export As HTML": "導出 HTML",
|
||||
"Export As Markdown": "以 Markdown 導出",
|
||||
"Export As PDF (Unsupported)": "導出為 PDF(即將可用)",
|
||||
"Export Workspace": "導出 Workspace",
|
||||
"Import Workspace": "導入 Workspace",
|
||||
"Language": "語言",
|
||||
"Last edited by": "最後編輯者為 {{name}}",
|
||||
"Layout": "佈局",
|
||||
"Logout": "退出登錄",
|
||||
"Settings": "設置",
|
||||
"Share": "分享",
|
||||
"Sync to Disk": "同步到磁盤",
|
||||
"Turn into": "轉換為",
|
||||
"WarningTips": {
|
||||
"DoNotStore": "我們正在積極開發 AFFiNE,目前版本尚不穩定,請避免存儲信息或數據。",
|
||||
"IsNotLocalWorkspace": "歡迎來到 AFFiNE 演示界面。您可以通過「同步到磁盤」來保存更改。",
|
||||
"IsNotfsApiSupported": "歡迎進入AFFiNE演示!使用最新版本的基於 Chromium 內核的瀏覽器如Chrome/Edge,您可以通過「同步到磁盤」來保存更改"
|
||||
}
|
||||
}
|
||||
{}
|
||||
|
||||
@@ -10,17 +10,18 @@ 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/provider';
|
||||
import ConfirmProvider from '@/providers/confirm-provider';
|
||||
import { ModalProvider } from '@/providers/global-modal-provider';
|
||||
import { AppStateProvider } from '@/providers/app-state-provider/Provider';
|
||||
import ConfirmProvider from '@/providers/ConfirmProvider';
|
||||
import { ModalProvider } from '@/providers/GlobalModalProvider';
|
||||
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 '@/libs/i18n';
|
||||
import TemporaryHelperProvider from '@/providers/temporary-helper-provider';
|
||||
|
||||
const ThemeProvider = dynamic(() => import('@/providers/themeProvider'), {
|
||||
const ThemeProvider = dynamic(() => import('@/providers/ThemeProvider'), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
@@ -52,6 +53,7 @@ const App = ({ Component, pageProps }: AppPropsWithLayout) => {
|
||||
<Logger />
|
||||
<ProviderComposer
|
||||
contexts={[
|
||||
<TemporaryHelperProvider key="TemporaryHelperProvider" />,
|
||||
<ThemeProvider key="ThemeProvider" />,
|
||||
<AppStateProvider key="appStateProvider" />,
|
||||
<ModalProvider key="ModalProvider" />,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { NextPage } from 'next';
|
||||
|
||||
const Home: NextPage = () => {
|
||||
return <div>Home Page</div>;
|
||||
return <div title="Home Page"></div>;
|
||||
};
|
||||
|
||||
export default Home;
|
||||
|
||||
47
packages/app/src/pages/new-workspace/index.tsx
Normal file
47
packages/app/src/pages/new-workspace/index.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import { WorkspaceModal } from '@/components/workspace-modal';
|
||||
import { getWorkspaces } from '@/hooks/mock-data/mock';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { styled } from '@/styles';
|
||||
import Button from '@/ui/button/Button';
|
||||
|
||||
const Page = () => {
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
const data = getWorkspaces();
|
||||
if (!data.length) {
|
||||
setOpen(true);
|
||||
}
|
||||
}, []);
|
||||
return (
|
||||
<Workspace>
|
||||
<h1>workspace</h1>
|
||||
<div>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setOpen(true);
|
||||
}}
|
||||
>
|
||||
View Workspace List
|
||||
</Button>
|
||||
</div>
|
||||
<WorkspaceModal
|
||||
open={open}
|
||||
onClose={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
></WorkspaceModal>
|
||||
</Workspace>
|
||||
);
|
||||
};
|
||||
export default Page;
|
||||
|
||||
const Workspace = styled.div(({ theme }) => {
|
||||
return {
|
||||
height: '100vh',
|
||||
background: theme.colors.pageBackground,
|
||||
color: '#FFFFFF',
|
||||
fontSize: '18px',
|
||||
fontWeight: 500,
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
import { useEffect } from 'react';
|
||||
import type { Page } from '@blocksuite/store';
|
||||
import '@blocksuite/blocks';
|
||||
import { EditorContainer } from '@blocksuite/editor';
|
||||
import type { CreateEditorHandler } from './context';
|
||||
|
||||
interface Props {
|
||||
setCreateEditorHandler: (handler: CreateEditorHandler) => void;
|
||||
}
|
||||
|
||||
const DynamicBlocksuite = ({ setCreateEditorHandler }: Props) => {
|
||||
useEffect(() => {
|
||||
const createEditorHandler: CreateEditorHandler = (page: Page) => {
|
||||
const editor = new EditorContainer();
|
||||
editor.page = page;
|
||||
return editor;
|
||||
};
|
||||
|
||||
setCreateEditorHandler(createEditorHandler);
|
||||
}, [setCreateEditorHandler]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
|
||||
export default DynamicBlocksuite;
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useMemo, useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { useMemo, useState, useCallback, useRef } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { getDataCenter } from '@affine/datacenter';
|
||||
@@ -8,12 +8,20 @@ import type {
|
||||
CreateEditorHandler,
|
||||
LoadWorkspaceHandler,
|
||||
} from './context';
|
||||
import { Page, Workspace as StoreWorkspace } from '@blocksuite/store';
|
||||
import { Page } from '@blocksuite/store';
|
||||
import { EditorContainer } from '@blocksuite/editor';
|
||||
const DynamicBlocksuite = dynamic(() => import('./dynamic-blocksuite'), {
|
||||
const DynamicBlocksuite = dynamic(() => import('./DynamicBlocksuite'), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
const loadWorkspaceHandler: LoadWorkspaceHandler = async workspaceId => {
|
||||
if (workspaceId) {
|
||||
const dc = await getDataCenter();
|
||||
return dc.load(workspaceId, { providerId: 'affine' });
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
export const AppStateProvider = ({ children }: { children?: ReactNode }) => {
|
||||
const refreshWorkspacesMeta = async () => {
|
||||
const dc = await getDataCenter();
|
||||
@@ -30,43 +38,10 @@ export const AppStateProvider = ({ children }: { children?: ReactNode }) => {
|
||||
currentWorkspace: null,
|
||||
currentPage: null,
|
||||
editor: null,
|
||||
// Synced is used to ensure that the provider has synced with the server,
|
||||
// So after Synced set to true, the other state is sure to be set.
|
||||
synced: false,
|
||||
refreshWorkspacesMeta,
|
||||
workspaces: {},
|
||||
synced: true,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const workspacesList = await Promise.all(
|
||||
state.workspacesMeta.map(async ({ id }) => {
|
||||
const workspace =
|
||||
(await loadWorkspaceHandler?.(id, state.user)) || null;
|
||||
return { id, workspace };
|
||||
})
|
||||
);
|
||||
const workspaces: Record<string, StoreWorkspace | null> = {};
|
||||
workspacesList.forEach(({ id, workspace }) => {
|
||||
workspaces[id] = workspace;
|
||||
});
|
||||
setState(state => ({
|
||||
...state,
|
||||
workspaces,
|
||||
}));
|
||||
})();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [state.workspacesMeta]);
|
||||
|
||||
const [loadWorkspaceHandler, _setLoadWorkspaceHandler] =
|
||||
useState<LoadWorkspaceHandler>();
|
||||
const setLoadWorkspaceHandler = useCallback(
|
||||
(handler: LoadWorkspaceHandler) => {
|
||||
_setLoadWorkspaceHandler(() => handler);
|
||||
},
|
||||
[_setLoadWorkspaceHandler]
|
||||
);
|
||||
|
||||
const [createEditorHandler, _setCreateEditorHandler] =
|
||||
useState<CreateEditorHandler>();
|
||||
|
||||
@@ -85,7 +60,7 @@ export const AppStateProvider = ({ children }: { children?: ReactNode }) => {
|
||||
return state.currentWorkspace;
|
||||
}
|
||||
const workspace =
|
||||
(await loadWorkspaceHandler?.(workspaceId, state.user)) || null;
|
||||
(await loadWorkspaceHandler(workspaceId, state.user)) || null;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
@@ -103,6 +78,7 @@ export const AppStateProvider = ({ children }: { children?: ReactNode }) => {
|
||||
}));
|
||||
return workspace;
|
||||
};
|
||||
|
||||
const loadPage = useRef<AppStateContext['loadPage']>(() =>
|
||||
Promise.resolve(null)
|
||||
);
|
||||
@@ -147,17 +123,6 @@ export const AppStateProvider = ({ children }: { children?: ReactNode }) => {
|
||||
setState(state => ({ ...state, editor }));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!loadWorkspaceHandler) {
|
||||
return;
|
||||
}
|
||||
setState(state => ({
|
||||
...state,
|
||||
workspacesMeta: [],
|
||||
synced: true,
|
||||
}));
|
||||
}, [loadWorkspaceHandler]);
|
||||
|
||||
const context = useMemo(
|
||||
() => ({
|
||||
...state,
|
||||
@@ -172,10 +137,7 @@ export const AppStateProvider = ({ children }: { children?: ReactNode }) => {
|
||||
|
||||
return (
|
||||
<AppState.Provider value={context}>
|
||||
<DynamicBlocksuite
|
||||
setLoadWorkspaceHandler={setLoadWorkspaceHandler}
|
||||
setCreateEditorHandler={setCreateEditorHandler}
|
||||
/>
|
||||
<DynamicBlocksuite setCreateEditorHandler={setCreateEditorHandler} />
|
||||
{children}
|
||||
</AppState.Provider>
|
||||
);
|
||||
@@ -5,6 +5,7 @@ import type {
|
||||
Workspace as StoreWorkspace,
|
||||
} from '@blocksuite/store';
|
||||
import type { EditorContainer } from '@blocksuite/editor';
|
||||
|
||||
export type LoadWorkspaceHandler = (
|
||||
workspaceId: string,
|
||||
user?: AccessTokenMessage | null
|
||||
@@ -20,7 +21,7 @@ export interface AppStateValue {
|
||||
|
||||
currentPage: StorePage | null;
|
||||
|
||||
workspaces: Record<string, StoreWorkspace | null>;
|
||||
// workspaces: Record<string, StoreWorkspace | null>;
|
||||
|
||||
editor: EditorContainer | null;
|
||||
synced: boolean;
|
||||
@@ -40,14 +41,10 @@ export interface AppStateContext extends AppStateValue {
|
||||
export const AppState = createContext<AppStateContext>({
|
||||
user: null,
|
||||
workspacesMeta: [],
|
||||
|
||||
currentWorkspaceId: '',
|
||||
currentWorkspace: null,
|
||||
|
||||
currentPage: null,
|
||||
|
||||
editor: null,
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
setState: () => {},
|
||||
createEditor: undefined,
|
||||
@@ -57,7 +54,6 @@ export const AppState = createContext<AppStateContext>({
|
||||
synced: false,
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
refreshWorkspacesMeta: () => {},
|
||||
workspaces: {},
|
||||
});
|
||||
|
||||
export const useAppState = () => {
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
import type { Page } from '@blocksuite/store';
|
||||
import '@blocksuite/blocks';
|
||||
import { EditorContainer } from '@blocksuite/editor';
|
||||
import type { LoadWorkspaceHandler, CreateEditorHandler } from './context';
|
||||
import { getDataCenter } from '@affine/datacenter';
|
||||
|
||||
interface Props {
|
||||
setLoadWorkspaceHandler: (handler: LoadWorkspaceHandler) => void;
|
||||
setCreateEditorHandler: (handler: CreateEditorHandler) => void;
|
||||
}
|
||||
|
||||
const DynamicBlocksuite = ({
|
||||
setLoadWorkspaceHandler,
|
||||
setCreateEditorHandler,
|
||||
}: Props) => {
|
||||
useEffect(() => {
|
||||
const openWorkspace: LoadWorkspaceHandler = async (workspaceId: string) => {
|
||||
if (workspaceId) {
|
||||
const dc = await getDataCenter();
|
||||
return dc.load(workspaceId, { providerId: 'selfhosted' });
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
setLoadWorkspaceHandler(openWorkspace);
|
||||
}, [setLoadWorkspaceHandler]);
|
||||
|
||||
useEffect(() => {
|
||||
const createEditorHandler: CreateEditorHandler = (page: Page) => {
|
||||
const editor = new EditorContainer();
|
||||
editor.page = page;
|
||||
return editor;
|
||||
};
|
||||
|
||||
setCreateEditorHandler(createEditorHandler);
|
||||
}, [setCreateEditorHandler]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
|
||||
export default DynamicBlocksuite;
|
||||
@@ -1,51 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useAppState } from './context';
|
||||
import { usePageHelper } from '@/hooks/use-page-helper';
|
||||
export const useLoadWorkspace = () => {
|
||||
const router = useRouter();
|
||||
const { loadWorkspace, currentWorkspace, currentWorkspaceId } = useAppState();
|
||||
|
||||
const workspaceId = router.query.workspaceId as string;
|
||||
|
||||
useEffect(() => {
|
||||
loadWorkspace?.(workspaceId);
|
||||
}, [workspaceId, loadWorkspace]);
|
||||
|
||||
return currentWorkspaceId === workspaceId ? currentWorkspace : null;
|
||||
};
|
||||
|
||||
export const useLoadPage = () => {
|
||||
const router = useRouter();
|
||||
const { loadPage, currentPage, currentWorkspaceId } = useAppState();
|
||||
const { createPage } = usePageHelper();
|
||||
const workspace = useLoadWorkspace();
|
||||
|
||||
const pageId = router.query.pageId as string;
|
||||
|
||||
useEffect(() => {
|
||||
if (!workspace) {
|
||||
return;
|
||||
}
|
||||
const page = pageId ? workspace?.getPage(pageId) : null;
|
||||
if (page) {
|
||||
loadPage?.(pageId);
|
||||
return;
|
||||
}
|
||||
|
||||
const savedPageId = workspace.meta.pageMetas[0]?.id;
|
||||
if (savedPageId) {
|
||||
router.push(`/workspace/${currentWorkspaceId}/${savedPageId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
createPage().then(async pageId => {
|
||||
if (!pageId) {
|
||||
return;
|
||||
}
|
||||
router.push(`/workspace/${currentWorkspaceId}/${pageId}`);
|
||||
});
|
||||
}, [workspace, pageId, loadPage, createPage, router, currentWorkspaceId]);
|
||||
|
||||
return currentPage?.id === pageId ? currentPage : null;
|
||||
};
|
||||
183
packages/app/src/providers/temporary-helper-provider.tsx
Normal file
183
packages/app/src/providers/temporary-helper-provider.tsx
Normal file
@@ -0,0 +1,183 @@
|
||||
import { createContext, useContext, useEffect, useState } from 'react';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { getWorkspaces, User, Workspace } from '@/hooks/mock-data/mock';
|
||||
|
||||
type TemporaryContextValue = {
|
||||
workspaceMetaList: Workspace[];
|
||||
currentWorkspace: Workspace | null;
|
||||
user: User | null;
|
||||
|
||||
updateWorkspaceMeta: (
|
||||
workspaceId: string,
|
||||
workspaceData: {
|
||||
name?: string;
|
||||
avatar?: string;
|
||||
isPublish?: boolean;
|
||||
type?: 'local' | 'cloud' | 'join';
|
||||
}
|
||||
) => void;
|
||||
|
||||
createWorkspace: (workspaceName: string) => Workspace;
|
||||
|
||||
deleteWorkspace: (workspaceId: string) => void;
|
||||
|
||||
setWorkspacePublish: (id: string, isPublish: boolean) => void;
|
||||
|
||||
setActiveWorkspace: (workspaceData: Workspace) => void;
|
||||
|
||||
login: () => void;
|
||||
|
||||
signOut: () => void;
|
||||
};
|
||||
type TemporaryContextProps = PropsWithChildren<Record<string, unknown>>;
|
||||
|
||||
export const TemporaryContext = createContext<TemporaryContextValue>({
|
||||
workspaceMetaList: [],
|
||||
currentWorkspace: null,
|
||||
user: null,
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
updateWorkspaceMeta: () => {},
|
||||
|
||||
createWorkspace: () => {
|
||||
return {} as Workspace;
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
deleteWorkspace: () => {},
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
setWorkspacePublish: () => {},
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
setActiveWorkspace: () => {},
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
login: () => {},
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
signOut: () => {},
|
||||
});
|
||||
|
||||
export const useTemporaryHelper = () => useContext(TemporaryContext);
|
||||
|
||||
export const TemporaryHelperProvider = ({
|
||||
children,
|
||||
}: PropsWithChildren<TemporaryContextProps>) => {
|
||||
const [workspaceMetaList, setWorkspaceMetaList] = useState<Workspace[]>([]);
|
||||
const [currentWorkspace, setCurrentWorkspace] = useState<Workspace | null>(
|
||||
null
|
||||
);
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setWorkspaceMetaList(getWorkspaces());
|
||||
setCurrentWorkspace(
|
||||
JSON.parse(localStorage.getItem('affine-active-workspace') ?? '{}')
|
||||
);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<TemporaryContext.Provider
|
||||
value={{
|
||||
workspaceMetaList,
|
||||
currentWorkspace,
|
||||
user,
|
||||
|
||||
updateWorkspaceMeta: (workspaceId, workspaceData) => {
|
||||
const workspacesMeta = getWorkspaces();
|
||||
const newWorkspacesMeta = workspacesMeta.map(
|
||||
(workspace: Workspace) => {
|
||||
if (workspace.id === workspaceId) {
|
||||
const workspaceObj = Object.assign(workspace, workspaceData);
|
||||
return workspaceObj;
|
||||
}
|
||||
return workspace;
|
||||
}
|
||||
);
|
||||
localStorage.setItem(
|
||||
'affine-workspace',
|
||||
JSON.stringify(newWorkspacesMeta)
|
||||
);
|
||||
setWorkspaceMetaList(newWorkspacesMeta);
|
||||
if (workspaceId === currentWorkspace?.id) {
|
||||
setCurrentWorkspace(
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
newWorkspacesMeta.find(v => v.id === currentWorkspace.id)
|
||||
);
|
||||
}
|
||||
},
|
||||
createWorkspace: workspaceName => {
|
||||
const workspaceData = {
|
||||
name: workspaceName,
|
||||
id: 'workspace-' + Date.now(),
|
||||
isPublish: false,
|
||||
isLocal: true,
|
||||
avatar: '',
|
||||
type: 'local',
|
||||
} as Workspace;
|
||||
const workspacesMeta = getWorkspaces();
|
||||
workspacesMeta.push(workspaceData);
|
||||
localStorage.setItem(
|
||||
'affine-workspace',
|
||||
JSON.stringify(workspacesMeta)
|
||||
);
|
||||
|
||||
setWorkspaceMetaList([...workspacesMeta]);
|
||||
return workspaceData;
|
||||
},
|
||||
deleteWorkspace: workspaceId => {
|
||||
const workspacesMeta = getWorkspaces();
|
||||
const newWorkspacesMeta = workspacesMeta.filter(() => {
|
||||
return workspaceId !== workspaceId;
|
||||
});
|
||||
localStorage.setItem(
|
||||
'affine-workspace',
|
||||
JSON.stringify(newWorkspacesMeta)
|
||||
);
|
||||
setWorkspaceMetaList(workspacesMeta);
|
||||
},
|
||||
|
||||
setWorkspacePublish: (id, isPublish) => {
|
||||
const workspacesMeta = getWorkspaces();
|
||||
const newWorkspacesMeta = workspacesMeta.map(
|
||||
(workspace: Workspace) => {
|
||||
if (workspace.id === id) {
|
||||
workspace.isPublish = isPublish;
|
||||
}
|
||||
return workspace;
|
||||
}
|
||||
);
|
||||
localStorage.setItem(
|
||||
'affine-workspace',
|
||||
JSON.stringify(newWorkspacesMeta)
|
||||
);
|
||||
setWorkspaceMetaList(workspacesMeta);
|
||||
},
|
||||
setActiveWorkspace(workspaceData) {
|
||||
localStorage.setItem(
|
||||
'affine-active-workspace',
|
||||
JSON.stringify(workspaceData)
|
||||
);
|
||||
setCurrentWorkspace(workspaceData);
|
||||
},
|
||||
|
||||
login: () => {
|
||||
const userInfo = {
|
||||
name: 'Diamond',
|
||||
id: 'ttt',
|
||||
email: 'diamond.shx@gmail.com',
|
||||
avatar: 'string',
|
||||
};
|
||||
localStorage.setItem('affine-user', JSON.stringify(userInfo));
|
||||
setUser(userInfo);
|
||||
},
|
||||
|
||||
signOut(): void {
|
||||
localStorage.removeItem('affine-user');
|
||||
setUser(null);
|
||||
},
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</TemporaryContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default TemporaryHelperProvider;
|
||||
@@ -13,7 +13,7 @@ Let us know what you think of this latest version.
|
||||
5. You can self-host locally with Docker.
|
||||
|
||||
```basic
|
||||
docker run -d -v [YOUR_PATH]:/app/data -p 3000:3000 ghcr.io/toeverything/affine-self-hosted:alpha-abbey-wood
|
||||
docker run -it --name affine -d -v [YOUR_PATH]:/app/data -p 3000:3000 ghcr.io/toeverything/affine-self-hosted:alpha-abbey-wood
|
||||
```
|
||||
|
||||
**Looking for Markdown syntax or keyboard shortcuts?**
|
||||
@@ -24,6 +24,9 @@ docker run -d -v [YOUR_PATH]:/app/data -p 3000:3000 ghcr.io/toeverything/affine-
|
||||
|
||||
- Manage your pages from the collapsible **sidebar**, which allows you to add **favourites** and restore deleted files from the **trash**
|
||||
- Search through all your content with the quick search - activate with `Ctrl/⌘ + K`
|
||||
- A friendly Reminder:
|
||||
- In the case of unselected text, `Ctrl/⌘ + K` activates quick search;
|
||||
- In the case of selected text, `Ctrl/⌘ + K` will firstly ask to add a hyperlink, and then using `Ctrl/⌘ + K` again activates the quick search
|
||||
- Quickly format text with the **pop-up toolbar** (highlight any text to give it a try)
|
||||
- Copy and paste **images** into your pages, resize them and add captions
|
||||
- Add horizontal line dividers to your text with `---` and `***`
|
||||
|
||||
@@ -12,6 +12,7 @@ export type ConfirmProps = {
|
||||
title?: string;
|
||||
content?: string;
|
||||
confirmText?: string;
|
||||
cancelText?: string;
|
||||
// TODO: Confirm button's color should depend on confirm type
|
||||
confirmType?: 'primary' | 'warning' | 'danger';
|
||||
onConfirm?: () => void;
|
||||
@@ -25,6 +26,7 @@ export const Confirm = ({
|
||||
confirmType,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
cancelText = 'Cancel',
|
||||
}: ConfirmProps) => {
|
||||
const [open, setOpen] = useState(true);
|
||||
const { t } = useTranslation();
|
||||
@@ -50,7 +52,7 @@ export const Confirm = ({
|
||||
}}
|
||||
style={{ marginRight: '24px' }}
|
||||
>
|
||||
{t('Cancel')}
|
||||
{cancelText === 'Cancel' ? t('Cancel') : cancelText}
|
||||
</Button>
|
||||
<Button
|
||||
type={confirmType}
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { InputHTMLAttributes, useEffect, useState } from 'react';
|
||||
import {
|
||||
InputHTMLAttributes,
|
||||
useEffect,
|
||||
useState,
|
||||
FocusEventHandler,
|
||||
KeyboardEventHandler,
|
||||
} from 'react';
|
||||
import { StyledInput } from './style';
|
||||
|
||||
type inputProps = {
|
||||
@@ -9,8 +15,8 @@ type inputProps = {
|
||||
maxLength?: number;
|
||||
minLength?: number;
|
||||
onChange?: (value: string) => void;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
onBlur?: (e: any) => void;
|
||||
onBlur?: FocusEventHandler<HTMLInputElement>;
|
||||
onKeyDown?: KeyboardEventHandler<HTMLInputElement>;
|
||||
};
|
||||
|
||||
export const Input = (props: inputProps) => {
|
||||
@@ -23,6 +29,7 @@ export const Input = (props: inputProps) => {
|
||||
width = 260,
|
||||
onChange,
|
||||
onBlur,
|
||||
onKeyDown,
|
||||
} = props;
|
||||
const [value, setValue] = useState<string>(valueProp || '');
|
||||
const handleChange: InputHTMLAttributes<HTMLInputElement>['onChange'] = e => {
|
||||
@@ -37,6 +44,10 @@ export const Input = (props: inputProps) => {
|
||||
const handleBlur: InputHTMLAttributes<HTMLInputElement>['onBlur'] = e => {
|
||||
onBlur && onBlur(e);
|
||||
};
|
||||
const handleKeyDown: InputHTMLAttributes<HTMLInputElement>['onKeyDown'] =
|
||||
e => {
|
||||
onKeyDown && onKeyDown(e);
|
||||
};
|
||||
useEffect(() => {
|
||||
setValue(valueProp || '');
|
||||
}, [valueProp]);
|
||||
@@ -50,6 +61,7 @@ export const Input = (props: inputProps) => {
|
||||
minLength={minLength}
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
onKeyDown={handleKeyDown}
|
||||
></StyledInput>
|
||||
);
|
||||
};
|
||||
|
||||
20
packages/app/src/utils/colors.ts
Normal file
20
packages/app/src/utils/colors.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
export function stringToColour(str: string) {
|
||||
str = str || 'affine';
|
||||
let colour = '#';
|
||||
let hash = 0;
|
||||
// str to hash
|
||||
for (
|
||||
let i = 0;
|
||||
i < str.length;
|
||||
hash = str.charCodeAt(i++) + ((hash << 5) - hash)
|
||||
);
|
||||
|
||||
// int/hash to hex
|
||||
for (
|
||||
let i = 0;
|
||||
i < 3;
|
||||
colour += ('00' + ((hash >> (i++ * 8)) & 0xff).toString(16)).slice(-2)
|
||||
);
|
||||
|
||||
return colour;
|
||||
}
|
||||
@@ -3,3 +3,5 @@ export { isDev } from './env';
|
||||
export * from './useragent';
|
||||
|
||||
export * from './tools';
|
||||
|
||||
export * from './colors';
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user