Skip to content

Commit

Permalink
Update modal-provider.tsx
Browse files Browse the repository at this point in the history
  • Loading branch information
pengx17 committed May 31, 2024
1 parent bae6fd8 commit 0c742eb
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 190 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { useDocMetaHelper } from '@affine/core/hooks/use-block-suite-page-meta';
import { useJournalHelper } from '@affine/core/hooks/use-journal';
import { PeekViewService } from '@affine/core/modules/peek-view';
import { WorkbenchLink } from '@affine/core/modules/workbench';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { LinkedPageIcon, TodayIcon } from '@blocksuite/icons';
import type { DocCollection } from '@blocksuite/store';
import type { PropsWithChildren } from 'react';
import { useService } from '@toeverything/infra';
import { type PropsWithChildren, useCallback, useRef } from 'react';

import * as styles from './styles.css';

Expand Down Expand Up @@ -64,8 +66,30 @@ export function AffinePageReference({
t,
});

const ref = useRef<HTMLAnchorElement>(null);

const peekView = useService(PeekViewService).peekView;

const onClick = useCallback(
(e: React.MouseEvent) => {
if (e.shiftKey && ref.current) {
e.preventDefault();
e.stopPropagation();
peekView.open(ref.current);
return true; // means this click is handled
}
return false;
},
[peekView]
);

return (
<WorkbenchLink to={`/${pageId}`} className={styles.pageReferenceLink}>
<WorkbenchLink
ref={ref}
to={`/${pageId}`}
onClick={onClick}
className={styles.pageReferenceLink}
>
{Wrapper ? <Wrapper>{el}</Wrapper> : el}
</WorkbenchLink>
);
Expand Down
122 changes: 113 additions & 9 deletions packages/frontend/core/src/modules/peek-view/entities/peek-view.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,116 @@
import type { BlockElement } from '@blocksuite/block-std';
import type { AffineReference } from '@blocksuite/blocks';
import { Entity, LiveData } from '@toeverything/infra';
import {
AffineReference,
type EmbedLinkedDocModel,
type EmbedSyncedDocModel,
type SurfaceRefBlockComponent,
type SurfaceRefBlockModel,
} from '@blocksuite/blocks';
import { type DocMode, Entity, LiveData } from '@toeverything/infra';

export type PeekViewTarget =
| HTMLElement
| BlockElement
| AffineReference
| HTMLAnchorElement
| { docId: string; blockId?: string };

export type DocPeekViewInfo = {
docId: string;
blockId?: string;
mode?: DocMode;
xywh?: `[${number},${number},${number},${number}]`;
};

export type ActivePeekView = {
target:
| HTMLElement
| BlockElement
| AffineReference
| HTMLAnchorElement
| { docId: string; blockId?: string };
target: PeekViewTarget;
info: DocPeekViewInfo;
};

import type { BlockModel } from '@blocksuite/store';

const EMBED_DOC_FLAVOURS = [
'affine:embed-linked-doc',
'affine:embed-synced-doc',
];

const isEmbedDocModel = (
blockModel: BlockModel
): blockModel is EmbedSyncedDocModel | EmbedLinkedDocModel => {
return EMBED_DOC_FLAVOURS.includes(blockModel.flavour);
};

const isSurfaceRefModel = (
blockModel: BlockModel
): blockModel is SurfaceRefBlockModel => {
return blockModel.flavour === 'affine:surface-ref';
};

