mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-22 19:53:48 +08:00
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:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user