Add isCopyable and isCuttable as optional methods on ICopyable

This commit is contained in:
Erik Pasternak
2025-06-09 15:13:43 -07:00
parent 02f89d6f96
commit 9685498d21
4 changed files with 62 additions and 51 deletions

View File

@@ -1721,6 +1721,16 @@ export class BlockSvg
this.dragStrategy = dragStrategy;
}
/** Returns whether this block is copyable or not. */
isCopyable(): boolean {
return this.isOwnDeletable() && this.isOwnMovable();
}
/** Returns whether this block is cuttable or not. */
isCuttable(): boolean {
return this.isDeletable() && this.isMovable();
}
/** Returns whether this block is movable or not. */
override isMovable(): boolean {
return this.dragStrategy.isMovable();

View File

@@ -244,6 +244,16 @@ export class RenderedWorkspaceComment
}
}
/** Returns whether this comment is copyable or not */
isCopyable(): boolean {
return this.isOwnMovable() && this.isOwnDeletable();
}
/** Returns whether this comment is cuttable or not */
isCuttable(): boolean {
return this.isMovable() && this.isDeletable();
}
/** Returns whether this comment is movable or not. */
isMovable(): boolean {
return this.dragStrategy.isMovable();

View File

@@ -15,6 +15,20 @@ export interface ICopyable<T extends ICopyData> extends ISelectable {
* @returns Copy metadata.
*/
toCopyData(): T | null;
/**
* Whether this instance is currently copyable.
*
* @returns True if it can currently be copied.
*/
isCopyable?(): boolean;
/**
* Whether this instance is currently cuttable.
*
* @returns True if it can currently be cut.
*/
isCuttable?(): boolean;
}
export namespace ICopyable {
@@ -25,7 +39,7 @@ export namespace ICopyable {
export type ICopyData = ICopyable.ICopyData;
/** @returns true if the given object is copyable. */
/** @returns true if the given object is an ICopyable. */
export function isCopyable(obj: any): obj is ICopyable<ICopyData> {
return obj.toCopyData !== undefined;
}

View File

@@ -8,6 +8,7 @@
import {BlockSvg} from './block_svg.js';
import * as clipboard from './clipboard.js';
import { RenderedWorkspaceComment } from './comments.js';
import * as eventUtils from './events/utils.js';
import {getFocusManager} from './focus_manager.js';
import {Gesture} from './gesture.js';
@@ -106,68 +107,44 @@ let copyCoords: Coordinate | null = null;
/**
* Determine if a focusable node can be copied.
*
* Unfortunately the ICopyable interface doesn't include an isCopyable
* method, so we must use some other criteria to make the decision.
* Specifically,
*
* - It must be an ICopyable.
* - So that a pasted copy can be manipluated and/or disposed of, it
* must be both an IDraggable and an IDeletable.
* - Additionally, both .isOwnMovable() and .isOwnDeletable() must return
* true (i.e., the copy could be moved and deleted).
*
* TODO(#9098): Revise these criteria. The latter criteria prevents
* shadow blocks from being copied; additionally, there are likely to
* be other circumstances were it is desirable to allow movable /
* copyable copies of a currently-unmovable / -copyable block to be
* made.
* This will use the isCopyable method if the node implements it, otherwise
* it will fall back to checking if the node is deletable and draggable not
* considering the workspace's edit state.
*
* @param focused The focused object.
*/
function isCopyable(
focused: IFocusableNode,
): focused is ICopyable<ICopyData> & IDeletable & IDraggable {
if (!(focused instanceof BlockSvg)) return false;
return (
isICopyable(focused) &&
isIDeletable(focused) &&
focused.isOwnDeletable() &&
isDraggable(focused) &&
focused.isOwnMovable()
);
function isCopyable(focused: IFocusableNode): boolean {
if (!isICopyable(focused) || !isIDeletable(focused) || !isDraggable(focused))
return false;
if (focused.isCopyable !== undefined) {
return focused.isCopyable();
} else if (
focused instanceof BlockSvg ||
focused instanceof RenderedWorkspaceComment
) {
return focused.isOwnDeletable() && focused.isOwnMovable();
}
// This isn't a class Blockly knows about, so fall back to the stricter
// checks for deletable and movable.
return focused.isDeletable() && focused.isMovable();
}
/**
* Determine if a focusable node can be cut.
*
* Unfortunately the ICopyable interface doesn't include an isCuttable
* method, so we must use some other criteria to make the decision.
* Specifically,
*
* - It must be an ICopyable.
* - So that a pasted copy can be manipluated and/or disposed of, it
* must be both an IDraggable and an IDeletable.
* - Additionally, both .isMovable() and .isDeletable() must return
* true (i.e., can currently be moved and deleted). This is the main
* difference with isCopyable.
*
* TODO(#9098): Revise these criteria. The latter criteria prevents
* shadow blocks from being copied; additionally, there are likely to
* be other circumstances were it is desirable to allow movable /
* copyable copies of a currently-unmovable / -copyable block to be
* made.
* This will use the isCuttable method if the node implements it, otherwise
* it will fall back to checking if the node can be moved and deleted in its
* current workspace.
*
* @param focused The focused object.
*/
function isCuttable(focused: IFocusableNode): boolean {
if (!(focused instanceof BlockSvg)) return false;
return (
isICopyable(focused) &&
isIDeletable(focused) &&
focused.isDeletable() &&
isDraggable(focused) &&
focused.isMovable()
);
if (!isICopyable(focused) || !isIDeletable(focused) || !isDraggable(focused))
return false;
if (focused.isCuttable !== undefined) {
return focused.isCuttable();
}
return focused.isMovable() && focused.isDeletable();
}
/**