mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-23 20:18:42 +08:00
feat(mobile): ios selfhost server support (#11563)
This commit is contained in:
+2
-2
@@ -41,8 +41,8 @@
|
|||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/swiftlang/swift-cmark",
|
"location" : "https://github.com/swiftlang/swift-cmark",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "3ccff77b2dc5b96b77db3da0d68d28068593fa53",
|
"revision" : "b022b08312decdc46585e0b3440d97f6f22ef703",
|
||||||
"version" : "0.5.0"
|
"version" : "0.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
+2
-2
@@ -41,8 +41,8 @@
|
|||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/swiftlang/swift-cmark",
|
"location" : "https://github.com/swiftlang/swift-cmark",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "3ccff77b2dc5b96b77db3da0d68d28068593fa53",
|
"revision" : "b022b08312decdc46585e0b3440d97f6f22ef703",
|
||||||
"version" : "0.5.0"
|
"version" : "0.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
PODS:
|
PODS:
|
||||||
- Capacitor (7.2.0):
|
- Capacitor (7.2.0):
|
||||||
- CapacitorCordova
|
- CapacitorCordova
|
||||||
- CapacitorApp (7.0.0):
|
- CapacitorApp (7.0.1):
|
||||||
- Capacitor
|
- Capacitor
|
||||||
- CapacitorBrowser (7.0.0):
|
- CapacitorBrowser (7.0.1):
|
||||||
- Capacitor
|
- Capacitor
|
||||||
- CapacitorCordova (7.2.0)
|
- CapacitorCordova (7.2.0)
|
||||||
- CapacitorHaptics (7.0.0):
|
- CapacitorHaptics (7.0.1):
|
||||||
- Capacitor
|
- Capacitor
|
||||||
- CapacitorKeyboard (7.0.0):
|
- CapacitorKeyboard (7.0.1):
|
||||||
- Capacitor
|
- Capacitor
|
||||||
- CryptoSwift (1.8.3)
|
- CryptoSwift (1.8.3)
|
||||||
|
|
||||||
@@ -41,11 +41,11 @@ EXTERNAL SOURCES:
|
|||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
Capacitor: 106e7a4205f4618d582b886a975657c61179138d
|
Capacitor: 106e7a4205f4618d582b886a975657c61179138d
|
||||||
CapacitorApp: 45cb7cbef4aa380b9236fd6980033eb5cde6fcd2
|
CapacitorApp: d63334c052278caf5d81585d80b21905c6f93f39
|
||||||
CapacitorBrowser: 352a66541b15ceadae1d703802b11979023705e3
|
CapacitorBrowser: 081852cf532acf77b9d2953f3a88fe5b9711fb06
|
||||||
CapacitorCordova: 5967b9ba03915ef1d585469d6e31f31dc49be96f
|
CapacitorCordova: 5967b9ba03915ef1d585469d6e31f31dc49be96f
|
||||||
CapacitorHaptics: 1fba3e460e7614349c6d5f868b1fccdc5c87b66d
|
CapacitorHaptics: 70e47470fa1a6bd6338cd102552e3846b7f9a1b3
|
||||||
CapacitorKeyboard: 2c26c6fccde35023c579fc37d4cae6326d5e6343
|
CapacitorKeyboard: 969647d0ca2e5c737d7300088e2517aa832434e2
|
||||||
CryptoSwift: 967f37cea5a3294d9cce358f78861652155be483
|
CryptoSwift: 967f37cea5a3294d9cce358f78861652155be483
|
||||||
|
|
||||||
PODFILE CHECKSUM: bd61c17ff51f31ae55ec8dc579da83fda7bb51cb
|
PODFILE CHECKSUM: bd61c17ff51f31ae55ec8dc579da83fda7bb51cb
|
||||||
|
|||||||
@@ -337,6 +337,7 @@ CapacitorApp.addListener('appUrlOpen', ({ url }) => {
|
|||||||
if (urlObj.hostname === 'authentication') {
|
if (urlObj.hostname === 'authentication') {
|
||||||
const method = urlObj.searchParams.get('method');
|
const method = urlObj.searchParams.get('method');
|
||||||
const payload = JSON.parse(urlObj.searchParams.get('payload') ?? 'false');
|
const payload = JSON.parse(urlObj.searchParams.get('payload') ?? 'false');
|
||||||
|
const serverBaseUrl = urlObj.searchParams.get('server');
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!method ||
|
!method ||
|
||||||
@@ -347,9 +348,18 @@ CapacitorApp.addListener('appUrlOpen', ({ url }) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const authService = frameworkProvider
|
let authService = frameworkProvider
|
||||||
.get(DefaultServerService)
|
.get(DefaultServerService)
|
||||||
.server.scope.get(AuthService);
|
.server.scope.get(AuthService);
|
||||||
|
|
||||||
|
if (serverBaseUrl) {
|
||||||
|
const serversService = frameworkProvider.get(ServersService);
|
||||||
|
const server = serversService.getServerByBaseUrl(serverBaseUrl);
|
||||||
|
if (server) {
|
||||||
|
authService = server.scope.get(AuthService);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (method === 'oauth') {
|
if (method === 'oauth') {
|
||||||
authService
|
authService
|
||||||
.signInOauth(payload.code, payload.state, payload.provider)
|
.signInOauth(payload.code, payload.state, payload.provider)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { SignInPanel } from '@affine/core/components/sign-in';
|
import { SignInPanel, type SignInStep } from '@affine/core/components/sign-in';
|
||||||
import type { AuthSessionStatus } from '@affine/core/modules/cloud/entities/session';
|
import type { AuthSessionStatus } from '@affine/core/modules/cloud/entities/session';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
@@ -7,9 +7,11 @@ import { MobileSignInLayout } from './layout';
|
|||||||
export const MobileSignInPanel = ({
|
export const MobileSignInPanel = ({
|
||||||
onClose,
|
onClose,
|
||||||
server,
|
server,
|
||||||
|
initStep,
|
||||||
}: {
|
}: {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
server?: string;
|
server?: string;
|
||||||
|
initStep?: SignInStep;
|
||||||
}) => {
|
}) => {
|
||||||
const onAuthenticated = useCallback(
|
const onAuthenticated = useCallback(
|
||||||
(status: AuthSessionStatus) => {
|
(status: AuthSessionStatus) => {
|
||||||
@@ -26,6 +28,7 @@ export const MobileSignInPanel = ({
|
|||||||
onSkip={onClose}
|
onSkip={onClose}
|
||||||
onAuthenticated={onAuthenticated}
|
onAuthenticated={onAuthenticated}
|
||||||
server={server}
|
server={server}
|
||||||
|
initStep={initStep}
|
||||||
/>
|
/>
|
||||||
</MobileSignInLayout>
|
</MobileSignInLayout>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export const divider = style({
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
|
flexShrink: 0,
|
||||||
':before': {
|
':before': {
|
||||||
content: '""',
|
content: '""',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@@ -38,6 +39,11 @@ export const head = style([
|
|||||||
color: cssVarV2('text/primary'),
|
color: cssVarV2('text/primary'),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
export const headActions = style({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 14,
|
||||||
|
});
|
||||||
export const body = style({
|
export const body = style({
|
||||||
overflowY: 'auto',
|
overflowY: 'auto',
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
@@ -79,3 +85,30 @@ export const wsName = style([
|
|||||||
textAlign: 'left',
|
textAlign: 'left',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
export const serverInfo = style({
|
||||||
|
padding: '6px 20px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
});
|
||||||
|
export const serverName = style([
|
||||||
|
footnoteRegular,
|
||||||
|
{
|
||||||
|
color: cssVarV2.text.secondary,
|
||||||
|
flexShrink: 0,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
export const serverAccount = style([
|
||||||
|
serverName,
|
||||||
|
{
|
||||||
|
flexShrink: 1,
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
overflow: 'hidden',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
export const spaceX = style({
|
||||||
|
width: 0,
|
||||||
|
flex: 1,
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,26 +1,37 @@
|
|||||||
import { IconButton } from '@affine/component';
|
import { Divider, IconButton, Menu, MenuItem } from '@affine/component';
|
||||||
|
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||||
import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper';
|
import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper';
|
||||||
import { useWorkspaceInfo } from '@affine/core/components/hooks/use-workspace-info';
|
import { useWorkspaceInfo } from '@affine/core/components/hooks/use-workspace-info';
|
||||||
import { WorkspaceAvatar } from '@affine/core/components/workspace-avatar';
|
import { WorkspaceAvatar } from '@affine/core/components/workspace-avatar';
|
||||||
|
import {
|
||||||
|
type AuthAccountInfo,
|
||||||
|
AuthService,
|
||||||
|
type Server,
|
||||||
|
ServersService,
|
||||||
|
} from '@affine/core/modules/cloud';
|
||||||
|
import { GlobalDialogService } from '@affine/core/modules/dialogs';
|
||||||
|
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||||
|
import { GlobalContextService } from '@affine/core/modules/global-context';
|
||||||
import {
|
import {
|
||||||
type WorkspaceMetadata,
|
type WorkspaceMetadata,
|
||||||
WorkspaceService,
|
|
||||||
WorkspacesService,
|
WorkspacesService,
|
||||||
} from '@affine/core/modules/workspace';
|
} from '@affine/core/modules/workspace';
|
||||||
import { CloseIcon, CollaborationIcon } from '@blocksuite/icons/rc';
|
import { useI18n } from '@affine/i18n';
|
||||||
import {
|
import {
|
||||||
useLiveData,
|
AccountIcon,
|
||||||
useService,
|
CloseIcon,
|
||||||
useServiceOptional,
|
CollaborationIcon,
|
||||||
} from '@toeverything/infra';
|
DeleteIcon,
|
||||||
|
MoreHorizontalIcon,
|
||||||
|
SelfhostIcon,
|
||||||
|
SignOutIcon,
|
||||||
|
} from '@blocksuite/icons/rc';
|
||||||
|
import { FrameworkScope, useLiveData, useService } from '@toeverything/infra';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { type HTMLAttributes, useCallback, useMemo } from 'react';
|
import { type HTMLAttributes, useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import * as styles from './menu.css';
|
import * as styles from './menu.css';
|
||||||
|
|
||||||
const filterByFlavour = (workspaces: WorkspaceMetadata[], flavour: string) =>
|
|
||||||
workspaces.filter(ws => flavour === ws.flavour);
|
|
||||||
|
|
||||||
const WorkspaceItem = ({
|
const WorkspaceItem = ({
|
||||||
workspace,
|
workspace,
|
||||||
className,
|
className,
|
||||||
@@ -49,82 +60,306 @@ const WorkspaceItem = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const WorkspaceList = ({
|
interface WorkspaceListProps {
|
||||||
list,
|
items: WorkspaceMetadata[];
|
||||||
title,
|
onClick: (workspace: WorkspaceMetadata) => void;
|
||||||
onClose,
|
onSettingClick?: (workspace: WorkspaceMetadata) => void;
|
||||||
}: {
|
onEnableCloudClick?: (meta: WorkspaceMetadata) => void;
|
||||||
title: string;
|
}
|
||||||
list: WorkspaceMetadata[];
|
export const WorkspaceList = (props: WorkspaceListProps) => {
|
||||||
onClose?: () => void;
|
const workspaceList = props.items;
|
||||||
}) => {
|
|
||||||
const currentWorkspace = useServiceOptional(WorkspaceService)?.workspace;
|
|
||||||
|
|
||||||
const { jumpToPage } = useNavigateHelper();
|
return workspaceList.map(item => (
|
||||||
const toggleWorkspace = useCallback(
|
<WorkspaceItem
|
||||||
(id: string) => {
|
key={item.id}
|
||||||
if (id !== currentWorkspace?.id) {
|
workspace={item}
|
||||||
jumpToPage(id, 'home');
|
onClick={() => props.onClick(item)}
|
||||||
}
|
/>
|
||||||
onClose?.();
|
));
|
||||||
},
|
};
|
||||||
[currentWorkspace?.id, jumpToPage, onClose]
|
|
||||||
|
const WorkspaceServerInfo = ({
|
||||||
|
server,
|
||||||
|
name,
|
||||||
|
account,
|
||||||
|
accountStatus,
|
||||||
|
onDeleteServer,
|
||||||
|
onSignOut,
|
||||||
|
onSignIn,
|
||||||
|
}: {
|
||||||
|
server: string;
|
||||||
|
name: string;
|
||||||
|
account?: AuthAccountInfo | null;
|
||||||
|
accountStatus?: 'authenticated' | 'unauthenticated';
|
||||||
|
onDeleteServer?: () => void;
|
||||||
|
onSignOut?: () => void;
|
||||||
|
onSignIn?: () => void;
|
||||||
|
}) => {
|
||||||
|
const t = useI18n();
|
||||||
|
const isCloud = server !== 'local';
|
||||||
|
|
||||||
|
const menuItems = useMemo(
|
||||||
|
() =>
|
||||||
|
[
|
||||||
|
server !== 'affine-cloud' && server !== 'local' && (
|
||||||
|
<MenuItem
|
||||||
|
prefixIcon={<DeleteIcon />}
|
||||||
|
type="danger"
|
||||||
|
key="delete-server"
|
||||||
|
onClick={onDeleteServer}
|
||||||
|
>
|
||||||
|
{t['com.affine.server.delete']()}
|
||||||
|
</MenuItem>
|
||||||
|
),
|
||||||
|
accountStatus === 'authenticated' && (
|
||||||
|
<MenuItem
|
||||||
|
prefixIcon={<SignOutIcon />}
|
||||||
|
key="sign-out"
|
||||||
|
onClick={onSignOut}
|
||||||
|
type="danger"
|
||||||
|
>
|
||||||
|
{t['Sign out']()}
|
||||||
|
</MenuItem>
|
||||||
|
),
|
||||||
|
accountStatus === 'unauthenticated' && (
|
||||||
|
<MenuItem
|
||||||
|
prefixIcon={<AccountIcon />}
|
||||||
|
key="sign-in"
|
||||||
|
onClick={onSignIn}
|
||||||
|
>
|
||||||
|
{t['Sign in']()}
|
||||||
|
</MenuItem>
|
||||||
|
),
|
||||||
|
].filter(Boolean),
|
||||||
|
[accountStatus, onDeleteServer, onSignIn, onSignOut, server, t]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!list.length) return null;
|
return (
|
||||||
|
<div className={styles.serverInfo}>
|
||||||
|
<div className={styles.serverName}>{name}</div>
|
||||||
|
{isCloud ? (
|
||||||
|
<div className={styles.serverAccount}>
|
||||||
|
- {account ? account.email : 'Not signed in'}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
<div className={styles.spaceX} />
|
||||||
|
{menuItems.length ? (
|
||||||
|
<Menu items={menuItems}>
|
||||||
|
<IconButton icon={<MoreHorizontalIcon />} />
|
||||||
|
</Menu>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const LocalWorkspaces = ({
|
||||||
|
workspaces,
|
||||||
|
onClickWorkspace,
|
||||||
|
onClickWorkspaceSetting,
|
||||||
|
onClickEnableCloud,
|
||||||
|
}: {
|
||||||
|
workspaces: WorkspaceMetadata[];
|
||||||
|
onClickWorkspace: (workspaceMetadata: WorkspaceMetadata) => void;
|
||||||
|
onClickWorkspaceSetting?: (workspaceMetadata: WorkspaceMetadata) => void;
|
||||||
|
onClickEnableCloud?: (meta: WorkspaceMetadata) => void;
|
||||||
|
}) => {
|
||||||
|
const t = useI18n();
|
||||||
|
if (workspaces.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<WorkspaceServerInfo
|
||||||
|
server="local"
|
||||||
|
name={t['com.affine.workspaceList.workspaceListType.local']()}
|
||||||
|
/>
|
||||||
|
<WorkspaceList
|
||||||
|
items={workspaces}
|
||||||
|
onClick={onClickWorkspace}
|
||||||
|
onSettingClick={onClickWorkspaceSetting}
|
||||||
|
onEnableCloudClick={onClickEnableCloud}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const CloudWorkSpaceList = ({
|
||||||
|
server,
|
||||||
|
workspaces,
|
||||||
|
onClickWorkspace,
|
||||||
|
onClickEnableCloud,
|
||||||
|
}: {
|
||||||
|
server: Server;
|
||||||
|
workspaces: WorkspaceMetadata[];
|
||||||
|
onClickWorkspace: (workspaceMetadata: WorkspaceMetadata) => void;
|
||||||
|
onClickEnableCloud?: (meta: WorkspaceMetadata) => void;
|
||||||
|
}) => {
|
||||||
|
const globalContextService = useService(GlobalContextService);
|
||||||
|
const globalDialogService = useService(GlobalDialogService);
|
||||||
|
const serverName = useLiveData(server.config$.selector(c => c.serverName));
|
||||||
|
const authService = useService(AuthService);
|
||||||
|
const serversService = useService(ServersService);
|
||||||
|
const account = useLiveData(authService.session.account$);
|
||||||
|
const accountStatus = useLiveData(authService.session.status$);
|
||||||
|
const navigateHelper = useNavigateHelper();
|
||||||
|
|
||||||
|
const currentWorkspaceFlavour = useLiveData(
|
||||||
|
globalContextService.globalContext.workspaceFlavour.$
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleDeleteServer = useCallback(() => {
|
||||||
|
serversService.removeServer(server.id);
|
||||||
|
|
||||||
|
if (currentWorkspaceFlavour === server.id) {
|
||||||
|
const otherWorkspace = workspaces.find(w => w.flavour !== server.id);
|
||||||
|
if (otherWorkspace) {
|
||||||
|
navigateHelper.openPage(otherWorkspace.id, 'all');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
currentWorkspaceFlavour,
|
||||||
|
navigateHelper,
|
||||||
|
server.id,
|
||||||
|
serversService,
|
||||||
|
workspaces,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const handleSignOut = useAsyncCallback(async () => {
|
||||||
|
await authService.signOut();
|
||||||
|
}, [authService]);
|
||||||
|
|
||||||
|
const handleSignIn = useAsyncCallback(async () => {
|
||||||
|
globalDialogService.open('sign-in', {
|
||||||
|
server: server.baseUrl,
|
||||||
|
});
|
||||||
|
}, [globalDialogService, server.baseUrl]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<section className={styles.wsListTitle}>{title}</section>
|
<WorkspaceServerInfo
|
||||||
<ul className={styles.wsList}>
|
server={server.id}
|
||||||
{list.map(ws => (
|
name={serverName}
|
||||||
<WorkspaceItem
|
account={account}
|
||||||
key={ws.id}
|
accountStatus={accountStatus}
|
||||||
workspace={ws}
|
onDeleteServer={handleDeleteServer}
|
||||||
onClick={() => toggleWorkspace?.(ws.id)}
|
onSignOut={handleSignOut}
|
||||||
/>
|
onSignIn={handleSignIn}
|
||||||
))}
|
/>
|
||||||
</ul>
|
<WorkspaceList
|
||||||
|
items={workspaces}
|
||||||
|
onClick={onClickWorkspace}
|
||||||
|
onEnableCloudClick={onClickEnableCloud}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const AddServer = () => {
|
||||||
|
const globalDialogService = useService(GlobalDialogService);
|
||||||
|
const featureFlagService = useService(FeatureFlagService);
|
||||||
|
const enableMultipleServer = useLiveData(
|
||||||
|
featureFlagService.flags.enable_multiple_cloud_servers.$
|
||||||
|
);
|
||||||
|
|
||||||
|
const onAddServer = useCallback(() => {
|
||||||
|
globalDialogService.open('sign-in', { step: 'addSelfhosted' });
|
||||||
|
}, [globalDialogService]);
|
||||||
|
|
||||||
|
if (!enableMultipleServer) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return <IconButton onClick={onAddServer} size="24" icon={<SelfhostIcon />} />;
|
||||||
|
};
|
||||||
|
|
||||||
export const SelectorMenu = ({ onClose }: { onClose?: () => void }) => {
|
export const SelectorMenu = ({ onClose }: { onClose?: () => void }) => {
|
||||||
const workspacesService = useService(WorkspacesService);
|
const workspacesService = useService(WorkspacesService);
|
||||||
const workspaces = useLiveData(workspacesService.list.workspaces$);
|
const workspaces = useLiveData(workspacesService.list.workspaces$);
|
||||||
|
const serversService = useService(ServersService);
|
||||||
|
const { jumpToPage } = useNavigateHelper();
|
||||||
|
|
||||||
|
const servers = useLiveData(serversService.servers$);
|
||||||
|
const affineCloudServer = useMemo(
|
||||||
|
() => servers.find(s => s.id === 'affine-cloud') as Server,
|
||||||
|
[servers]
|
||||||
|
);
|
||||||
|
const selfhostServers = useMemo(
|
||||||
|
() => servers.filter(s => s.id !== 'affine-cloud'),
|
||||||
|
[servers]
|
||||||
|
);
|
||||||
|
|
||||||
// TODO: support selfhosted
|
|
||||||
const cloudWorkspaces = useMemo(
|
const cloudWorkspaces = useMemo(
|
||||||
() => filterByFlavour(workspaces, 'affine-cloud'),
|
() =>
|
||||||
|
workspaces.filter(
|
||||||
|
({ flavour }) => flavour !== 'local'
|
||||||
|
) as WorkspaceMetadata[],
|
||||||
[workspaces]
|
[workspaces]
|
||||||
);
|
);
|
||||||
|
|
||||||
const localWorkspaces = useMemo(
|
const localWorkspaces = useMemo(
|
||||||
() => filterByFlavour(workspaces, 'local'),
|
() =>
|
||||||
|
workspaces.filter(
|
||||||
|
({ flavour }) => flavour === 'local'
|
||||||
|
) as WorkspaceMetadata[],
|
||||||
[workspaces]
|
[workspaces]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleClickWorkspace = useCallback(
|
||||||
|
(workspaceMetadata: WorkspaceMetadata) => {
|
||||||
|
const id = workspaceMetadata.id;
|
||||||
|
if (id !== currentWorkspace?.id) {
|
||||||
|
jumpToPage(id, 'home');
|
||||||
|
}
|
||||||
|
onClose?.();
|
||||||
|
},
|
||||||
|
[onClose, jumpToPage]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.root}>
|
<div className={styles.root}>
|
||||||
<header className={styles.head}>
|
<header className={styles.head}>
|
||||||
Workspace
|
Workspace
|
||||||
<IconButton onClick={onClose} size="24" icon={<CloseIcon />} />
|
<div className={styles.headActions}>
|
||||||
|
<AddServer />
|
||||||
|
<IconButton onClick={onClose} size="24" icon={<CloseIcon />} />
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div className={styles.divider} />
|
<div className={styles.divider} />
|
||||||
<main className={styles.body}>
|
<main className={styles.body}>
|
||||||
<WorkspaceList
|
{/* 1. affine-cloud */}
|
||||||
onClose={onClose}
|
<FrameworkScope
|
||||||
title="Cloud Sync"
|
key={affineCloudServer.id}
|
||||||
list={cloudWorkspaces}
|
scope={affineCloudServer.scope}
|
||||||
/>
|
>
|
||||||
{cloudWorkspaces.length && localWorkspaces.length ? (
|
<CloudWorkSpaceList
|
||||||
<div className={styles.divider} />
|
server={affineCloudServer}
|
||||||
) : null}
|
workspaces={cloudWorkspaces.filter(
|
||||||
<WorkspaceList
|
({ flavour }) => flavour === affineCloudServer.id
|
||||||
onClose={onClose}
|
)}
|
||||||
title="Local Storage"
|
onClickWorkspace={handleClickWorkspace}
|
||||||
list={localWorkspaces}
|
/>
|
||||||
|
</FrameworkScope>
|
||||||
|
{(localWorkspaces.length > 0 || selfhostServers.length > 0) && (
|
||||||
|
<Divider size="thinner" />
|
||||||
|
)}
|
||||||
|
<LocalWorkspaces
|
||||||
|
workspaces={localWorkspaces}
|
||||||
|
onClickWorkspace={handleClickWorkspace}
|
||||||
/>
|
/>
|
||||||
|
{selfhostServers.length > 0 && <Divider size="thinner" />}
|
||||||
|
|
||||||
|
{/* 3. selfhost */}
|
||||||
|
{selfhostServers.map((server, index) => (
|
||||||
|
<FrameworkScope key={server.id} scope={server.scope}>
|
||||||
|
<CloudWorkSpaceList
|
||||||
|
server={server}
|
||||||
|
workspaces={cloudWorkspaces.filter(
|
||||||
|
({ flavour }) => flavour === server.id
|
||||||
|
)}
|
||||||
|
onClickWorkspace={handleClickWorkspace}
|
||||||
|
/>
|
||||||
|
{index !== selfhostServers.length - 1 && <Divider size="thinner" />}
|
||||||
|
</FrameworkScope>
|
||||||
|
))}
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { IconButton, Modal, SafeArea } from '@affine/component';
|
import { IconButton, Modal, SafeArea } from '@affine/component';
|
||||||
|
import type { SignInStep } from '@affine/core/components/sign-in';
|
||||||
import type {
|
import type {
|
||||||
DialogComponentProps,
|
DialogComponentProps,
|
||||||
GLOBAL_DIALOG_SCHEMA,
|
GLOBAL_DIALOG_SCHEMA,
|
||||||
@@ -11,6 +12,7 @@ import { MobileSignInPanel } from '../../components/sign-in';
|
|||||||
export const SignInDialog = ({
|
export const SignInDialog = ({
|
||||||
close,
|
close,
|
||||||
server: initialServerBaseUrl,
|
server: initialServerBaseUrl,
|
||||||
|
step,
|
||||||
}: DialogComponentProps<GLOBAL_DIALOG_SCHEMA['sign-in']>) => {
|
}: DialogComponentProps<GLOBAL_DIALOG_SCHEMA['sign-in']>) => {
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
@@ -27,7 +29,11 @@ export const SignInDialog = ({
|
|||||||
}}
|
}}
|
||||||
withoutCloseButton
|
withoutCloseButton
|
||||||
>
|
>
|
||||||
<MobileSignInPanel onClose={close} server={initialServerBaseUrl} />
|
<MobileSignInPanel
|
||||||
|
onClose={close}
|
||||||
|
server={initialServerBaseUrl}
|
||||||
|
initStep={step as SignInStep}
|
||||||
|
/>
|
||||||
<SafeArea
|
<SafeArea
|
||||||
top
|
top
|
||||||
style={{ position: 'absolute', top: 0, right: 0, paddingRight: 16 }}
|
style={{ position: 'absolute', top: 0, right: 0, paddingRight: 16 }}
|
||||||
@@ -38,7 +44,10 @@ export const SignInDialog = ({
|
|||||||
variant="solid"
|
variant="solid"
|
||||||
icon={<CloseIcon />}
|
icon={<CloseIcon />}
|
||||||
style={{ borderRadius: 8, padding: 4 }}
|
style={{ borderRadius: 8, padding: 4 }}
|
||||||
onClick={() => close()}
|
onClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
close();
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</SafeArea>
|
</SafeArea>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ export const AFFINE_FLAGS = {
|
|||||||
description:
|
description:
|
||||||
'com.affine.settings.workspace.experimental-features.enable-multiple-cloud-servers.description',
|
'com.affine.settings.workspace.experimental-features.enable-multiple-cloud-servers.description',
|
||||||
configurable: false,
|
configurable: false,
|
||||||
defaultState: isDesktopEnvironment,
|
defaultState: isDesktopEnvironment || BUILD_CONFIG.isIOS,
|
||||||
},
|
},
|
||||||
enable_mobile_edgeless_editing: {
|
enable_mobile_edgeless_editing: {
|
||||||
category: 'affine',
|
category: 'affine',
|
||||||
|
|||||||
Reference in New Issue
Block a user