mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-16 17:46:18 +08:00
Merge branch 'feat/filesystem_and_search' of github.com:toeverything/AFFiNE into feat/filesystem_and_search
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
import Modal from '@/ui/modal';
|
||||
import Input from '@/ui/input';
|
||||
import {
|
||||
StyledModalHeader,
|
||||
StyledTextContent,
|
||||
StyledModalWrapper,
|
||||
StyledInputContent,
|
||||
StyledButtonContent,
|
||||
StyledWorkspaceName,
|
||||
} from './style';
|
||||
import { useState } from 'react';
|
||||
import { ModalCloseButton } from '@/ui/modal';
|
||||
import { Button } from '@/ui/button';
|
||||
import { deleteWorkspace } from '@pathfinder/data-services';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
interface WorkspaceDeleteProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
workspaceName: string;
|
||||
workspaceId: string;
|
||||
nextWorkSpaceId: string;
|
||||
}
|
||||
|
||||
export const WorkspaceDelete = ({
|
||||
open,
|
||||
onClose,
|
||||
workspaceId,
|
||||
workspaceName,
|
||||
nextWorkSpaceId,
|
||||
}: WorkspaceDeleteProps) => {
|
||||
const [deleteStr, setDeleteStr] = useState<string>('');
|
||||
const router = useRouter();
|
||||
|
||||
const handlerInputChange = (workspaceName: string) => {
|
||||
setDeleteStr(workspaceName);
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
await deleteWorkspace({ id: workspaceId });
|
||||
router.push(`/workspace/${nextWorkSpaceId}`);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal open={open} onClose={onClose}>
|
||||
<StyledModalWrapper>
|
||||
<ModalCloseButton onClick={onClose} />
|
||||
<StyledModalHeader>Delete Workspace</StyledModalHeader>
|
||||
<StyledTextContent>
|
||||
This action cannot be undone. This will permanently delete (
|
||||
<StyledWorkspaceName>{workspaceName}</StyledWorkspaceName>) along with
|
||||
all its content.
|
||||
</StyledTextContent>
|
||||
<StyledInputContent>
|
||||
<Input
|
||||
onChange={handlerInputChange}
|
||||
placeholder="Please type “delete” to confirm"
|
||||
value={deleteStr}
|
||||
></Input>
|
||||
</StyledInputContent>
|
||||
<StyledButtonContent>
|
||||
<Button shape="circle" onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
disabled={deleteStr !== 'delete'}
|
||||
onClick={handleDelete}
|
||||
type="danger"
|
||||
shape="circle"
|
||||
style={{ marginLeft: '24px' }}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</StyledButtonContent>
|
||||
</StyledModalWrapper>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default WorkspaceDelete;
|
||||
@@ -0,0 +1 @@
|
||||
export * from './delete';
|
||||
@@ -0,0 +1,76 @@
|
||||
import { styled } from '@/styles';
|
||||
import { Button } from '@/ui/button';
|
||||
|
||||
export const StyledModalWrapper = styled('div')(({ theme }) => {
|
||||
return {
|
||||
position: 'relative',
|
||||
padding: '0px',
|
||||
width: '460px',
|
||||
background: theme.colors.popoverBackground,
|
||||
borderRadius: '12px',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledModalHeader = styled('div')(({ theme }) => {
|
||||
return {
|
||||
margin: '44px 0px 12px 0px',
|
||||
width: '460px',
|
||||
fontWeight: '600',
|
||||
fontSize: '20px;',
|
||||
textAlign: 'center',
|
||||
color: theme.colors.popoverColor,
|
||||
};
|
||||
});
|
||||
|
||||
// export const StyledModalContent = styled('div')(({ theme }) => {});
|
||||
|
||||
export const StyledTextContent = styled('div')(({ theme }) => {
|
||||
return {
|
||||
margin: 'auto',
|
||||
width: '425px',
|
||||
fontFamily: 'Avenir Next',
|
||||
fontStyle: 'normal',
|
||||
fontWeight: '400',
|
||||
fontSize: '18px',
|
||||
lineHeight: '26px',
|
||||
textAlign: 'center',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledInputContent = styled('div')(({ theme }) => {
|
||||
return {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
margin: '40px 0 24px 0',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledButtonContent = styled('div')(({ theme }) => {
|
||||
return {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
margin: '0px 0 32px 0',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledWorkspaceName = styled('span')(({ theme }) => {
|
||||
return {
|
||||
color: '#E8178A',
|
||||
};
|
||||
});
|
||||
|
||||
// export const StyledCancelButton = styled(Button)(({ theme }) => {
|
||||
// return {
|
||||
// width: '100px',
|
||||
// justifyContent: 'center',
|
||||
// };
|
||||
// });
|
||||
|
||||
// export const StyledDeleteButton = styled(Button)(({ theme }) => {
|
||||
// return {
|
||||
// width: '100px',
|
||||
// justifyContent: 'center',
|
||||
// };
|
||||
// });
|
||||
@@ -1,10 +1,10 @@
|
||||
import Modal, { ModalCloseButton } from '@/ui/modal';
|
||||
import {
|
||||
StyledDeleteButtonContainer,
|
||||
StyledSettingAvatar,
|
||||
StyledSettingAvatarContent,
|
||||
StyledSettingInputContainer,
|
||||
} from './style';
|
||||
import { StyledSettingH2 } from '../style';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Button } from '@/ui/button';
|
||||
@@ -12,15 +12,20 @@ import Input from '@/ui/input';
|
||||
import { Workspace, WorkspaceType } from '@pathfinder/data-services';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { WorkspaceDetails } from '@/components/workspace-slider-bar/WorkspaceSelector/SelectorPopperContent';
|
||||
import { StyledSettingH2 } from '../style';
|
||||
import { WorkspaceDelete } from './delete';
|
||||
import { Workspace as StoreWorkspaces } from '@blocksuite/store';
|
||||
|
||||
export const GeneralPage = ({
|
||||
workspace,
|
||||
owner,
|
||||
workspaces,
|
||||
}: {
|
||||
workspace: Workspace;
|
||||
owner: WorkspaceDetails[string]['owner'];
|
||||
workspaces: Record<string, StoreWorkspaces | null>;
|
||||
}) => {
|
||||
const { workspaces, user, currentWorkspace } = useAppState();
|
||||
const { user, currentWorkspace, workspacesMeta } = useAppState();
|
||||
const [showDelete, setShowDelete] = useState<boolean>(false);
|
||||
const [workspaceName, setWorkspaceName] = useState<string>(
|
||||
workspaces[workspace.id]?.meta.name ||
|
||||
(workspace.type === WorkspaceType.Private && user
|
||||
@@ -33,8 +38,28 @@ export const GeneralPage = ({
|
||||
currentWorkspace?.meta.setName(newName);
|
||||
workspaces[workspace.id]?.meta.setName(newName);
|
||||
};
|
||||
const currentWorkspaceIndex = workspacesMeta.findIndex(
|
||||
meta => meta.id === workspace.id
|
||||
);
|
||||
const nextWorkSpaceId =
|
||||
currentWorkspaceIndex > workspacesMeta.length - 1
|
||||
? workspacesMeta[currentWorkspaceIndex - 1]?.id
|
||||
: workspacesMeta[currentWorkspaceIndex + 1]?.id;
|
||||
const handleCloseDelete = () => {
|
||||
setShowDelete(false);
|
||||
};
|
||||
const handleClickDelete = () => {
|
||||
setShowDelete(true);
|
||||
};
|
||||
return workspace ? (
|
||||
<div>
|
||||
<WorkspaceDelete
|
||||
open={showDelete}
|
||||
onClose={handleCloseDelete}
|
||||
workspaceName={workspaceName}
|
||||
workspaceId={workspace.id}
|
||||
nextWorkSpaceId={nextWorkSpaceId}
|
||||
></WorkspaceDelete>
|
||||
<StyledSettingH2 marginTop={56}>Workspace Avatar</StyledSettingH2>
|
||||
<StyledSettingAvatarContent>
|
||||
<StyledSettingAvatar
|
||||
@@ -43,6 +68,7 @@ export const GeneralPage = ({
|
||||
>
|
||||
W
|
||||
</StyledSettingAvatar>
|
||||
{/* TODO: add upload logic */}
|
||||
{/* {isOwner ? (
|
||||
<StyledAvatarUploadBtn shape="round">upload</StyledAvatarUploadBtn>
|
||||
) : null} */}
|
||||
@@ -70,7 +96,7 @@ export const GeneralPage = ({
|
||||
></Input>
|
||||
</StyledSettingInputContainer>
|
||||
<StyledDeleteButtonContainer>
|
||||
<Button type="danger" shape="circle">
|
||||
<Button type="danger" shape="circle" onClick={handleClickDelete}>
|
||||
Delete Workspace
|
||||
</Button>
|
||||
</StyledDeleteButtonContainer>
|
||||
|
||||
@@ -40,17 +40,16 @@ import { InviteMembers } from '../invite-members/index';
|
||||
import {
|
||||
getWorkspaceMembers,
|
||||
Workspace,
|
||||
WorkspaceType,
|
||||
Member,
|
||||
removeMember,
|
||||
} from '@pathfinder/data-services';
|
||||
import { Avatar } from '@mui/material';
|
||||
import { Menu, MenuItem } from '@/ui/menu';
|
||||
import { toast } from '@/ui/toast';
|
||||
import { useConfirm } from '@/providers/confirm-provider';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { WorkspaceDetails } from '../workspace-slider-bar/WorkspaceSelector/SelectorPopperContent';
|
||||
import { GeneralPage } from './general';
|
||||
import { Workspace as StoreWorkspaces } from '@blocksuite/store';
|
||||
|
||||
enum ActiveTab {
|
||||
'general' = 'general',
|
||||
@@ -68,6 +67,7 @@ type WorkspaceSettingProps = {
|
||||
onClose?: () => void;
|
||||
workspace: Workspace;
|
||||
owner: WorkspaceDetails[string]['owner'];
|
||||
workspaces: Record<string, StoreWorkspaces | null>;
|
||||
};
|
||||
|
||||
const WorkspaceSettingTab = ({ activeTab, onTabChange }: SettingTabProps) => {
|
||||
@@ -115,8 +115,9 @@ export const WorkspaceSetting = ({
|
||||
onClose,
|
||||
workspace,
|
||||
owner,
|
||||
workspaces,
|
||||
}: WorkspaceSettingProps) => {
|
||||
const { workspaces, user } = useAppState();
|
||||
const { user } = useAppState();
|
||||
const [activeTab, setActiveTab] = useState<ActiveTab>(ActiveTab.general);
|
||||
const handleTabChange = (tab: ActiveTab) => {
|
||||
setActiveTab(tab);
|
||||
@@ -148,7 +149,11 @@ export const WorkspaceSetting = ({
|
||||
) : null}
|
||||
<StyledSettingContent>
|
||||
{activeTab === ActiveTab.general && (
|
||||
<GeneralPage workspace={workspace} owner={owner} />
|
||||
<GeneralPage
|
||||
workspace={workspace}
|
||||
owner={owner}
|
||||
workspaces={workspaces}
|
||||
/>
|
||||
)}
|
||||
{activeTab === ActiveTab.members && workspace && (
|
||||
<MembersPage workspace={workspace} />
|
||||
|
||||
+39
-2
@@ -12,7 +12,12 @@ import {
|
||||
} from './WorkspaceItem';
|
||||
import { WorkspaceSetting } from '@/components/workspace-setting';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { getWorkspaceDetail, WorkspaceType } from '@pathfinder/data-services';
|
||||
import {
|
||||
downloadWorkspace,
|
||||
getWorkspaceDetail,
|
||||
WorkspaceType,
|
||||
} from '@pathfinder/data-services';
|
||||
import { Workspace } from '@blocksuite/store';
|
||||
|
||||
export type WorkspaceDetails = Record<
|
||||
string,
|
||||
@@ -26,13 +31,16 @@ type SelectorPopperContentProps = {
|
||||
export const SelectorPopperContent = ({
|
||||
isShow,
|
||||
}: SelectorPopperContentProps) => {
|
||||
const { user, workspacesMeta, workspaces } = useAppState();
|
||||
const { user, workspacesMeta } = useAppState();
|
||||
const [settingWorkspaceId, setSettingWorkspaceId] = useState<string | null>(
|
||||
null
|
||||
);
|
||||
const [workSpaceDetails, setWorkSpaceDetails] = useState<WorkspaceDetails>(
|
||||
{}
|
||||
);
|
||||
const [workspaces, setWorkspaces] = useState<
|
||||
Record<string, Workspace | null>
|
||||
>({});
|
||||
const handleClickSettingWorkspace = (workspaceId: string) => {
|
||||
setSettingWorkspaceId(workspaceId);
|
||||
};
|
||||
@@ -73,10 +81,38 @@ export const SelectorPopperContent = ({
|
||||
setWorkSpaceDetails(workSpaceDetails);
|
||||
}, [user, workspacesMeta]);
|
||||
|
||||
// TODO: add to context
|
||||
const refreshWorkspaces = useCallback(async () => {
|
||||
const workspacesList = await Promise.all(
|
||||
workspacesMeta.map(async ({ id }) => {
|
||||
const workspace = new Workspace({
|
||||
room: id,
|
||||
providers: [],
|
||||
});
|
||||
const updates = await downloadWorkspace({ workspaceId: id });
|
||||
updates &&
|
||||
Workspace.Y.applyUpdate(workspace.doc, new Uint8Array(updates));
|
||||
// if after update, the space:meta is empty, then we need to get map with doc
|
||||
workspace.doc.getMap('space:meta');
|
||||
return { id, workspace };
|
||||
})
|
||||
);
|
||||
const workspaces: Record<string, Workspace | null> = {};
|
||||
|
||||
workspacesList.forEach(({ id, workspace }) => {
|
||||
workspaces[id] = workspace;
|
||||
});
|
||||
|
||||
console.log('workspaces', workspaces);
|
||||
|
||||
setWorkspaces(workspaces);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (isShow) {
|
||||
setSettingWorkspaceId(null);
|
||||
refreshDetails();
|
||||
refreshWorkspaces();
|
||||
}
|
||||
}, [isShow, refreshDetails]);
|
||||
|
||||
@@ -128,6 +164,7 @@ export const SelectorPopperContent = ({
|
||||
name: user.name,
|
||||
}
|
||||
}
|
||||
workspaces={workspaces}
|
||||
/>
|
||||
) : null}
|
||||
<StyledDivider />
|
||||
|
||||
@@ -15,7 +15,6 @@ export type CreateEditorHandler = (page: StorePage) => EditorContainer | null;
|
||||
export interface AppStateValue {
|
||||
user: AccessTokenMessage | null;
|
||||
workspacesMeta: Workspace[];
|
||||
workspaces: Record<string, StoreWorkspace | null>;
|
||||
|
||||
currentWorkspaceId: string;
|
||||
currentWorkspace: StoreWorkspace | null;
|
||||
@@ -56,7 +55,7 @@ export const AppState = createContext<AppStateContext>({
|
||||
loadPage: () => Promise.resolve(null),
|
||||
createPage: () => Promise.resolve(null),
|
||||
synced: false,
|
||||
workspaces: {},
|
||||
// workspaces: {},
|
||||
});
|
||||
|
||||
export const useAppState = () => {
|
||||
|
||||
@@ -13,7 +13,7 @@ import type {
|
||||
LoadWorkspaceHandler,
|
||||
CreateEditorHandler,
|
||||
} from './context';
|
||||
import { downloadWorkspace, token } from '@pathfinder/data-services';
|
||||
import { token } from '@pathfinder/data-services';
|
||||
import { WebsocketProvider } from './y-websocket';
|
||||
|
||||
const getEditorParams = (workspaceId: string) => {
|
||||
@@ -90,19 +90,9 @@ const DynamicBlocksuite = ({
|
||||
);
|
||||
if (indexDBProvider) {
|
||||
(indexDBProvider as IndexedDBDocProvider).on('synced', async () => {
|
||||
// const updates = await downloadWorkspace({ workspaceId });
|
||||
// updates &&
|
||||
// Workspace.Y.applyUpdate(workspace.doc, new Uint8Array(updates));
|
||||
// if after update, the space:meta is empty, then we need to get map with doc
|
||||
workspace.doc.getMap('space:meta');
|
||||
resolve(workspace);
|
||||
});
|
||||
} else {
|
||||
const updates = await downloadWorkspace({ workspaceId });
|
||||
updates &&
|
||||
Workspace.Y.applyUpdate(workspace.doc, new Uint8Array(updates));
|
||||
// if after update, the space:meta is empty, then we need to get map with doc
|
||||
workspace.doc.getMap('space:meta');
|
||||
resolve(workspace);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -26,7 +26,6 @@ export const AppStateProvider = ({ children }: { children?: ReactNode }) => {
|
||||
currentWorkspace: null,
|
||||
currentPage: null,
|
||||
editor: null,
|
||||
workspaces: {},
|
||||
// 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,
|
||||
|
||||
@@ -25,6 +25,11 @@ export const Input = (props: inputProps) => {
|
||||
} = props;
|
||||
const [value, setValue] = useState<string>(valueProp || '');
|
||||
const handleChange: InputHTMLAttributes<HTMLInputElement>['onChange'] = e => {
|
||||
if (
|
||||
(maxLength && e.target.value.length > maxLength) ||
|
||||
(minLength && e.target.value.length < minLength)
|
||||
)
|
||||
return;
|
||||
setValue(e.target.value);
|
||||
onChange && onChange(e.target.value);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user