const resolveLinkToDoc = (href: string) => {
// http://xxx/workspace/48__RTCSwASvWZxyAk3Jw/-Uge-K6SYcAbcNYfQ5U-j#xxxx
// to { workspaceId: '48__RTCSwASvWZxyAk3Jw', docId: '-Uge-K6SYcAbcNYfQ5U-j', blockId: 'xxxx' }

const [_, workspaceId, docId, blockId] =
href.match(/\/workspace\/([^/]+)\/([^#]+)(?:#(.+))?/) || [];

/**
* @see /packages/frontend/core/src/router.tsx
*/
const excludedPaths = ['all', 'collection', 'tag', 'trash'];

if (!docId || excludedPaths.includes(docId)) {
return null;
}

return { workspaceId, docId, blockId };
};

function resolvePeekInfoFromPeekTarget(
peekTarget?: PeekViewTarget
): DocPeekViewInfo | null {
if (!peekTarget) return null;
if (peekTarget instanceof AffineReference) {
if (peekTarget.refMeta) {
return {
docId: peekTarget.refMeta.id,
};
}
} else if ('model' in peekTarget) {
const blockModel = peekTarget.model;
if (isEmbedDocModel(blockModel)) {
return {
docId: blockModel.pageId,
};
} else if (isSurfaceRefModel(blockModel)) {
const refModel = (peekTarget as SurfaceRefBlockComponent).referenceModel;
// refModel can be null if the reference is invalid
if (refModel) {
const docId =
'doc' in refModel ? refModel.doc.id : refModel.surface.doc.id;
return {
docId,
mode: 'edgeless',
xywh: refModel.xywh,
};
}
}
} else if (peekTarget instanceof HTMLAnchorElement) {
const maybeDoc = resolveLinkToDoc(peekTarget.href);
if (maybeDoc) {
return {
docId: maybeDoc.docId,
blockId: maybeDoc.blockId,
};
}
} else if ('docId' in peekTarget) {
return {
docId: peekTarget.docId,
blockId: peekTarget.blockId,
};
}
return null;
}

export class PeekViewEntity extends Entity {
constructor() {
super();
Expand All @@ -25,7 +125,11 @@ export class PeekViewEntity extends Entity {
.distinctUntilChanged();

open = (target: ActivePeekView['target']) => {
this._active$.next({ target });
const resolvedInfo = resolvePeekInfoFromPeekTarget(target);
if (!resolvedInfo) {
return;
}
this._active$.next({ target, info: resolvedInfo });
this._show$.next(true);
};

Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/core/src/modules/peek-view/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ export function configurePeekViewModule(framework: Framework) {
}

export { PeekViewEntity, PeekViewService };
export { useInsidePeekView } from './view';
export { PeekViewManagerModal,useInsidePeekView } from './view';
131 changes: 28 additions & 103 deletions packages/frontend/core/src/modules/peek-view/view/peek-view-manager.tsx
Original file line number Diff line number Diff line change
@@ -1,132 +1,57 @@
import {
AffineReference,
type SurfaceRefBlockComponent,
} from '@blocksuite/blocks';
import { type DocMode, useLiveData, useService } from '@toeverything/infra';
import { useLiveData, useService } from '@toeverything/infra';
import { useMemo } from 'react';

import type { ActivePeekView } from '../entities/peek-view';
import { PeekViewService } from '../services/peek-view';
import { DocPeekViewControls } from './doc-peek-controls';
import { DocPeekView, SurfaceRefPeekView } from './doc-peek-view';
import { PeekViewModalContainer } from './modal-container';
import { isEmbedDocModel, isSurfaceRefModel, resolveLinkToDoc } from './utils';

type DocPeekViewInfo = {
docId: string;
blockId?: string;
mode?: DocMode;
xywh?: `[${number},${number},${number},${number}]`;
};

function resolveDocIdFromPeekTarget(
peekTarget?: ActivePeekView['target']
): DocPeekViewInfo | null {
if (!peekTarget) return null;
if (peekTarget instanceof AffineReference) {
if (peekTarget.refMeta) {
return {
docId: peekTarget.refMeta.id,
};
}
} else if ('model' in peekTarget) {
const blockModel = peekTarget.model;
if (isEmbedDocModel(blockModel)) {
return {
docId: blockModel.pageId,
};
} else if (isSurfaceRefModel(blockModel)) {
const refModel = (peekTarget as SurfaceRefBlockComponent).referenceModel;
// refModel can be null if the reference is invalid
if (refModel) {
const docId =
'doc' in refModel ? refModel.doc.id : refModel.surface.doc.id;
return {
docId,
mode: 'edgeless',
xywh: refModel.xywh,
};
}
}
} else if (peekTarget instanceof HTMLAnchorElement) {
const maybeDoc = resolveLinkToDoc(peekTarget.href);
if (maybeDoc) {
return {
docId: maybeDoc.docId,
blockId: maybeDoc.blockId,
};
}
} else if ('docId' in peekTarget) {
return {
docId: peekTarget.docId,
blockId: peekTarget.blockId,
};
}
return null;
}

function renderPeekView(peekTarget?: ActivePeekView['target']) {
const docPeekViewInfo = resolveDocIdFromPeekTarget(peekTarget);

if (docPeekViewInfo) {
if (docPeekViewInfo.mode === 'edgeless' && docPeekViewInfo.xywh) {
return (
<SurfaceRefPeekView
docId={docPeekViewInfo.docId}
xywh={docPeekViewInfo.xywh}
/>
);
}

return (
<DocPeekView
mode={docPeekViewInfo.mode}
docId={docPeekViewInfo.docId}
blockId={docPeekViewInfo.blockId}
/>
);
function renderPeekView({ info }: ActivePeekView) {
if (info.mode === 'edgeless' && info.xywh) {
return <SurfaceRefPeekView docId={info.docId} xywh={info.xywh} />;
}

return null;
return (
<DocPeekView mode={info.mode} docId={info.docId} blockId={info.blockId} />
);
}

const renderControls = (peekTarget?: ActivePeekView['target']) => {
const docPeekViewInfo = resolveDocIdFromPeekTarget(peekTarget);

if (docPeekViewInfo) {
return (
<DocPeekViewControls
mode={docPeekViewInfo.mode}
docId={docPeekViewInfo.docId}
blockId={docPeekViewInfo.docId}
/>
);
}

return null;
const renderControls = ({ info }: ActivePeekView) => {
return (
<DocPeekViewControls
mode={info.mode}
docId={info.docId}
blockId={info.docId}
/>
);
};

export const PeekViewManagerModal = () => {
const peekView = useService(PeekViewService).peekView;
const peekTarget = useLiveData(peekView.active$)?.target;
const show = useLiveData(peekView.show$);
const peekViewEntity = useService(PeekViewService).peekView;
const activePeekView = useLiveData(peekViewEntity.active$);
const show = useLiveData(peekViewEntity.show$);

const preview = useMemo(() => {
return renderPeekView(peekTarget);
}, [peekTarget]);
return activePeekView ? renderPeekView(activePeekView) : null;
}, [activePeekView]);

const controls = useMemo(() => {
return renderControls(peekTarget);
}, [peekTarget]);
return activePeekView ? renderControls(activePeekView) : null;
}, [activePeekView]);

return (
<PeekViewModalContainer
open={show}
target={peekTarget instanceof HTMLElement ? peekTarget : undefined}
target={
activePeekView?.target instanceof HTMLElement
? activePeekView.target
: undefined
}
controls={controls}
onOpenChange={open => {
if (!open) {
peekView.close();
peekViewEntity.close();
}
}}
>
Expand Down
Loading

0 comments on commit 0c742eb

Please sign in to comment.