feat!: Invalid Blocks (#7958)

* feat: Invalid Blocks

* Rename the new json property from invalid to invalidReasons.

* Merged isValid into isEnabled.

* Minor fixes.

* More minor fixes.

* Reverting some stuff that didn't need to change.

* Addressing PR feedback.

* Update the BlockInfo interface to match State.

* Make BlockChange.disabledReason private.
This commit is contained in:
John Nesky
2024-04-17 19:47:51 -07:00
committed by GitHub
parent 7d8f88a4f1
commit cee7f916bb
26 changed files with 492 additions and 97 deletions

View File

@@ -25,7 +25,9 @@ import {ConnectionType} from './connection_type.js';
import * as constants from './constants.js';
import {DuplicateIconType} from './icons/exceptions.js';
import type {Abstract} from './events/events_abstract.js';
import type {BlockChange} from './events/events_block_change.js';
import type {BlockMove} from './events/events_block_move.js';
import * as deprecation from './utils/deprecation.js';
import * as eventUtils from './events/utils.js';
import * as Extensions from './extensions.js';
import type {Field} from './field.js';
@@ -166,7 +168,7 @@ export class Block implements IASTNodeLocation {
inputList: Input[] = [];
inputsInline?: boolean;
icons: IIcon[] = [];
private disabled = false;
private disabledReasons = new Set<string>();
tooltip: Tooltip.TipInfo = '';
contextMenu = true;
@@ -1390,32 +1392,89 @@ export class Block implements IASTNodeLocation {
}
/**
* Get whether this block is enabled or not.
* Get whether this block is enabled or not. A block is considered enabled
* if there aren't any reasons why it would be disabled. A block may still
* be disabled for other reasons even if the user attempts to manually
* enable it, such as when the block is in an invalid location.
*
* @returns True if enabled.
*/
isEnabled(): boolean {
return !this.disabled;
return this.disabledReasons.size === 0;
}
/** @deprecated v11 - Get whether the block is manually disabled. */
private get disabled(): boolean {
deprecation.warn(
'disabled',
'v11',
'v12',
'the isEnabled or hasDisabledReason methods of Block',
);
return this.hasDisabledReason(constants.MANUALLY_DISABLED);
}
/** @deprecated v11 - Set whether the block is manually disabled. */
private set disabled(value: boolean) {
deprecation.warn(
'disabled',
'v11',
'v12',
'the setDisabledReason method of Block',
);
this.setDisabledReason(value, constants.MANUALLY_DISABLED);
}
/**
* Set whether the block is enabled or not.
* @deprecated v11 - Set whether the block is manually enabled or disabled.
* The user can toggle whether a block is disabled from a context menu
* option. A block may still be disabled for other reasons even if the user
* attempts to manually enable it, such as when the block is in an invalid
* location. This method is deprecated and setDisabledReason should be used
* instead.
*
* @param enabled True if enabled.
*/
setEnabled(enabled: boolean) {
if (this.isEnabled() !== enabled) {
const oldValue = this.disabled;
this.disabled = !enabled;
eventUtils.fire(
new (eventUtils.get(eventUtils.BLOCK_CHANGE))(
this,
'disabled',
null,
oldValue,
!enabled,
),
);
deprecation.warn(
'setEnabled',
'v11',
'v12',
'the setDisabledReason method of Block',
);
this.setDisabledReason(!enabled, constants.MANUALLY_DISABLED);
}
/**
* Add or remove a reason why the block might be disabled. If a block has
* any reasons to be disabled, then the block itself will be considered
* disabled. A block could be disabled for multiple independent reasons
* simultaneously, such as when the user manually disables it, or the block
* is invalid.
*
* @param disabled If true, then the block should be considered disabled for
* at least the provided reason, otherwise the block is no longer disabled
* for that reason.
* @param reason A language-neutral identifier for a reason why the block
* could be disabled. Call this method again with the same identifier to
* update whether the block is currently disabled for this reason.
*/
setDisabledReason(disabled: boolean, reason: string): void {
if (this.disabledReasons.has(reason) !== disabled) {
if (disabled) {
this.disabledReasons.add(reason);
} else {
this.disabledReasons.delete(reason);
}
const blockChangeEvent = new (eventUtils.get(eventUtils.BLOCK_CHANGE))(
this,
'disabled',
/* name= */ null,
/* oldValue= */ !disabled,
/* newValue= */ disabled,
) as BlockChange;
blockChangeEvent.setDisabledReason(reason);
eventUtils.fire(blockChangeEvent);
}
}
@@ -1428,7 +1487,7 @@ export class Block implements IASTNodeLocation {
getInheritedDisabled(): boolean {
let ancestor = this.getSurroundParent();
while (ancestor) {
if (ancestor.disabled) {
if (!ancestor.isEnabled()) {
return true;
}
ancestor = ancestor.getSurroundParent();
@@ -1437,6 +1496,27 @@ export class Block implements IASTNodeLocation {
return false;
}
/**
* Get whether the block is currently disabled for the provided reason.
*
* @param reason A language-neutral identifier for a reason why the block
* could be disabled.
* @returns Whether the block is disabled for the provided reason.
*/
hasDisabledReason(reason: string): boolean {
return this.disabledReasons.has(reason);
}
/**
* Get a set of reasons why the block is currently disabled, if any. If the
* block is enabled, this set will be empty.
*
* @returns The set of reasons why the block is disabled, if any.
*/
getDisabledReasons(): ReadonlySet<string> {
return this.disabledReasons;
}
/**
* Get whether the block is collapsed or not.
*