mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-20 19:46:32 +08:00
feat: render placeholder in edgeless mode (#11387)
Complete [BS-2997](https://linear.app/affine-design/issue/BS-2997/在白板上渲染-inserted-frame-group-时提供占位)
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
import { html } from 'lit';
|
||||
|
||||
export const SurfaceRefNotFoundBackground = html`
|
||||
<svg
|
||||
width="204"
|
||||
height="66"
|
||||
viewBox="0 0 204 66"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g filter="url(#filter0_d_877_26)">
|
||||
<rect width="53" height="66" transform="translate(49 22)" fill="white" />
|
||||
<rect
|
||||
x="57.0168"
|
||||
y="30.8"
|
||||
width="26.0545"
|
||||
height="3.85"
|
||||
rx="1.925"
|
||||
fill="black"
|
||||
fill-opacity="0.07"
|
||||
/>
|
||||
<rect
|
||||
x="57.0168"
|
||||
y="38.5"
|
||||
width="36.9664"
|
||||
height="2.2"
|
||||
rx="1.1"
|
||||
fill="black"
|
||||
fill-opacity="0.07"
|
||||
/>
|
||||
<rect
|
||||
x="57.0168"
|
||||
y="44"
|
||||
width="19.5409"
|
||||
height="2.2"
|
||||
rx="1.1"
|
||||
fill="black"
|
||||
fill-opacity="0.07"
|
||||
/>
|
||||
<rect
|
||||
x="57.0168"
|
||||
y="49.5"
|
||||
width="36.9664"
|
||||
height="2.2"
|
||||
rx="1.1"
|
||||
fill="black"
|
||||
fill-opacity="0.07"
|
||||
/>
|
||||
<rect
|
||||
x="57.0168"
|
||||
y="55"
|
||||
width="19.5409"
|
||||
height="2.2"
|
||||
rx="1.1"
|
||||
fill="black"
|
||||
fill-opacity="0.07"
|
||||
/>
|
||||
</g>
|
||||
<path
|
||||
d="M157.341 17.6783L144.153 14.356L144.708 12.2671C145.628 8.80598 143.153 5.18897 139.18 4.18815L129.589 1.77198C125.616 0.771163 121.65 2.76557 120.73 6.22669L120.175 8.31563L106.987 4.99341C103.676 4.15942 100.371 5.82148 99.6048 8.70566L98.4947 12.8835C98.1881 14.0373 99.0131 15.2429 100.337 15.5765L157.885 30.0735C159.209 30.4071 160.531 29.7424 160.837 28.5886L161.948 24.4107C162.714 21.5266 160.651 18.5123 157.341 17.6783ZM125.526 7.43477C125.831 6.28324 127.157 5.61689 128.478 5.94987L138.07 8.36603C139.391 8.69901 140.218 9.90749 139.912 11.059L139.357 13.148L124.97 9.52372L125.526 7.43477Z"
|
||||
fill="#E6E6E6"
|
||||
/>
|
||||
<path
|
||||
d="M98.6798 34.2638C98.2253 34.2631 97.8625 34.6108 97.8834 35.0271L99.9152 75.4671C100.103 79.2097 103.451 82.1461 107.536 82.1527L146.222 82.216C150.307 82.2226 153.665 79.2973 153.866 75.5552L156.037 35.1221C156.06 34.7059 155.698 34.357 155.243 34.3563L98.6798 34.2638ZM137.141 40.1651C137.143 38.8748 138.285 37.8316 139.693 37.8339C141.1 37.8362 142.238 38.8831 142.236 40.1734L142.183 70.5327C142.181 71.8229 141.039 72.8661 139.632 72.8638C138.225 72.8615 137.086 71.8146 137.089 70.5244L137.141 40.1651ZM124.404 40.1442C124.406 38.854 125.548 37.8108 126.956 37.8131C128.363 37.8154 129.501 38.8623 129.499 40.1526L129.447 70.5119C129.444 71.8021 128.303 72.8453 126.895 72.843C125.488 72.8407 124.349 71.7938 124.352 70.5035L124.404 40.1442ZM111.667 40.1234C111.669 38.8331 112.811 37.7899 114.219 37.7922C115.626 37.7945 116.764 38.8415 116.762 40.1317L116.71 70.491C116.707 71.7813 115.566 72.8245 114.158 72.8222C112.751 72.8199 111.613 71.773 111.615 70.4827L111.667 40.1234Z"
|
||||
fill="#E6E6E6"
|
||||
/>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_d_877_26"
|
||||
x="46"
|
||||
y="19"
|
||||
width="59"
|
||||
height="72"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset />
|
||||
<feGaussianBlur stdDeviation="1.5" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0.258824 0 0 0 0 0.254902 0 0 0 0 0.286275 0 0 0 0.1 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow_877_26"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_877_26"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
`;
|
||||
@@ -1,10 +1,182 @@
|
||||
import type { SurfaceRefBlockModel } from '@blocksuite/affine-model';
|
||||
import { isFrameBlock } from '@blocksuite/affine-block-frame';
|
||||
import {
|
||||
GroupElementModel,
|
||||
type SurfaceRefBlockModel,
|
||||
} from '@blocksuite/affine-model';
|
||||
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
||||
import {
|
||||
DeleteIcon,
|
||||
EdgelessIcon,
|
||||
FrameIcon,
|
||||
GroupIcon,
|
||||
MindmapIcon,
|
||||
} from '@blocksuite/icons/lit';
|
||||
import { BlockComponent } from '@blocksuite/std';
|
||||
import { nothing } from 'lit';
|
||||
import {
|
||||
GfxControllerIdentifier,
|
||||
type GfxModel,
|
||||
isPrimitiveModel,
|
||||
} from '@blocksuite/std/gfx';
|
||||
import { css, html, nothing } from 'lit';
|
||||
import { state } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
|
||||
import { SurfaceRefNotFoundBackground } from './icons';
|
||||
|
||||
const TYPE_ICON_MAP: {
|
||||
[key: string]: {
|
||||
name: string;
|
||||
icon: typeof DeleteIcon;
|
||||
};
|
||||
} = {
|
||||
'affine:frame': {
|
||||
name: 'Frame',
|
||||
icon: FrameIcon,
|
||||
},
|
||||
group: {
|
||||
name: 'Group',
|
||||
icon: GroupIcon,
|
||||
},
|
||||
mindmap: {
|
||||
name: 'Mind map',
|
||||
icon: MindmapIcon,
|
||||
},
|
||||
edgeless: {
|
||||
name: 'Edgeless content',
|
||||
icon: EdgelessIcon,
|
||||
},
|
||||
};
|
||||
|
||||
export class EdgelessSurfaceRefBlockComponent extends BlockComponent<SurfaceRefBlockModel> {
|
||||
override render() {
|
||||
return nothing;
|
||||
static override styles = css`
|
||||
affine-edgeless-surface-ref {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.affine-edgeless-surface-ref-container {
|
||||
border-radius: 8px;
|
||||
border: 1px solid
|
||||
${unsafeCSSVarV2('layer/insideBorder/border', '#e6e6e6')};
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.affine-edgeless-surface-ref-container.not-found {
|
||||
background: ${unsafeCSSVarV2('layer/background/secondary', '#F5F5F5')};
|
||||
}
|
||||
|
||||
.affine-edgeless-surface-ref-container .not-found-background {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
bottom: -5px;
|
||||
}
|
||||
|
||||
.edgeless-surface-ref-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.edgeless-surface-ref-content > .surface-ref-heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
align-self: stretch;
|
||||
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 22px;
|
||||
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
|
||||
color: ${unsafeCSSVarV2('text/primary', '#141414')};
|
||||
}
|
||||
|
||||
.edgeless-surface-ref-content > .surface-ref-heading svg {
|
||||
color: ${unsafeCSSVarV2('text/primary', '#141414')};
|
||||
}
|
||||
|
||||
.edgeless-surface-ref-content > .surface-ref-body {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
color: ${unsafeCSSVarV2('text/disable', '#7a7a7a')};
|
||||
}
|
||||
`;
|
||||
|
||||
override connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
|
||||
const elementModel = this.gfx.getElementById(
|
||||
this.model.props.reference
|
||||
) as GfxModel;
|
||||
|
||||
this._referenceModel = elementModel;
|
||||
}
|
||||
|
||||
get gfx() {
|
||||
return this.std.get(GfxControllerIdentifier);
|
||||
}
|
||||
|
||||
@state()
|
||||
accessor _referenceModel: GfxModel | null = null;
|
||||
|
||||
private _renderRefContent(referenceModel: GfxModel | null) {
|
||||
const modelNotFound = !referenceModel;
|
||||
const flavourOrType = modelNotFound
|
||||
? (this.model.props.refFlavour ?? 'edgeless')
|
||||
: isPrimitiveModel(referenceModel)
|
||||
? referenceModel.type
|
||||
: referenceModel.flavour;
|
||||
const matchedType =
|
||||
TYPE_ICON_MAP[flavourOrType] ?? TYPE_ICON_MAP['edgeless'];
|
||||
|
||||
const title = modelNotFound
|
||||
? matchedType.name
|
||||
: isFrameBlock(referenceModel)
|
||||
? referenceModel.props.title.toString()
|
||||
: referenceModel instanceof GroupElementModel
|
||||
? referenceModel.title.toString()
|
||||
: matchedType.name;
|
||||
|
||||
return html`
|
||||
<div class="edgeless-surface-ref-content">
|
||||
<div class="surface-ref-heading">
|
||||
${modelNotFound
|
||||
? DeleteIcon({ width: '16px', height: '16px' })
|
||||
: matchedType.icon({ width: '16px', height: '16px' })}
|
||||
<span class="surface-ref-title">
|
||||
${modelNotFound
|
||||
? `This ${matchedType.name} not available`
|
||||
: `${title}`}
|
||||
</span>
|
||||
</div>
|
||||
<div class="surface-ref-body">
|
||||
<span class="surface-ref-text">
|
||||
${modelNotFound
|
||||
? `The ${matchedType.name.toLowerCase()} is deleted or not in this doc.`
|
||||
: `The ${matchedType.name.toLowerCase()} is inserted but cannot display in edgeless mode. Switch to page mode to view the block.`}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
${modelNotFound
|
||||
? html`<div class="not-found-background">
|
||||
${SurfaceRefNotFoundBackground}
|
||||
</div>`
|
||||
: nothing}
|
||||
`;
|
||||
}
|
||||
|
||||
override renderBlock() {
|
||||
return html` <div
|
||||
class=${classMap({
|
||||
'affine-edgeless-surface-ref-container': true,
|
||||
'not-found': !this._referenceModel,
|
||||
})}
|
||||
>
|
||||
${this._renderRefContent(this._referenceModel)}
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ export {
|
||||
GfxCompatibleBlockModel as GfxCompatible,
|
||||
type GfxCompatibleProps,
|
||||
} from './model/gfx-block-model.js';
|
||||
export { type GfxModel } from './model/model.js';
|
||||
export { type GfxModel, isPrimitiveModel } from './model/model.js';
|
||||
export {
|
||||
convert,
|
||||
convertProps,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { GfxGroupCompatibleInterface } from './base.js';
|
||||
import type { GfxBlockElementModel } from './gfx-block-model.js';
|
||||
import type {
|
||||
GfxGroupLikeElementModel,
|
||||
import {
|
||||
type GfxGroupLikeElementModel,
|
||||
GfxPrimitiveElementModel,
|
||||
} from './surface/element-model.js';
|
||||
|
||||
@@ -10,3 +10,9 @@ export type GfxModel = GfxBlockElementModel | GfxPrimitiveElementModel;
|
||||
export type GfxGroupModel =
|
||||
| (GfxGroupCompatibleInterface & GfxBlockElementModel)
|
||||
| GfxGroupLikeElementModel;
|
||||
|
||||
export const isPrimitiveModel = (
|
||||
model: GfxModel
|
||||
): model is GfxPrimitiveElementModel => {
|
||||
return model instanceof GfxPrimitiveElementModel;
|
||||
};
|
||||
|
||||
@@ -71,25 +71,6 @@ describe('basic', () => {
|
||||
).instanceOf(Element);
|
||||
});
|
||||
|
||||
test('surface-ref should be rendered as empty surface-ref-block-edgeless component page mode', async () => {
|
||||
const surfaceRefId = doc.addBlock(
|
||||
'affine:surface-ref',
|
||||
{
|
||||
reference: frameId,
|
||||
},
|
||||
noteAId
|
||||
);
|
||||
|
||||
await wait();
|
||||
|
||||
const refBlock = document.querySelector(
|
||||
`affine-edgeless-surface-ref[data-block-id="${surfaceRefId}"]`
|
||||
)! as HTMLElement;
|
||||
|
||||
expect(refBlock).instanceOf(Element);
|
||||
expect(refBlock.innerText).toBe('');
|
||||
});
|
||||
|
||||
test('content in frame should be rendered in the correct order', async () => {
|
||||
const surfaceRefId = doc.addBlock(
|
||||
'affine:surface-ref',
|
||||
|
||||
Reference in New Issue
Block a user