mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-21 16:26:58 +08:00
feat(core): view selfhosted shared docs in electron (#9365)
This commit is contained in:
@@ -4,8 +4,8 @@ import { workbenchRoutes } from '@affine/core/desktop/workbench-router';
|
||||
import {
|
||||
DefaultServerService,
|
||||
ServersService,
|
||||
WorkspaceServerService,
|
||||
} from '@affine/core/modules/cloud';
|
||||
import { GlobalDialogService } from '@affine/core/modules/dialogs';
|
||||
import { DndService } from '@affine/core/modules/dnd/services';
|
||||
import { GlobalContextService } from '@affine/core/modules/global-context';
|
||||
import {
|
||||
@@ -33,7 +33,6 @@ import { AffineErrorBoundary } from '../../../components/affine/affine-error-bou
|
||||
import { WorkbenchRoot } from '../../../modules/workbench';
|
||||
import { AppContainer } from '../../components/app-container';
|
||||
import { PageNotFound } from '../404';
|
||||
import { SignIn } from '../auth/sign-in';
|
||||
import { WorkspaceLayout } from './layouts/workspace-layout';
|
||||
import { SharePage } from './share/share-page';
|
||||
|
||||
@@ -53,8 +52,18 @@ declare global {
|
||||
}
|
||||
|
||||
export const Component = (): ReactElement => {
|
||||
const { workspacesService } = useServices({
|
||||
const {
|
||||
workspacesService,
|
||||
globalDialogService,
|
||||
serversService,
|
||||
defaultServerService,
|
||||
globalContextService,
|
||||
} = useServices({
|
||||
WorkspacesService,
|
||||
GlobalDialogService,
|
||||
ServersService,
|
||||
DefaultServerService,
|
||||
GlobalContextService,
|
||||
});
|
||||
|
||||
const params = useParams();
|
||||
@@ -88,11 +97,6 @@ export const Component = (): ReactElement => {
|
||||
const [workspaceNotFound, setWorkspaceNotFound] = useState(false);
|
||||
const listLoading = useLiveData(workspacesService.list.isRevalidating$);
|
||||
const workspaces = useLiveData(workspacesService.list.workspaces$);
|
||||
const serversService = useService(ServersService);
|
||||
const serverFromSearchParams = searchParams.get('server');
|
||||
const serverNotFound = serverFromSearchParams
|
||||
? serversService.getServerByBaseUrl(serverFromSearchParams)
|
||||
: null;
|
||||
const meta = useMemo(() => {
|
||||
return workspaces.find(({ id }) => id === params.workspaceId);
|
||||
}, [workspaces, params.workspaceId]);
|
||||
@@ -125,36 +129,87 @@ export const Component = (): ReactElement => {
|
||||
return;
|
||||
}, [listLoading, meta, workspaceNotFound, workspacesService]);
|
||||
|
||||
if (workspaceNotFound) {
|
||||
if (BUILD_CONFIG.isElectron && serverNotFound) {
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.delete('server');
|
||||
const redirectUrl = url.toString().replace(window.location.origin, '');
|
||||
return (
|
||||
<AffineOtherPageLayout>
|
||||
<SignIn redirectUrl={redirectUrl} />
|
||||
</AffineOtherPageLayout>
|
||||
);
|
||||
// server search params
|
||||
const serverFromSearchParams = useLiveData(
|
||||
searchParams.has('server')
|
||||
? serversService.serverByBaseUrl$(searchParams.get('server') as string)
|
||||
: undefined
|
||||
);
|
||||
// server from workspace
|
||||
const serverFromWorkspace = useLiveData(
|
||||
meta?.flavour && meta.flavour !== 'local'
|
||||
? serversService.server$(meta?.flavour)
|
||||
: undefined
|
||||
);
|
||||
const server = serverFromWorkspace ?? serverFromSearchParams;
|
||||
|
||||
useEffect(() => {
|
||||
if (server) {
|
||||
globalContextService.globalContext.serverId.set(server.id);
|
||||
return () => {
|
||||
globalContextService.globalContext.serverId.set(
|
||||
defaultServerService.server.id
|
||||
);
|
||||
};
|
||||
}
|
||||
return;
|
||||
}, [
|
||||
defaultServerService.server.id,
|
||||
globalContextService.globalContext.serverId,
|
||||
server,
|
||||
]);
|
||||
|
||||
// if server is not found, and we have server in search params, we should show add selfhosted dialog
|
||||
const needAddSelfhosted = server === undefined && searchParams.has('server');
|
||||
// use ref to avoid useEffect trigger twice
|
||||
const addSelfhostedDialogOpened = useRef<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (addSelfhostedDialogOpened.current) {
|
||||
return;
|
||||
}
|
||||
addSelfhostedDialogOpened.current = true;
|
||||
if (BUILD_CONFIG.isElectron && needAddSelfhosted) {
|
||||
globalDialogService.open('sign-in', {
|
||||
server: searchParams.get('server') as string,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}, [
|
||||
globalDialogService,
|
||||
needAddSelfhosted,
|
||||
searchParams,
|
||||
serverFromSearchParams,
|
||||
]);
|
||||
|
||||
if (workspaceNotFound) {
|
||||
if (detailDocRoute) {
|
||||
return (
|
||||
<SharePage
|
||||
docId={detailDocRoute.docId}
|
||||
workspaceId={detailDocRoute.workspaceId}
|
||||
/>
|
||||
<FrameworkScope scope={server?.scope}>
|
||||
<SharePage
|
||||
docId={detailDocRoute.docId}
|
||||
workspaceId={detailDocRoute.workspaceId}
|
||||
/>
|
||||
</FrameworkScope>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<AffineOtherPageLayout>
|
||||
<PageNotFound noPermission />
|
||||
</AffineOtherPageLayout>
|
||||
<FrameworkScope scope={server?.scope}>
|
||||
<AffineOtherPageLayout>
|
||||
<PageNotFound noPermission />
|
||||
</AffineOtherPageLayout>
|
||||
</FrameworkScope>
|
||||
);
|
||||
}
|
||||
if (!meta) {
|
||||
return <AppContainer fallback />;
|
||||
}
|
||||
|
||||
return <WorkspacePage meta={meta} />;
|
||||
return (
|
||||
<FrameworkScope scope={server?.scope}>
|
||||
<WorkspacePage meta={meta} />
|
||||
</FrameworkScope>
|
||||
);
|
||||
};
|
||||
|
||||
const DNDContextProvider = ({ children }: PropsWithChildren) => {
|
||||
@@ -171,15 +226,12 @@ const DNDContextProvider = ({ children }: PropsWithChildren) => {
|
||||
};
|
||||
|
||||
const WorkspacePage = ({ meta }: { meta: WorkspaceMetadata }) => {
|
||||
const { workspacesService, globalContextService, defaultServerService } =
|
||||
useServices({
|
||||
WorkspacesService,
|
||||
GlobalContextService,
|
||||
DefaultServerService,
|
||||
});
|
||||
const { workspacesService, globalContextService } = useServices({
|
||||
WorkspacesService,
|
||||
GlobalContextService,
|
||||
});
|
||||
|
||||
const [workspace, setWorkspace] = useState<Workspace | null>(null);
|
||||
const workspaceServer = workspace?.scope.get(WorkspaceServerService).server;
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const ref = workspacesService.open({ metadata: meta });
|
||||
@@ -238,30 +290,17 @@ const WorkspacePage = ({ meta }: { meta: WorkspaceMetadata }) => {
|
||||
};
|
||||
localStorage.setItem('last_workspace_id', workspace.id);
|
||||
globalContextService.globalContext.workspaceId.set(workspace.id);
|
||||
if (workspaceServer) {
|
||||
globalContextService.globalContext.serverId.set(workspaceServer.id);
|
||||
}
|
||||
globalContextService.globalContext.workspaceFlavour.set(
|
||||
workspace.flavour
|
||||
);
|
||||
return () => {
|
||||
window.currentWorkspace = undefined;
|
||||
globalContextService.globalContext.workspaceId.set(null);
|
||||
if (workspaceServer) {
|
||||
globalContextService.globalContext.serverId.set(
|
||||
defaultServerService.server.id
|
||||
);
|
||||
}
|
||||
globalContextService.globalContext.workspaceFlavour.set(null);
|
||||
};
|
||||
}
|
||||
return;
|
||||
}, [
|
||||
defaultServerService.server.id,
|
||||
globalContextService,
|
||||
workspace,
|
||||
workspaceServer,
|
||||
]);
|
||||
}, [globalContextService, workspace]);
|
||||
|
||||
if (!workspace) {
|
||||
return null; // skip this, workspace will be set in layout effect
|
||||
@@ -269,27 +308,23 @@ const WorkspacePage = ({ meta }: { meta: WorkspaceMetadata }) => {
|
||||
|
||||
if (!isRootDocReady) {
|
||||
return (
|
||||
<FrameworkScope scope={workspaceServer?.scope}>
|
||||
<FrameworkScope scope={workspace.scope}>
|
||||
<DNDContextProvider>
|
||||
<AppContainer fallback />
|
||||
</DNDContextProvider>
|
||||
</FrameworkScope>
|
||||
<FrameworkScope scope={workspace.scope}>
|
||||
<DNDContextProvider>
|
||||
<AppContainer fallback />
|
||||
</DNDContextProvider>
|
||||
</FrameworkScope>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<FrameworkScope scope={workspaceServer?.scope}>
|
||||
<FrameworkScope scope={workspace.scope}>
|
||||
<DNDContextProvider>
|
||||
<AffineErrorBoundary height="100vh">
|
||||
<WorkspaceLayout>
|
||||
<WorkbenchRoot />
|
||||
</WorkspaceLayout>
|
||||
</AffineErrorBoundary>
|
||||
</DNDContextProvider>
|
||||
</FrameworkScope>
|
||||
<FrameworkScope scope={workspace.scope}>
|
||||
<DNDContextProvider>
|
||||
<AffineErrorBoundary height="100vh">
|
||||
<WorkspaceLayout>
|
||||
<WorkbenchRoot />
|
||||
</WorkspaceLayout>
|
||||
</AffineErrorBoundary>
|
||||
</DNDContextProvider>
|
||||
</FrameworkScope>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
AuthService,
|
||||
FetchService,
|
||||
GraphQLService,
|
||||
ServerService,
|
||||
} from '@affine/core/modules/cloud';
|
||||
import { type Doc, DocsService } from '@affine/core/modules/doc';
|
||||
import {
|
||||
@@ -64,8 +65,9 @@ export const SharePage = ({
|
||||
workspaceId: string;
|
||||
docId: string;
|
||||
}) => {
|
||||
const { shareReaderService } = useServices({
|
||||
const { shareReaderService, serverService } = useServices({
|
||||
ShareReaderService,
|
||||
ServerService,
|
||||
});
|
||||
|
||||
const isLoading = useLiveData(shareReaderService.reader.isLoading$);
|
||||
@@ -104,8 +106,12 @@ export const SharePage = ({
|
||||
}, [location.search]);
|
||||
|
||||
useEffect(() => {
|
||||
shareReaderService.reader.loadShare({ workspaceId, docId });
|
||||
}, [shareReaderService, docId, workspaceId]);
|
||||
shareReaderService.reader.loadShare({
|
||||
serverId: serverService.server.id,
|
||||
workspaceId,
|
||||
docId,
|
||||
});
|
||||
}, [shareReaderService, docId, workspaceId, serverService.server.id]);
|
||||
|
||||
let element: ReactNode = null;
|
||||
if (isLoading) {
|
||||
|
||||
@@ -55,6 +55,12 @@ export class ServersService extends Service {
|
||||
);
|
||||
}
|
||||
|
||||
serverByBaseUrl$(url: string) {
|
||||
return this.servers$.map(servers =>
|
||||
servers.find(server => server.baseUrl === url)
|
||||
);
|
||||
}
|
||||
|
||||
private readonly serverPool = new ObjectPool<string, Server>({
|
||||
onDelete(obj) {
|
||||
obj.dispose();
|
||||
|
||||
@@ -31,8 +31,18 @@ export class ShareReader extends Entity {
|
||||
|
||||
loadShare = effect(
|
||||
switchMap(
|
||||
({ workspaceId, docId }: { workspaceId: string; docId: string }) => {
|
||||
return fromPromise(this.store.loadShare(workspaceId, docId)).pipe(
|
||||
({
|
||||
serverId,
|
||||
workspaceId,
|
||||
docId,
|
||||
}: {
|
||||
serverId: string;
|
||||
workspaceId: string;
|
||||
docId: string;
|
||||
}) => {
|
||||
return fromPromise(
|
||||
this.store.loadShare(serverId, workspaceId, docId)
|
||||
).pipe(
|
||||
mergeMap(data => {
|
||||
if (!data) {
|
||||
this.data$.next(null);
|
||||
|
||||
@@ -5,7 +5,7 @@ export { ShareReaderService } from './services/share-reader';
|
||||
|
||||
import { type Framework } from '@toeverything/infra';
|
||||
|
||||
import { RawFetchProvider, WorkspaceServerService } from '../cloud';
|
||||
import { ServersService, WorkspaceServerService } from '../cloud';
|
||||
import { DocScope, DocService } from '../doc';
|
||||
import {
|
||||
WorkspaceLocalCache,
|
||||
@@ -26,7 +26,7 @@ export function configureShareDocsModule(framework: Framework) {
|
||||
framework
|
||||
.service(ShareReaderService)
|
||||
.entity(ShareReader, [ShareReaderStore])
|
||||
.store(ShareReaderStore, [RawFetchProvider])
|
||||
.store(ShareReaderStore, [ServersService])
|
||||
.scope(WorkspaceScope)
|
||||
.service(ShareDocsListService, [WorkspaceService])
|
||||
.store(ShareDocsStore, [WorkspaceServerService])
|
||||
|
||||
@@ -2,36 +2,31 @@ import { ErrorNames, UserFriendlyError } from '@affine/graphql';
|
||||
import type { DocMode } from '@blocksuite/affine/blocks';
|
||||
import { Store } from '@toeverything/infra';
|
||||
|
||||
import type { RawFetchProvider } from '../../cloud';
|
||||
import type { ServersService } from '../../cloud';
|
||||
import { isBackendError } from '../../cloud';
|
||||
|
||||
export class ShareReaderStore extends Store {
|
||||
constructor(private readonly rawFetch?: RawFetchProvider) {
|
||||
constructor(private readonly serversService: ServersService) {
|
||||
super();
|
||||
}
|
||||
|
||||
async loadShare(workspaceId: string, docId: string) {
|
||||
if (!this.rawFetch) {
|
||||
throw new Error('No Fetch Service');
|
||||
async loadShare(serverId: string, workspaceId: string, docId: string) {
|
||||
const server = this.serversService.server$(serverId).value;
|
||||
if (!server) {
|
||||
throw new Error(`Server ${serverId} not found`);
|
||||
}
|
||||
try {
|
||||
const docResponse = await this.rawFetch.fetch(
|
||||
const docResponse = await server.fetch(
|
||||
`/api/workspaces/${workspaceId}/docs/${docId}`
|
||||
);
|
||||
if (docResponse.status !== 200) {
|
||||
throw new Error('Failed to fetch workspace');
|
||||
}
|
||||
const publishMode = docResponse.headers.get(
|
||||
'publish-mode'
|
||||
) as DocMode | null;
|
||||
const docBinary = await docResponse.arrayBuffer();
|
||||
|
||||
const workspaceResponse = await this.rawFetch.fetch(
|
||||
const workspaceResponse = await server.fetch(
|
||||
`/api/workspaces/${workspaceId}/docs/${workspaceId}`
|
||||
);
|
||||
if (workspaceResponse.status !== 200) {
|
||||
throw new Error('Failed to fetch workspace');
|
||||
}
|
||||
const workspaceBinary = await workspaceResponse.arrayBuffer();
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user