diff --git a/.eslintignore b/.eslintignore index f2e3541e4..4e1862166 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,5 +1,4 @@ **/webpack.config.js -**/jest.config.js **/scripts/*.js **/node_modules/** .github/** diff --git a/.github/workflows/build-test-version.yml b/.github/workflows/build-test-version.yml index 3862fefa3..9eb1aaf6d 100644 --- a/.github/workflows/build-test-version.yml +++ b/.github/workflows/build-test-version.yml @@ -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 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bcc016642..6d0c26263 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 94d64c8f4..00ce55b51 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -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 diff --git a/.gitignore b/.gitignore index 24bb27912..f9333264d 100644 --- a/.gitignore +++ b/.gitignore @@ -48,6 +48,7 @@ Thumbs.db out/ module-resolve.js +module-resolve.cjs /test-results/ /playwright-report/ /playwright/.cache/ diff --git a/.pnpmfile.cjs b/.pnpmfile.cjs index 4873d9492..d2541eb7e 100644 --- a/.pnpmfile.cjs +++ b/.pnpmfile.cjs @@ -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; } diff --git a/.vscode/extensions.json b/.vscode/extensions.json index f192bb1ba..bbe3b2cbf 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -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" + ] } diff --git a/package.json b/package.json index 09b6405ad..52151ef77 100644 --- a/package.json +++ b/package.json @@ -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, diff --git a/packages/app/jest.config.js b/packages/app/jest.config.js deleted file mode 100644 index 3745fc223..000000000 --- a/packages/app/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -/** @type {import('ts-jest').JestConfigWithTsJest} */ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', -}; diff --git a/packages/app/package.json b/packages/app/package.json index 3ba74e271..16fd889fd 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -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": { diff --git a/packages/app/src/components/contact-modal/icons.tsx b/packages/app/src/components/contact-modal/Icons.tsx similarity index 100% rename from packages/app/src/components/contact-modal/icons.tsx rename to packages/app/src/components/contact-modal/Icons.tsx diff --git a/packages/app/src/components/contact-modal/index.tsx b/packages/app/src/components/contact-modal/index.tsx index 921b34ba6..0cb97f117 100644 --- a/packages/app/src/components/contact-modal/index.tsx +++ b/packages/app/src/components/contact-modal/index.tsx @@ -8,7 +8,7 @@ import { TelegramIcon, RedditIcon, LinkIcon, -} from './icons'; +} from './Icons'; import logo from './affine-text-logo.png'; import { StyledBigLink, diff --git a/packages/app/src/components/create-workspace/index.tsx b/packages/app/src/components/create-workspace/index.tsx new file mode 100644 index 000000000..05c37f990 --- /dev/null +++ b/packages/app/src/components/create-workspace/index.tsx @@ -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) => { + if (event.key === 'Enter') { + // 👇 Get input value + handleCreateWorkspace(); + } + }; + const { t } = useTranslation(); + return ( +
+ + +
+ {t('New Workspace')} + { + onClose({}); + }} + /> +
+ +

{t('Workspace description')}

