From 179701f587539837c27b40959e6f13fe3ca23a60 Mon Sep 17 00:00:00 2001 From: fundon Date: Wed, 7 May 2025 08:01:56 +0000 Subject: [PATCH] fix(editor): switch view is not allowed during upload (#12018) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes: [BS-3352](https://linear.app/affine-design/issue/BS-3352/[improvement]-附件上传时禁止切-view) ![Screenshot 2025-05-07 at 15.50.58.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/8ypiIKZXudF5a0tIgIzf/a2267752-8459-44f7-8c24-08bb50d390c0.png) ## Summary by CodeRabbit - **Bug Fixes** - Improved the logic for enabling or disabling the embed and card actions in the attachment toolbar, ensuring actions are only available when the file is ready and conditions are met. - **Refactor** - Enhanced reliability by centralizing block state checks and refining conditions for action availability in the attachment dropdown menu. - Centralized resource state management with a new computed signal, improving consistency in loading and error state handling. --- .../blocks/attachment/src/configs/toolbar.ts | 28 +++++++--- .../affine/components/src/resource/index.ts | 54 ++++++++++++------- 2 files changed, 55 insertions(+), 27 deletions(-) diff --git a/blocksuite/affine/blocks/attachment/src/configs/toolbar.ts b/blocksuite/affine/blocks/attachment/src/configs/toolbar.ts index d9e952c755..254d880cd5 100644 --- a/blocksuite/affine/blocks/attachment/src/configs/toolbar.ts +++ b/blocksuite/affine/blocks/attachment/src/configs/toolbar.ts @@ -94,18 +94,32 @@ export const attachmentViewDropdownMenu = { }, ], content(ctx) { - const model = ctx.getCurrentModelByType(AttachmentBlockModel); - if (!model) return null; + const block = ctx.getCurrentBlockByType(AttachmentBlockComponent); + if (!block) return null; + const model = block.model; const embedProvider = ctx.std.get(AttachmentEmbedProvider); - const actions = this.actions.map(action => ({ ...action })); - const viewType$ = computed(() => { - const [cardAction, embedAction] = actions; + const actions = computed(() => { + const [cardAction, embedAction] = this.actions.map(action => ({ + ...action, + })); + + const ok = block.resourceController.resolvedState$.value.state === 'none'; + const sourceId = Boolean(model.props.sourceId$.value); const embed = model.props.embed$.value ?? false; + // 1. Check whether `sourceId` exists. + // 2. Check if `embedded` is allowed. + // 3. Check `blobState$` + const allowed = ok && sourceId && embedProvider.embedded(model) && !embed; cardAction.disabled = !embed; - embedAction.disabled = embed && embedProvider.embedded(model); + embedAction.disabled = !allowed; + return [cardAction, embedAction]; + }); + const viewType$ = computed(() => { + const [cardAction, embedAction] = actions.value; + const embed = model.props.embed$.value ?? false; return embed ? embedAction.label : cardAction.label; }); const onToggle = (e: CustomEvent) => { @@ -123,7 +137,7 @@ export const attachmentViewDropdownMenu = { model, html`` diff --git a/blocksuite/affine/components/src/resource/index.ts b/blocksuite/affine/components/src/resource/index.ts index c040b61cfd..4c9c41406e 100644 --- a/blocksuite/affine/components/src/resource/index.ts +++ b/blocksuite/affine/components/src/resource/index.ts @@ -1,6 +1,11 @@ import type { Disposable } from '@blocksuite/global/disposable'; import type { BlobEngine, BlobState } from '@blocksuite/sync'; -import { effect, type ReadonlySignal, signal } from '@preact/signals-core'; +import { + computed, + effect, + type ReadonlySignal, + signal, +} from '@preact/signals-core'; import type { TemplateResult } from 'lit-html'; export type ResourceKind = 'Blob' | 'File' | 'Image'; @@ -18,17 +23,40 @@ export type StateInfo = { description?: string; }; -export type ResolvedStateInfo = StateInfo & { +export type ResolvedStateInfoPart = { loading: boolean; error: boolean; state: StateKind; }; +export type ResolvedStateInfo = StateInfo & ResolvedStateInfoPart; + export class ResourceController implements Disposable { readonly blobUrl$ = signal(null); readonly state$ = signal>({}); + readonly resolvedState$ = computed(() => { + const { + uploading = false, + downloading = false, + overSize = false, + errorMessage, + } = this.state$.value; + const hasExceeded = overSize; + const hasError = hasExceeded || Boolean(errorMessage); + const state = this.determineState( + hasExceeded, + hasError, + uploading, + downloading + ); + + const loading = state === 'uploading' || state === 'loading'; + + return { error: hasError, loading, state }; + }); + private engine?: BlobEngine; constructor( @@ -61,26 +89,12 @@ export class ResourceController implements Disposable { errorIcon?: TemplateResult; } & StateInfo ): ResolvedStateInfo { - const { - uploading = false, - downloading = false, - overSize = false, - errorMessage, - } = this.state$.value; - const hasExceeded = overSize; - const hasError = hasExceeded || Boolean(errorMessage); - const state = this.determineState( - hasExceeded, - hasError, - uploading, - downloading - ); - const loading = state === 'uploading' || state === 'loading'; + const { error, loading, state } = this.resolvedState$.value; const { icon, title, description, loadingIcon, errorIcon } = info; const result = { - error: hasError, + error, loading, state, icon, @@ -91,9 +105,9 @@ export class ResourceController implements Disposable { if (loading) { result.icon = loadingIcon ?? icon; result.title = state === 'uploading' ? 'Uploading...' : 'Loading...'; - } else if (hasError) { + } else if (error) { result.icon = errorIcon ?? icon; - result.description = errorMessage ?? description; + result.description = this.state$.value.errorMessage ?? description; } return result;