fix(editor): switch view is not allowed during upload (#12018)
Closes: [BS-3352](https://linear.app/affine-design/issue/BS-3352/[improvement]-附件上传时禁止切-view)  <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## 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. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -94,18 +94,32 @@ export const attachmentViewDropdownMenu = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
content(ctx) {
|
content(ctx) {
|
||||||
const model = ctx.getCurrentModelByType(AttachmentBlockModel);
|
const block = ctx.getCurrentBlockByType(AttachmentBlockComponent);
|
||||||
if (!model) return null;
|
if (!block) return null;
|
||||||
|
|
||||||
|
const model = block.model;
|
||||||
const embedProvider = ctx.std.get(AttachmentEmbedProvider);
|
const embedProvider = ctx.std.get(AttachmentEmbedProvider);
|
||||||
const actions = this.actions.map(action => ({ ...action }));
|
const actions = computed(() => {
|
||||||
const viewType$ = computed(() => {
|
const [cardAction, embedAction] = this.actions.map(action => ({
|
||||||
const [cardAction, embedAction] = actions;
|
...action,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const ok = block.resourceController.resolvedState$.value.state === 'none';
|
||||||
|
const sourceId = Boolean(model.props.sourceId$.value);
|
||||||
const embed = model.props.embed$.value ?? false;
|
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;
|
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;
|
return embed ? embedAction.label : cardAction.label;
|
||||||
});
|
});
|
||||||
const onToggle = (e: CustomEvent<boolean>) => {
|
const onToggle = (e: CustomEvent<boolean>) => {
|
||||||
@@ -123,7 +137,7 @@ export const attachmentViewDropdownMenu = {
|
|||||||
model,
|
model,
|
||||||
html`<affine-view-dropdown-menu
|
html`<affine-view-dropdown-menu
|
||||||
@toggle=${onToggle}
|
@toggle=${onToggle}
|
||||||
.actions=${actions}
|
.actions=${actions.value}
|
||||||
.context=${ctx}
|
.context=${ctx}
|
||||||
.viewType$=${viewType$}
|
.viewType$=${viewType$}
|
||||||
></affine-view-dropdown-menu>`
|
></affine-view-dropdown-menu>`
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import type { Disposable } from '@blocksuite/global/disposable';
|
import type { Disposable } from '@blocksuite/global/disposable';
|
||||||
import type { BlobEngine, BlobState } from '@blocksuite/sync';
|
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';
|
import type { TemplateResult } from 'lit-html';
|
||||||
|
|
||||||
export type ResourceKind = 'Blob' | 'File' | 'Image';
|
export type ResourceKind = 'Blob' | 'File' | 'Image';
|
||||||
@@ -18,17 +23,40 @@ export type StateInfo = {
|
|||||||
description?: string;
|
description?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ResolvedStateInfo = StateInfo & {
|
export type ResolvedStateInfoPart = {
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
error: boolean;
|
error: boolean;
|
||||||
state: StateKind;
|
state: StateKind;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ResolvedStateInfo = StateInfo & ResolvedStateInfoPart;
|
||||||
|
|
||||||
export class ResourceController implements Disposable {
|
export class ResourceController implements Disposable {
|
||||||
readonly blobUrl$ = signal<string | null>(null);
|
readonly blobUrl$ = signal<string | null>(null);
|
||||||
|
|
||||||
readonly state$ = signal<Partial<BlobState>>({});
|
readonly state$ = signal<Partial<BlobState>>({});
|
||||||
|
|
||||||
|
readonly resolvedState$ = computed<ResolvedStateInfoPart>(() => {
|
||||||
|
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;
|
private engine?: BlobEngine;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -61,26 +89,12 @@ export class ResourceController implements Disposable {
|
|||||||
errorIcon?: TemplateResult;
|
errorIcon?: TemplateResult;
|
||||||
} & StateInfo
|
} & StateInfo
|
||||||
): ResolvedStateInfo {
|
): ResolvedStateInfo {
|
||||||
const {
|
const { error, loading, state } = this.resolvedState$.value;
|
||||||
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 { icon, title, description, loadingIcon, errorIcon } = info;
|
const { icon, title, description, loadingIcon, errorIcon } = info;
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
error: hasError,
|
error,
|
||||||
loading,
|
loading,
|
||||||
state,
|
state,
|
||||||
icon,
|
icon,
|
||||||
@@ -91,9 +105,9 @@ export class ResourceController implements Disposable {
|
|||||||
if (loading) {
|
if (loading) {
|
||||||
result.icon = loadingIcon ?? icon;
|
result.icon = loadingIcon ?? icon;
|
||||||
result.title = state === 'uploading' ? 'Uploading...' : 'Loading...';
|
result.title = state === 'uploading' ? 'Uploading...' : 'Loading...';
|
||||||
} else if (hasError) {
|
} else if (error) {
|
||||||
result.icon = errorIcon ?? icon;
|
result.icon = errorIcon ?? icon;
|
||||||
result.description = errorMessage ?? description;
|
result.description = this.state$.value.errorMessage ?? description;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
Reference in New Issue
Block a user