feat: dnd image preview && edgeless dnd preview issue (#10177)

### Changed
- Add new image block to render dnd preview
- Fixed the bug that dragging uploaded image does not have width and height
- Fixed the bug that drag image block from page to edgeless does not have width and height
- Better edgeless dnd preview
This commit is contained in:
doouding
2025-02-14 16:02:03 +00:00
parent 631c8b8145
commit 8ece812017
11 changed files with 245 additions and 53 deletions
@@ -2,10 +2,20 @@ import { ImageBlockFallbackCard } from './components/image-block-fallback.js';
import { ImageBlockPageComponent } from './components/page-image-block.js';
import { ImageBlockComponent } from './image-block.js';
import { ImageEdgelessBlockComponent } from './image-edgeless-block.js';
import { ImageEdgelessPlaceholderBlockComponent } from './preview-image/edgeless.js';
import { ImagePlaceholderBlockComponent } from './preview-image/page.js';
export function effects() {
customElements.define('affine-image', ImageBlockComponent);
customElements.define('affine-edgeless-image', ImageEdgelessBlockComponent);
customElements.define('affine-page-image', ImageBlockPageComponent);
customElements.define('affine-image-fallback-card', ImageBlockFallbackCard);
customElements.define(
'affine-placeholder-preview-image',
ImagePlaceholderBlockComponent
);
customElements.define(
'affine-edgeless-placeholder-preview-image',
ImageEdgelessPlaceholderBlockComponent
);
}
@@ -43,7 +43,7 @@ export class ImageEdgelessBlockComponent extends GfxBlockComponent<ImageBlockMod
fetchImageBlob(this)
.then(() => {
const { width, height } = this.model;
if (!width || !height) {
if ((!width || !height) && this.blob) {
return resetImageSize(this);
}
@@ -0,0 +1,25 @@
import { toGfxBlockComponent } from '@blocksuite/block-std';
import { css } from 'lit';
import { ImagePlaceholderBlockComponent } from './page.js';
export class ImageEdgelessPlaceholderBlockComponent extends toGfxBlockComponent(
ImagePlaceholderBlockComponent
) {
static override styles = css`
affine-edgeless-placeholder-preview-image
.affine-placeholder-preview-container {
border: 1px solid var(--affine-background-tertiary-color);
}
`;
override renderGfxBlock(): unknown {
return super.renderGfxBlock();
}
}
declare global {
interface HTMLElementTagNameMap {
'affine-edgeless-placeholder-preview-image': ImageEdgelessPlaceholderBlockComponent;
}
}
@@ -0,0 +1,58 @@
import type { ImageBlockModel } from '@blocksuite/affine-model';
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
import { BlockComponent } from '@blocksuite/block-std';
import { ImageIcon } from '@blocksuite/icons/lit';
import { css, html } from 'lit';
export class ImagePlaceholderBlockComponent extends BlockComponent<ImageBlockModel> {
static override styles = css`
.affine-placeholder-preview-container {
display: flex;
flex-direction: column;
align-items: flex-start;
padding: 4px;
box-sizing: border-box;
border-radius: 8px;
background-color: ${unsafeCSSVarV2(
'layer/background/overlayPanel',
'#FBFBFC'
)};
}
.placeholder-preview-content {
padding: 4px 16px;
display: flex;
gap: 8px;
}
.placeholder-preview-content > svg {
color: ${unsafeCSSVarV2('icon/primary', '#77757D')};
}
.placeholder-preview-content > .text {
color: var(--affine-text-primary-color);
color: ${unsafeCSSVarV2('text/primary', '#121212')};
font-size: 14px;
line-height: 24px;
}
`;
override renderBlock() {
return html`<div
class="affine-placeholder-preview-container"
contenteditable="false"
>
<div class="placeholder-preview-content">
${ImageIcon({ width: '24px', height: '24px' })}
<span class="text">Image Block</span>
</div>
</div>`;
}
}
declare global {
interface HTMLElementTagNameMap {
'affine-placeholder-preview-image': ImagePlaceholderBlockComponent;
}
}
+35 -11
View File
@@ -68,15 +68,30 @@ export async function uploadBlobForImage(
setImageUploaded(blockId);
const imageModel = doc.getBlockById(blockId) as ImageBlockModel | null;
doc.withoutTransact(() => {
if (!imageModel) {
return;
}
doc.updateBlock(imageModel, {
if (sourceId && imageModel) {
const props: Partial<ImageBlockProps> = {
sourceId,
} satisfies Partial<ImageBlockProps>);
});
// Assign a default size to make sure the image can be displayed correctly.
width: 100,
height: 100,
};
const blob = await doc.blobSync.get(sourceId);
if (blob) {
try {
const size = await readImageSize(blob);
props.width = size.width;
props.height = size.height;
} catch {
// Ignore the error
console.warn('Failed to read image size');
}
}
doc.withoutTransact(() => {
doc.updateBlock(imageModel, props);
});
}
}
}
@@ -198,10 +213,19 @@ export async function resetImageSize(
const file = new File([blob], 'image.png', { type: blob.type });
const size = await readImageSize(file);
block.doc.updateBlock(model, {
const bound = model.elementBound;
const props: Partial<ImageBlockProps> = {
width: size.width,
height: size.height,
});
};
if (!bound.w || !bound.h) {
bound.w = size.width;
bound.h = size.height;
props.xywh = bound.serialize();
}
block.doc.updateBlock(model, props);
}
function convertToPng(blob: Blob): Promise<Blob | null> {
@@ -401,7 +425,7 @@ export async function turnImageIntoCardView(
transformModel(model, 'affine:attachment', attachmentProp);
}
export function readImageSize(file: File) {
export function readImageSize(file: File | Blob) {
return new Promise<{ width: number; height: number }>(resolve => {
const size = { width: 0, height: 0 };
const img = new Image();