+ { + setWorkspaceName(value); + }} + > + +
+
+
+
+ ); +}; + +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', +// }); diff --git a/packages/app/src/components/edgeless-toolbar/icons.tsx b/packages/app/src/components/edgeless-toolbar/Icons.tsx similarity index 100% rename from packages/app/src/components/edgeless-toolbar/icons.tsx rename to packages/app/src/components/edgeless-toolbar/Icons.tsx diff --git a/packages/app/src/components/edgeless-toolbar/index.tsx b/packages/app/src/components/edgeless-toolbar/index.tsx index c44f6396c..c30e9a358 100644 --- a/packages/app/src/components/edgeless-toolbar/index.tsx +++ b/packages/app/src/components/edgeless-toolbar/index.tsx @@ -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: , - 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: , + toolTip: t('Select'), + disable: false, + callback: () => { + window.dispatchEvent( + new CustomEvent('affine.switch-mouse-mode', { + detail: { + type: 'default', + }, + }) + ); + }, }, - }, - { - flavor: 'text', - icon: , - toolTip: 'Text (coming soon)', - disable: true, - }, - { - flavor: 'shape', - icon: , - toolTip: 'Shape', - disable: false, - callback: () => { - window.dispatchEvent( - new CustomEvent('affine.switch-mouse-mode', { - detail: { - type: 'shape', - color: 'black', - shape: 'rectangle', - }, - }) - ); + { + flavor: 'text', + icon: , + toolTip: t('Text'), + disable: true, + }, + { + flavor: 'shape', + icon: , + toolTip: t('Shape'), + disable: false, + callback: () => { + window.dispatchEvent( + new CustomEvent('affine.switch-mouse-mode', { + detail: { + type: 'shape', + color: 'black', + shape: 'rectangle', + }, + }) + ); + }, + }, + { + flavor: 'sticky', + icon: , + toolTip: t('Sticky'), + disable: true, + }, + { + flavor: 'pen', + icon: , + toolTip: t('Pen'), + disable: true, }, - }, - { - flavor: 'sticky', - icon: , - toolTip: 'Sticky (coming soon)', - disable: true, - }, - { - flavor: 'pen', - icon: , - toolTip: 'Pen (coming soon)', - disable: true, - }, - { - flavor: 'connector', - icon: , - toolTip: 'Connector (coming soon)', - disable: true, - }, -]; + { + flavor: 'connector', + icon: , + 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 ( - + { @@ -105,7 +109,7 @@ const UndoRedo = () => { - + { @@ -131,7 +135,7 @@ export const EdgelessToolbar = () => { > - {toolbarList1.map( + {useToolbarList1().map( ({ icon, toolTip, flavor, disable, callback }, index) => { return ( diff --git a/packages/app/src/components/editor-mode-switch/icons.tsx b/packages/app/src/components/editor-mode-switch/Icons.tsx similarity index 100% rename from packages/app/src/components/editor-mode-switch/icons.tsx rename to packages/app/src/components/editor-mode-switch/Icons.tsx diff --git a/packages/app/src/components/editor-mode-switch/index.tsx b/packages/app/src/components/editor-mode-switch/index.tsx index 86a54d860..2e8ccfdcf 100644 --- a/packages/app/src/components/editor-mode-switch/index.tsx +++ b/packages/app/src/components/editor-mode-switch/index.tsx @@ -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'; diff --git a/packages/app/src/components/file-upload/index.tsx b/packages/app/src/components/file-upload/index.tsx index 25d91fb06..090d628b2 100644 --- a/packages/app/src/components/file-upload/index.tsx +++ b/packages/app/src/components/file-upload/index.tsx @@ -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 => { const { fileChange, accept } = props; + const { t } = useTranslation(); const input_ref = useRef(null); const _chooseFile = () => { if (input_ref.current) { @@ -28,7 +30,7 @@ export const Upload: FC = props => { }; return ( - {props.children ?? } + {props.children ?? } { const { permanentlyDeletePage } = usePageHelper(); @@ -12,7 +13,7 @@ export const TrashButtonGroup = () => { const { confirm } = useConfirm(); const router = useRouter(); const { id = '' } = useCurrentPageMeta() || {}; - + const { t } = useTranslation(); return ( <> ); diff --git a/packages/app/src/components/header/header-right-items/theme-mode-switch/icons.tsx b/packages/app/src/components/header/header-right-items/theme-mode-switch/Icons.tsx similarity index 100% rename from packages/app/src/components/header/header-right-items/theme-mode-switch/icons.tsx rename to packages/app/src/components/header/header-right-items/theme-mode-switch/Icons.tsx diff --git a/packages/app/src/components/header/header-right-items/theme-mode-switch/index.tsx b/packages/app/src/components/header/header-right-items/theme-mode-switch/index.tsx index 20f6c86bd..c1d842e23 100644 --- a/packages/app/src/components/header/header-right-items/theme-mode-switch/index.tsx +++ b/packages/app/src/components/header/header-right-items/theme-mode-switch/index.tsx @@ -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 = () => { diff --git a/packages/app/src/components/header/index.tsx b/packages/app/src/components/header/index.tsx index d31b8f7ab..8af7382af 100644 --- a/packages/app/src/components/header/index.tsx +++ b/packages/app/src/components/header/index.tsx @@ -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'; diff --git a/packages/app/src/components/help-island/icons.tsx b/packages/app/src/components/help-island/Icons.tsx similarity index 100% rename from packages/app/src/components/help-island/icons.tsx rename to packages/app/src/components/help-island/Icons.tsx diff --git a/packages/app/src/components/help-island/index.tsx b/packages/app/src/components/help-island/index.tsx index 64ef678d8..b904cefb5 100644 --- a/packages/app/src/components/help-island/index.tsx +++ b/packages/app/src/components/help-island/index.tsx @@ -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 = ({ diff --git a/packages/app/src/components/invite-members/index.tsx b/packages/app/src/components/invite-members/index.tsx index 22a72220e..6fb020fc5 100644 --- a/packages/app/src/components/invite-members/index.tsx +++ b/packages/app/src/components/invite-members/index.tsx @@ -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({}); 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(() => { diff --git a/packages/app/src/components/loading/loading.tsx b/packages/app/src/components/loading/Loading.tsx similarity index 100% rename from packages/app/src/components/loading/loading.tsx rename to packages/app/src/components/loading/Loading.tsx diff --git a/packages/app/src/components/loading/page-loading.tsx b/packages/app/src/components/loading/PageLoading.tsx similarity index 95% rename from packages/app/src/components/loading/page-loading.tsx rename to packages/app/src/components/loading/PageLoading.tsx index e4a802fa6..025eba768 100644 --- a/packages/app/src/components/loading/page-loading.tsx +++ b/packages/app/src/components/loading/PageLoading.tsx @@ -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')(() => { diff --git a/packages/app/src/components/loading/index.tsx b/packages/app/src/components/loading/index.tsx index d435470de..49aaacec8 100644 --- a/packages/app/src/components/loading/index.tsx +++ b/packages/app/src/components/loading/index.tsx @@ -1,3 +1,3 @@ -import Loading from './loading'; -export * from './page-loading'; +import Loading from './Loading'; +export * from './PageLoading'; export default Loading; diff --git a/packages/app/src/components/login-modal/icons.tsx b/packages/app/src/components/login-modal/Icons.tsx similarity index 100% rename from packages/app/src/components/login-modal/icons.tsx rename to packages/app/src/components/login-modal/Icons.tsx diff --git a/packages/app/src/components/login-modal/LoginOptionButton.tsx b/packages/app/src/components/login-modal/LoginOptionButton.tsx index f8e3cdafc..2199b21dc 100644 --- a/packages/app/src/components/login-modal/LoginOptionButton.tsx +++ b/packages/app/src/components/login-modal/LoginOptionButton.tsx @@ -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(); diff --git a/packages/app/src/components/page-list/date-cell.tsx b/packages/app/src/components/page-list/DateCell.tsx similarity index 100% rename from packages/app/src/components/page-list/date-cell.tsx rename to packages/app/src/components/page-list/DateCell.tsx diff --git a/packages/app/src/components/page-list/empty.tsx b/packages/app/src/components/page-list/Empty.tsx similarity index 100% rename from packages/app/src/components/page-list/empty.tsx rename to packages/app/src/components/page-list/Empty.tsx diff --git a/packages/app/src/components/page-list/operation-cell.tsx b/packages/app/src/components/page-list/OperationCell.tsx similarity index 98% rename from packages/app/src/components/page-list/operation-cell.tsx rename to packages/app/src/components/page-list/OperationCell.tsx index 1b4ec7434..d72b99b51 100644 --- a/packages/app/src/components/page-list/operation-cell.tsx +++ b/packages/app/src/components/page-list/OperationCell.tsx @@ -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'; diff --git a/packages/app/src/components/page-list/index.tsx b/packages/app/src/components/page-list/index.tsx index 604d71000..2d5f13ed5 100644 --- a/packages/app/src/components/page-list/index.tsx +++ b/packages/app/src/components/page-list/index.tsx @@ -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 }, diff --git a/packages/app/src/components/quick-search/footer.tsx b/packages/app/src/components/quick-search/Footer.tsx similarity index 94% rename from packages/app/src/components/quick-search/footer.tsx rename to packages/app/src/components/quick-search/Footer.tsx index 48914cda7..59680216f 100644 --- a/packages/app/src/components/quick-search/footer.tsx +++ b/packages/app/src/components/quick-search/Footer.tsx @@ -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'; diff --git a/packages/app/src/components/quick-search/input.tsx b/packages/app/src/components/quick-search/Input.tsx similarity index 100% rename from packages/app/src/components/quick-search/input.tsx rename to packages/app/src/components/quick-search/Input.tsx diff --git a/packages/app/src/components/quick-search/noResultSVG.tsx b/packages/app/src/components/quick-search/NoResultSVG.tsx similarity index 100% rename from packages/app/src/components/quick-search/noResultSVG.tsx rename to packages/app/src/components/quick-search/NoResultSVG.tsx diff --git a/packages/app/src/components/quick-search/results.tsx b/packages/app/src/components/quick-search/Results.tsx similarity index 97% rename from packages/app/src/components/quick-search/results.tsx rename to packages/app/src/components/quick-search/Results.tsx index eeb9fc2d4..fc22e9713 100644 --- a/packages/app/src/components/quick-search/results.tsx +++ b/packages/app/src/components/quick-search/Results.tsx @@ -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'; diff --git a/packages/app/src/components/quick-search/index.tsx b/packages/app/src/components/quick-search/index.tsx index 7475d9756..b08f69196 100644 --- a/packages/app/src/components/quick-search/index.tsx +++ b/packages/app/src/components/quick-search/index.tsx @@ -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 = { diff --git a/packages/app/src/components/shortcuts-modal/icons.tsx b/packages/app/src/components/shortcuts-modal/Icons.tsx similarity index 100% rename from packages/app/src/components/shortcuts-modal/icons.tsx rename to packages/app/src/components/shortcuts-modal/Icons.tsx diff --git a/packages/app/src/components/shortcuts-modal/config.ts b/packages/app/src/components/shortcuts-modal/config.ts index 7749209fe..95dd4c5b6 100644 --- a/packages/app/src/components/shortcuts-modal/config.ts +++ b/packages/app/src/components/shortcuts-modal/config.ts @@ -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', diff --git a/packages/app/src/components/shortcuts-modal/index.tsx b/packages/app/src/components/shortcuts-modal/index.tsx index 4f28b0ece..980f281d7 100644 --- a/packages/app/src/components/shortcuts-modal/index.tsx +++ b/packages/app/src/components/shortcuts-modal/index.tsx @@ -1,5 +1,5 @@ import { createPortal } from 'react-dom'; -import { KeyboardIcon } from './icons'; +import { KeyboardIcon } from './Icons'; import { StyledListItem, StyledModalHeader, diff --git a/packages/app/src/components/workspace-avatar/index.tsx b/packages/app/src/components/workspace-avatar/index.tsx new file mode 100644 index 000000000..b810a0e72 --- /dev/null +++ b/packages/app/src/components/workspace-avatar/index.tsx @@ -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 ( + <> +
+ {(props.name || 'AFFiNE').substring(0, 1)} +
+ + ); +}; diff --git a/packages/app/src/components/workspace-modal/index.tsx b/packages/app/src/components/workspace-modal/index.tsx new file mode 100644 index 000000000..cb0605a78 --- /dev/null +++ b/packages/app/src/components/workspace-modal/index.tsx @@ -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 ( +
+ + +
+ My Workspaces + {/* */} + { + onClose(); + }} + /> +
+ + + {workspaceMetaList.map((item, index) => { + return ( + { + setActiveWorkspace(item); + onClose(); + }} + key={index} + > + +
+ +
+ + + {item.name || 'AFFiNE'} + +
+ + {(item.type === 'local' || !item.type) && ( + + )} + {item.type === 'cloud' && ( + + )} + {item.isPublish && } + + {/* {item.isLocal ? 'isLocal' : ''}/ */} +
+ ); + })} +
  • + +
  • +
    +

    + Tips:Workspace is your virtual space to capture, create and plan + as just one person or together as a team. +

    +
    +
    + {!user ? ( + + ) : ( + + )} +
    + { + 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 }); + } + } + }); + }} + > +
    +
    +
    + ); +}; + +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', + }, +}); diff --git a/packages/app/src/components/workspace-modal/languageMenu.tsx b/packages/app/src/components/workspace-modal/languageMenu.tsx new file mode 100644 index 000000000..557a4f8f0 --- /dev/null +++ b/packages/app/src/components/workspace-modal/languageMenu.tsx @@ -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 ( + + {LOCALES.map(option => { + return ( + { + changeLanguage(option.tag); + setShow(false); + setLanguageName(option.originalName); + }} + > + {option.originalName} + + ); + })} + + } + open={show} + > + { + setShow(!show); + }} + > + + {languageName} + + + + + ); +}; + +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) => ( + +))(({ 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, +})); diff --git a/packages/app/src/components/workspace-modal/styles.ts b/packages/app/src/components/workspace-modal/styles.ts new file mode 100644 index 000000000..40a8bcc37 --- /dev/null +++ b/packages/app/src/components/workspace-modal/styles.ts @@ -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'), + }; +}); diff --git a/packages/app/src/components/workspace-setting/ExportPage.tsx b/packages/app/src/components/workspace-setting/ExportPage.tsx new file mode 100644 index 000000000..531e88993 --- /dev/null +++ b/packages/app/src/components/workspace-setting/ExportPage.tsx @@ -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 ( + + Export Workspace{' '} + {workspace.name} Is Comming + + ); +}; diff --git a/packages/app/src/components/workspace-setting/MembersPage.tsx b/packages/app/src/components/workspace-setting/MembersPage.tsx new file mode 100644 index 000000000..0d45f4afa --- /dev/null +++ b/packages/app/src/components/workspace-setting/MembersPage.tsx @@ -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([]); + 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 ( +
    + {workspace.type === 'cloud' ? ( + <> + + + Users({members.length}) + + Access level + + + {members.length === 0 && ( + + )} + {members.length ? ( + members.map((member, index) => { + return ( + + + + + + + + {member.name} + + {member.email} + + + + {/* {member.accepted + ? member.type !== 99 + ? 'Member' + : 'Workspace Owner' + : 'Pending'} */} + Pending + + + + { + 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={} + > + Delete + + + } + placement="bottom-end" + disablePortal={true} + > + + + + + + + ); + }) + ) : ( + <> + )} + + + + { + setIsInviteModalShow(false); + }} + onInviteSuccess={() => { + setMembersList(); + setIsInviteModalShow(false); + // refreshMembers(); + }} + workspaceId={workspace.id} + open={isInviteModalShow} + > + + + ) : ( + +
    + Collaborating with other members requires AFFiNE Cloud service. +
    +
    + +
    +
    + )} +
    + ); +}; diff --git a/packages/app/src/components/workspace-setting/PublishPage.tsx b/packages/app/src/components/workspace-setting/PublishPage.tsx new file mode 100644 index 000000000..2e0105214 --- /dev/null +++ b/packages/app/src/components/workspace-setting/PublishPage.tsx @@ -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' ? ( +
    + + {workspace?.isPublish ? ( + <> + + Publishing to web requires AFFiNE Cloud service . + + Share with link + + + + + + + + ) : ( + + After publishing to the web, everyone can view the content of + this workspace through the link. + + )} + + {workspace.isPublish ? ( + + ) : ( + + )} +
    + ) : ( + + <> + + Publishing to web requires AFFiNE Cloud service. + + + + + + + + )} + + ); +}; diff --git a/packages/app/src/components/workspace-setting/SyncPage.tsx b/packages/app/src/components/workspace-setting/SyncPage.tsx new file mode 100644 index 000000000..c16ea3f0a --- /dev/null +++ b/packages/app/src/components/workspace-setting/SyncPage.tsx @@ -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 ( +
    + + {currentWorkspace?.type === 'local' ? ( + <> + + {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. + + + + + + + ) : ( + <> + + {currentWorkspace && currentWorkspace.name} is Cloud + Workspace. All data will be synchronized and saved to the AFFiNE + + + + { + deleteMember(workspace.id, 0); + }} + icon={} + > + Download core data to device + + { + deleteMember(workspace.id, 0); + }} + icon={} + > + Download all data to device + + + } + placement="bottom-end" + disablePortal={true} + > + + + + + )} + +
    + ); +}; diff --git a/packages/app/src/components/workspace-setting/WorkspaceSetting.tsx b/packages/app/src/components/workspace-setting/WorkspaceSetting.tsx new file mode 100644 index 000000000..cad8b82d8 --- /dev/null +++ b/packages/app/src/components/workspace-setting/WorkspaceSetting.tsx @@ -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 ( + + { + onTabChange && onTabChange(ActiveTab.general); + }} + > + + + + General + + + { + onTabChange && onTabChange(ActiveTab.sync); + }} + > + + + + Sync + + + { + onTabChange && onTabChange(ActiveTab.members); + }} + > + + + + Collaboration + + { + onTabChange && onTabChange(ActiveTab.publish); + }} + > + + + + Publish + + + { + onTabChange && onTabChange(ActiveTab.export); + }} + > + + + + Export + + + ); +}; + +export const WorkspaceSetting = ({ + isShow, + onClose, +}: WorkspaceSettingProps) => { + // const { workspaces } = useAppState(); + const [activeTab, setActiveTab] = useState(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 ( + + + + {isOwner ? ( + + + Workspace Settings + + + + ) : null} + + {activeTab === ActiveTab.general && currentWorkspace && ( + + )} + {activeTab === ActiveTab.sync && currentWorkspace && ( + + )} + {activeTab === ActiveTab.members && currentWorkspace && ( + + )} + {activeTab === ActiveTab.publish && currentWorkspace && ( + + )} + {activeTab === ActiveTab.export && currentWorkspace && ( + + )} + + + + ); +}; diff --git a/packages/app/src/components/workspace-setting/general/general.tsx b/packages/app/src/components/workspace-setting/general/General.tsx similarity index 52% rename from packages/app/src/components/workspace-setting/general/general.tsx rename to packages/app/src/components/workspace-setting/general/General.tsx index fc3c4cacf..0981a2a1f 100644 --- a/packages/app/src/components/workspace-setting/general/general.tsx +++ b/packages/app/src/components/workspace-setting/general/General.tsx @@ -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; -}) => { - 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(false); const [showLeave, setShowLeave] = useState(false); const [uploading, setUploading] = useState(false); - const [workspaceName, setWorkspaceName] = useState( - 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(''); + // 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 ? (
    - Workspace Avatar + Workspace Icon - - {workspaces[workspace.id]?.meta.name[0]} - + +
    Upload {/* TODO: add upload logic */} - {/* {isOwner ? ( - upload - ) : null} */} - {/* */} - Workspace Name + Workspace Name + { + handleUpdateWorkspaceName(); + }} + style={{ marginLeft: '10px' }} + > + ✔️ + - Workspace Owner + {/* {userInfo ? ( +
    + Workspace Owner + + + +
    + ) : ( + '' + )} */} + + Workspace Type - + {workspace.type} {isOwner ? ( @@ -143,9 +135,7 @@ export const GeneralPage = ({ ) : ( @@ -158,7 +148,6 @@ export const GeneralPage = ({ onClose={handleCloseLeave} workspaceName={workspaceName} workspaceId={workspace.id} - nextWorkSpaceId={nextWorkSpaceId} /> )} diff --git a/packages/app/src/components/workspace-setting/general/delete/delete.tsx b/packages/app/src/components/workspace-setting/general/delete/Delete.tsx similarity index 65% rename from packages/app/src/components/workspace-setting/general/delete/delete.tsx rename to packages/app/src/components/workspace-setting/general/delete/Delete.tsx index 5ba8b120f..df414d6bd 100644 --- a/packages/app/src/components/workspace-setting/general/delete/delete.tsx +++ b/packages/app/src/components/workspace-setting/general/delete/Delete.tsx @@ -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(''); - 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 = ({ Delete Workspace - - This action cannot be undone. This will permanently delete ( - {workspaceName}) along with - all its content. - + {workspace.type === 'local' ? ( + + Deleting ( + {workspace.name}) cannot + be undone, please proceed with caution. along with all its content. + + ) : ( + + Deleting ( + {workspace.name}) will + delete both local and cloud data, this operation cannot be undone, + please proceed with caution. + + )} { 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', }; }); diff --git a/packages/app/src/components/workspace-setting/general/index.ts b/packages/app/src/components/workspace-setting/general/index.ts index 12bc2fe54..0ab02c523 100644 --- a/packages/app/src/components/workspace-setting/general/index.ts +++ b/packages/app/src/components/workspace-setting/general/index.ts @@ -1 +1 @@ -export * from './general'; +export * from './General'; diff --git a/packages/app/src/components/workspace-setting/general/leave/leave.tsx b/packages/app/src/components/workspace-setting/general/leave/Leave.tsx similarity index 85% rename from packages/app/src/components/workspace-setting/general/leave/leave.tsx rename to packages/app/src/components/workspace-setting/general/leave/Leave.tsx index baea05491..a648da6a0 100644 --- a/packages/app/src/components/workspace-setting/general/leave/leave.tsx +++ b/packages/app/src/components/workspace-setting/general/leave/Leave.tsx @@ -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(); }; diff --git a/packages/app/src/components/workspace-setting/general/leave/index.ts b/packages/app/src/components/workspace-setting/general/leave/index.ts index ae98410fa..c3bfa43ca 100644 --- a/packages/app/src/components/workspace-setting/general/leave/index.ts +++ b/packages/app/src/components/workspace-setting/general/leave/index.ts @@ -1 +1 @@ -export * from './leave'; +export * from './Leave'; diff --git a/packages/app/src/components/workspace-setting/general/style.ts b/packages/app/src/components/workspace-setting/general/style.ts index e4b505f22..3ed8ff0b5 100644 --- a/packages/app/src/components/workspace-setting/general/style.ts +++ b/packages/app/src/components/workspace-setting/general/style.ts @@ -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', + }; +}); diff --git a/packages/app/src/components/workspace-setting/index.ts b/packages/app/src/components/workspace-setting/index.ts index eda9c6aeb..db995db5e 100644 --- a/packages/app/src/components/workspace-setting/index.ts +++ b/packages/app/src/components/workspace-setting/index.ts @@ -1 +1 @@ -export * from './workspace-setting'; +export * from './WorkspaceSetting'; diff --git a/packages/app/src/components/workspace-setting/style.ts b/packages/app/src/components/workspace-setting/style.ts index f59125a9f..7b4e64552 100644 --- a/packages/app/src/components/workspace-setting/style.ts +++ b/packages/app/src/components/workspace-setting/style.ts @@ -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', }; }); diff --git a/packages/app/src/components/workspace-setting/workspace-setting.tsx b/packages/app/src/components/workspace-setting/workspace-setting.tsx deleted file mode 100644 index fc11df64b..000000000 --- a/packages/app/src/components/workspace-setting/workspace-setting.tsx +++ /dev/null @@ -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 ( - - { - onTabChange && onTabChange(ActiveTab.general); - }} - > - - - - General - - { - onTabChange && onTabChange(ActiveTab.members); - }} - > - - - - Members - - { - onTabChange && onTabChange(ActiveTab.publish); - }} - > - - - - Publish - - - ); -}; - -export const WorkspaceSetting = ({ - isShow, - onClose, - workspace, - owner, -}: WorkspaceSettingProps) => { - const { user, workspaces } = useAppState(); - const [activeTab, setActiveTab] = useState(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 ( - - - - {isOwner ? ( - - - Workspace Settings - - - - ) : null} - - {activeTab === ActiveTab.general && ( - - )} - {activeTab === ActiveTab.members && workspace && ( - - )} - {activeTab === ActiveTab.publish && ( - - )} - - - - ); -}; - -const MembersPage = ({ workspace }: { workspace: Workspace }) => { - const [isInviteModalShow, setIsInviteModalShow] = useState(false); - const [members, setMembers] = useState([]); - 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 ( -
    - - - Users({members.length}) - - Access level - - - {members.length === 0 && ( - - )} - {members.length ? ( - members.map(member => { - return ( - - - {member.user.type === 'Registered' ? ( - - ) : ( - - - - )} - - - {member.user.type === 'Registered' ? ( - {member.user.name} - ) : ( - <> - )} - {member.user.email} - - - - {member.accepted - ? member.type !== 99 - ? 'Member' - : 'Workspace Owner' - : 'Pending'} - - - - { - // 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={} - > - Delete - - - } - placement="bottom-end" - disablePortal={true} - > - - - - - - - ); - }) - ) : ( - <> - )} - - - - { - setIsInviteModalShow(false); - }} - onInviteSuccess={() => { - refreshMembers(); - }} - workspaceId={workspace.id} - open={isInviteModalShow} - > - -
    - ); -}; - -const PublishPage = ({ workspace }: { workspace: Workspace }) => { - const shareUrl = window.location.host + '/workspace/' + workspace.id; - const [publicStatus, setPublicStatus] = useState( - 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 ( -
    - - {publicStatus ? ( - <> - - The current workspace has been published to the web, everyone can - view the contents of this workspace through the link. - - Share with link - - - - - - - - ) : ( - - After publishing to the web, everyone can view the content of this - workspace through the link. - - )} - - {!publicStatus ? ( - - ) : ( - - )} -
    - ); -}; diff --git a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/SelectorPopperContent.tsx b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/SelectorPopperContent.tsx index 5a35becfc..f8bca4787 100644 --- a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/SelectorPopperContent.tsx +++ b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/SelectorPopperContent.tsx @@ -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( 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 = ({ {settingWorkspace ? ( ) : null} diff --git a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/CreateWorkspaceItem/workspace-create/workspace-create.tsx b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/CreateWorkspaceItem/workspace-create/WorkspaceCreate.tsx similarity index 100% rename from packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/CreateWorkspaceItem/workspace-create/workspace-create.tsx rename to packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/CreateWorkspaceItem/workspace-create/WorkspaceCreate.tsx diff --git a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/CreateWorkspaceItem/workspace-create/index.ts b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/CreateWorkspaceItem/workspace-create/index.ts index 38645e20c..85746bc70 100644 --- a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/CreateWorkspaceItem/workspace-create/index.ts +++ b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/CreateWorkspaceItem/workspace-create/index.ts @@ -1 +1 @@ -export * from './workspace-create'; +export * from './WorkspaceCreate'; diff --git a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/LoginItem.tsx b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/LoginItem.tsx index 66b093e64..d7771f1ac 100644 --- a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/LoginItem.tsx +++ b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/LoginItem.tsx @@ -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, diff --git a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceSelector.tsx b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceSelector.tsx index 4e0299888..5dabc24c2 100644 --- a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceSelector.tsx +++ b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceSelector.tsx @@ -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 ( - } - zIndex={1000} - placement="bottom-start" - trigger="click" - onVisibleChange={setIsShow} - > - + <> + { + setWorkspaceListShow(true); + }} + data-testid="current-workspace" + > - {workspaceMeta?.type === WorkspaceType.Private && user ? ( - user?.name[0] - ) : ( - - )} +
    + +
    - {workspaceMeta?.type === WorkspaceType.Private - ? user - ? user.name - : 'AFFiNE' - : currentWorkspace?.meta.name || 'AFFiNE'} + {currentWorkspace?.name ?? 'AFFiNE'}
    -
    + { + setWorkspaceListShow(false); + }} + > + ); }; diff --git a/packages/app/src/components/workspace-slider-bar/icons/icons.tsx b/packages/app/src/components/workspace-slider-bar/icons/Icons.tsx similarity index 100% rename from packages/app/src/components/workspace-slider-bar/icons/icons.tsx rename to packages/app/src/components/workspace-slider-bar/icons/Icons.tsx diff --git a/packages/app/src/components/workspace-slider-bar/index.tsx b/packages/app/src/components/workspace-slider-bar/index.tsx index 61e81cb3e..333dea3e6 100644 --- a/packages/app/src/components/workspace-slider-bar/index.tsx +++ b/packages/app/src/components/workspace-slider-bar/index.tsx @@ -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 = () => { + { + setShowWorkspaceSetting(true); + }} + > + Setting + + + { + setShowWorkspaceSetting(false); + }} + /> { diff --git a/packages/app/src/components/workspace-slider-bar/style.ts b/packages/app/src/components/workspace-slider-bar/style.ts index f29de60a1..6f380b43e 100644 --- a/packages/app/src/components/workspace-slider-bar/style.ts +++ b/packages/app/src/components/workspace-slider-bar/style.ts @@ -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 ? { diff --git a/packages/app/src/hooks/mock-data/mock.ts b/packages/app/src/hooks/mock-data/mock.ts new file mode 100644 index 000000000..9a091e202 --- /dev/null +++ b/packages/app/src/hooks/mock-data/mock.ts @@ -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'); +} diff --git a/packages/app/src/libs/i18n/resources/bn.json b/packages/app/src/libs/i18n/resources/bn.json index 5eb50f8cd..0967ef424 100644 --- a/packages/app/src/libs/i18n/resources/bn.json +++ b/packages/app/src/libs/i18n/resources/bn.json @@ -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": "ডিস্ক এ সিঙ্ক করুন" -} +{} diff --git a/packages/app/src/libs/i18n/resources/en.json b/packages/app/src/libs/i18n/resources/en.json index 9ce5282e7..a72a198ac 100644 --- a/packages/app/src/libs/i18n/resources/en.json +++ b/packages/app/src/libs/i18n/resources/en.json @@ -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" } diff --git a/packages/app/src/libs/i18n/resources/fr.json b/packages/app/src/libs/i18n/resources/fr.json index d30fdcb13..0967ef424 100644 --- a/packages/app/src/libs/i18n/resources/fr.json +++ b/packages/app/src/libs/i18n/resources/fr.json @@ -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" -} +{} diff --git a/packages/app/src/libs/i18n/resources/sr.json b/packages/app/src/libs/i18n/resources/sr.json index edc6c32f2..0967ef424 100644 --- a/packages/app/src/libs/i18n/resources/sr.json +++ b/packages/app/src/libs/i18n/resources/sr.json @@ -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." - } -} +{} diff --git a/packages/app/src/libs/i18n/resources/zh-Hans.json b/packages/app/src/libs/i18n/resources/zh-Hans.json index 14786dcb7..47095233d 100644 --- a/packages/app/src/libs/i18n/resources/zh-Hans.json +++ b/packages/app/src/libs/i18n/resources/zh-Hans.json @@ -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" } diff --git a/packages/app/src/libs/i18n/resources/zh-Hant.json b/packages/app/src/libs/i18n/resources/zh-Hant.json index 4d0f28c9c..0967ef424 100644 --- a/packages/app/src/libs/i18n/resources/zh-Hant.json +++ b/packages/app/src/libs/i18n/resources/zh-Hant.json @@ -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,您可以通過「同步到磁盤」來保存更改" - } -} +{} diff --git a/packages/app/src/pages/_app.tsx b/packages/app/src/pages/_app.tsx index c7cbd78d4..da94a4eed 100644 --- a/packages/app/src/pages/_app.tsx +++ b/packages/app/src/pages/_app.tsx @@ -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) => { , , , , diff --git a/packages/app/src/pages/index.tsx b/packages/app/src/pages/index.tsx index ae21815c9..cf3012d36 100644 --- a/packages/app/src/pages/index.tsx +++ b/packages/app/src/pages/index.tsx @@ -1,7 +1,7 @@ import type { NextPage } from 'next'; const Home: NextPage = () => { - return
    Home Page
    ; + return
    ; }; export default Home; diff --git a/packages/app/src/pages/new-workspace/index.tsx b/packages/app/src/pages/new-workspace/index.tsx new file mode 100644 index 000000000..1a1d9e1a4 --- /dev/null +++ b/packages/app/src/pages/new-workspace/index.tsx @@ -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(false); + + useEffect(() => { + const data = getWorkspaces(); + if (!data.length) { + setOpen(true); + } + }, []); + return ( + +

    workspace

    +
    + +
    + { + setOpen(false); + }} + > +
    + ); +}; +export default Page; + +const Workspace = styled.div(({ theme }) => { + return { + height: '100vh', + background: theme.colors.pageBackground, + color: '#FFFFFF', + fontSize: '18px', + fontWeight: 500, + }; +}); diff --git a/packages/app/src/providers/confirm-provider.tsx b/packages/app/src/providers/ConfirmProvider.tsx similarity index 100% rename from packages/app/src/providers/confirm-provider.tsx rename to packages/app/src/providers/ConfirmProvider.tsx diff --git a/packages/app/src/providers/global-modal-provider.tsx b/packages/app/src/providers/GlobalModalProvider.tsx similarity index 100% rename from packages/app/src/providers/global-modal-provider.tsx rename to packages/app/src/providers/GlobalModalProvider.tsx diff --git a/packages/app/src/providers/themeProvider.tsx b/packages/app/src/providers/ThemeProvider.tsx similarity index 100% rename from packages/app/src/providers/themeProvider.tsx rename to packages/app/src/providers/ThemeProvider.tsx diff --git a/packages/app/src/providers/app-state-provider/DynamicBlocksuite.tsx b/packages/app/src/providers/app-state-provider/DynamicBlocksuite.tsx new file mode 100644 index 000000000..4b7b87654 --- /dev/null +++ b/packages/app/src/providers/app-state-provider/DynamicBlocksuite.tsx @@ -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; diff --git a/packages/app/src/providers/app-state-provider/provider.tsx b/packages/app/src/providers/app-state-provider/Provider.tsx similarity index 67% rename from packages/app/src/providers/app-state-provider/provider.tsx rename to packages/app/src/providers/app-state-provider/Provider.tsx index daa60ff85..f104e9be3 100644 --- a/packages/app/src/providers/app-state-provider/provider.tsx +++ b/packages/app/src/providers/app-state-provider/Provider.tsx @@ -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 = {}; - 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(); - const setLoadWorkspaceHandler = useCallback( - (handler: LoadWorkspaceHandler) => { - _setLoadWorkspaceHandler(() => handler); - }, - [_setLoadWorkspaceHandler] - ); - const [createEditorHandler, _setCreateEditorHandler] = useState(); @@ -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(() => 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 ( - + {children} ); diff --git a/packages/app/src/providers/app-state-provider/context.ts b/packages/app/src/providers/app-state-provider/context.ts index ee6b41d05..f907246ed 100644 --- a/packages/app/src/providers/app-state-provider/context.ts +++ b/packages/app/src/providers/app-state-provider/context.ts @@ -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; + // workspaces: Record; editor: EditorContainer | null; synced: boolean; @@ -40,14 +41,10 @@ export interface AppStateContext extends AppStateValue { export const AppState = createContext({ 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({ synced: false, // eslint-disable-next-line @typescript-eslint/no-empty-function refreshWorkspacesMeta: () => {}, - workspaces: {}, }); export const useAppState = () => { diff --git a/packages/app/src/providers/app-state-provider/dynamic-blocksuite.tsx b/packages/app/src/providers/app-state-provider/dynamic-blocksuite.tsx deleted file mode 100644 index 1875a3555..000000000 --- a/packages/app/src/providers/app-state-provider/dynamic-blocksuite.tsx +++ /dev/null @@ -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; diff --git a/packages/app/src/providers/app-state-provider/hooks.ts b/packages/app/src/providers/app-state-provider/hooks.ts deleted file mode 100644 index 6996bd03e..000000000 --- a/packages/app/src/providers/app-state-provider/hooks.ts +++ /dev/null @@ -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; -}; diff --git a/packages/app/src/providers/temporary-helper-provider.tsx b/packages/app/src/providers/temporary-helper-provider.tsx new file mode 100644 index 000000000..5731646a1 --- /dev/null +++ b/packages/app/src/providers/temporary-helper-provider.tsx @@ -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>; + +export const TemporaryContext = createContext({ + 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) => { + const [workspaceMetaList, setWorkspaceMetaList] = useState([]); + const [currentWorkspace, setCurrentWorkspace] = useState( + null + ); + const [user, setUser] = useState(null); + + useEffect(() => { + setWorkspaceMetaList(getWorkspaces()); + setCurrentWorkspace( + JSON.parse(localStorage.getItem('affine-active-workspace') ?? '{}') + ); + }, []); + + return ( + { + 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} + + ); +}; + +export default TemporaryHelperProvider; diff --git a/packages/app/src/templates/Welcome-to-AFFiNE-Alpha-v2.0.md b/packages/app/src/templates/Welcome-to-AFFiNE-Alpha-v2.0.md index 649513590..130b2918b 100644 --- a/packages/app/src/templates/Welcome-to-AFFiNE-Alpha-v2.0.md +++ b/packages/app/src/templates/Welcome-to-AFFiNE-Alpha-v2.0.md @@ -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 `***` diff --git a/packages/app/src/ui/confirm/Confirm.tsx b/packages/app/src/ui/confirm/Confirm.tsx index bbffe75f6..dee495381 100644 --- a/packages/app/src/ui/confirm/Confirm.tsx +++ b/packages/app/src/ui/confirm/Confirm.tsx @@ -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}