mirror of
https://github.com/google/blockly.git
synced 2026-01-07 00:50:27 +01:00
feat: Add support for toggling readonly mode. (#8750)
* feat: Add methods for toggling and inspecting the readonly state of a workspace. * refactor: Use the new readonly setters/getters in place of checking the injection options. * fix: Fix bug that allowed dragging blocks from a flyout onto a readonly workspace. * feat: Toggle a `blocklyReadOnly` class when readonly status is changed. * chore: Placate the linter. * chore: Placate the compiler.
This commit is contained in:
@@ -795,7 +795,7 @@ export class Block implements IASTNodeLocation {
|
||||
this.deletable &&
|
||||
!this.shadow &&
|
||||
!this.isDeadOrDying() &&
|
||||
!this.workspace.options.readOnly
|
||||
!this.workspace.isReadOnly()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -828,7 +828,7 @@ export class Block implements IASTNodeLocation {
|
||||
this.movable &&
|
||||
!this.shadow &&
|
||||
!this.isDeadOrDying() &&
|
||||
!this.workspace.options.readOnly
|
||||
!this.workspace.isReadOnly()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -917,7 +917,7 @@ export class Block implements IASTNodeLocation {
|
||||
*/
|
||||
isEditable(): boolean {
|
||||
return (
|
||||
this.editable && !this.isDeadOrDying() && !this.workspace.options.readOnly
|
||||
this.editable && !this.isDeadOrDying() && !this.workspace.isReadOnly()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -231,7 +231,7 @@ export class BlockSvg
|
||||
this.applyColour();
|
||||
this.pathObject.updateMovable(this.isMovable() || this.isInFlyout);
|
||||
const svg = this.getSvgRoot();
|
||||
if (!this.workspace.options.readOnly && svg) {
|
||||
if (svg) {
|
||||
browserEvents.conditionalBind(svg, 'pointerdown', this, this.onMouseDown);
|
||||
}
|
||||
|
||||
@@ -585,6 +585,8 @@ export class BlockSvg
|
||||
* @param e Pointer down event.
|
||||
*/
|
||||
private onMouseDown(e: PointerEvent) {
|
||||
if (this.workspace.isReadOnly()) return;
|
||||
|
||||
const gesture = this.workspace.getGesture(e);
|
||||
if (gesture) {
|
||||
gesture.handleBlockStart(e, this);
|
||||
@@ -612,7 +614,7 @@ export class BlockSvg
|
||||
protected generateContextMenu(): Array<
|
||||
ContextMenuOption | LegacyContextMenuOption
|
||||
> | null {
|
||||
if (this.workspace.options.readOnly || !this.contextMenu) {
|
||||
if (this.workspace.isReadOnly() || !this.contextMenu) {
|
||||
return null;
|
||||
}
|
||||
const menuOptions = ContextMenuRegistry.registry.getContextMenuOptions(
|
||||
|
||||
@@ -144,7 +144,7 @@ export class WorkspaceComment {
|
||||
* workspace is read-only.
|
||||
*/
|
||||
isEditable(): boolean {
|
||||
return this.isOwnEditable() && !this.workspace.options.readOnly;
|
||||
return this.isOwnEditable() && !this.workspace.isReadOnly();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -165,7 +165,7 @@ export class WorkspaceComment {
|
||||
* workspace is read-only.
|
||||
*/
|
||||
isMovable() {
|
||||
return this.isOwnMovable() && !this.workspace.options.readOnly;
|
||||
return this.isOwnMovable() && !this.workspace.isReadOnly();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -189,7 +189,7 @@ export class WorkspaceComment {
|
||||
return (
|
||||
this.isOwnDeletable() &&
|
||||
!this.isDeadOrDying() &&
|
||||
!this.workspace.options.readOnly
|
||||
!this.workspace.isReadOnly()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ export class BlockDragStrategy implements IDragStrategy {
|
||||
return (
|
||||
this.block.isOwnMovable() &&
|
||||
!this.block.isDeadOrDying() &&
|
||||
!this.workspace.options.readOnly &&
|
||||
!this.workspace.isReadOnly() &&
|
||||
// We never drag blocks in the flyout, only create new blocks that are
|
||||
// dragged.
|
||||
!this.block.isInFlyout
|
||||
|
||||
@@ -29,7 +29,7 @@ export class CommentDragStrategy implements IDragStrategy {
|
||||
return (
|
||||
this.comment.isOwnMovable() &&
|
||||
!this.comment.isDeadOrDying() &&
|
||||
!this.workspace.options.readOnly
|
||||
!this.workspace.isReadOnly()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -786,7 +786,7 @@ export abstract class Flyout
|
||||
* @internal
|
||||
*/
|
||||
isBlockCreatable(block: BlockSvg): boolean {
|
||||
return block.isEnabled();
|
||||
return block.isEnabled() && !this.getTargetWorkspace().isReadOnly();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -894,7 +894,7 @@ export class Gesture {
|
||||
'Cannot do a block click because the target block is ' + 'undefined',
|
||||
);
|
||||
}
|
||||
if (this.targetBlock.isEnabled()) {
|
||||
if (this.flyout.isBlockCreatable(this.targetBlock)) {
|
||||
if (!eventUtils.getGroup()) {
|
||||
eventUtils.setGroup(true);
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ export function registerEscape() {
|
||||
const escapeAction: KeyboardShortcut = {
|
||||
name: names.ESCAPE,
|
||||
preconditionFn(workspace) {
|
||||
return !workspace.options.readOnly;
|
||||
return !workspace.isReadOnly();
|
||||
},
|
||||
callback(workspace) {
|
||||
// AnyDuringMigration because: Property 'hideChaff' does not exist on
|
||||
@@ -62,7 +62,7 @@ export function registerDelete() {
|
||||
preconditionFn(workspace) {
|
||||
const selected = common.getSelected();
|
||||
return (
|
||||
!workspace.options.readOnly &&
|
||||
!workspace.isReadOnly() &&
|
||||
selected != null &&
|
||||
isDeletable(selected) &&
|
||||
selected.isDeletable() &&
|
||||
@@ -113,7 +113,7 @@ export function registerCopy() {
|
||||
preconditionFn(workspace) {
|
||||
const selected = common.getSelected();
|
||||
return (
|
||||
!workspace.options.readOnly &&
|
||||
!workspace.isReadOnly() &&
|
||||
!Gesture.inProgress() &&
|
||||
selected != null &&
|
||||
isDeletable(selected) &&
|
||||
@@ -164,7 +164,7 @@ export function registerCut() {
|
||||
preconditionFn(workspace) {
|
||||
const selected = common.getSelected();
|
||||
return (
|
||||
!workspace.options.readOnly &&
|
||||
!workspace.isReadOnly() &&
|
||||
!Gesture.inProgress() &&
|
||||
selected != null &&
|
||||
isDeletable(selected) &&
|
||||
@@ -221,7 +221,7 @@ export function registerPaste() {
|
||||
const pasteShortcut: KeyboardShortcut = {
|
||||
name: names.PASTE,
|
||||
preconditionFn(workspace) {
|
||||
return !workspace.options.readOnly && !Gesture.inProgress();
|
||||
return !workspace.isReadOnly() && !Gesture.inProgress();
|
||||
},
|
||||
callback() {
|
||||
if (!copyData || !copyWorkspace) return false;
|
||||
@@ -269,7 +269,7 @@ export function registerUndo() {
|
||||
const undoShortcut: KeyboardShortcut = {
|
||||
name: names.UNDO,
|
||||
preconditionFn(workspace) {
|
||||
return !workspace.options.readOnly && !Gesture.inProgress();
|
||||
return !workspace.isReadOnly() && !Gesture.inProgress();
|
||||
},
|
||||
callback(workspace, e) {
|
||||
// 'z' for undo 'Z' is for redo.
|
||||
@@ -308,7 +308,7 @@ export function registerRedo() {
|
||||
const redoShortcut: KeyboardShortcut = {
|
||||
name: names.REDO,
|
||||
preconditionFn(workspace) {
|
||||
return !Gesture.inProgress() && !workspace.options.readOnly;
|
||||
return !Gesture.inProgress() && !workspace.isReadOnly();
|
||||
},
|
||||
callback(workspace, e) {
|
||||
// 'z' for undo 'Z' is for redo.
|
||||
|
||||
@@ -114,6 +114,7 @@ export class Workspace implements IASTNodeLocation {
|
||||
private readonly typedBlocksDB = new Map<string, Block[]>();
|
||||
private variableMap: IVariableMap<IVariableModel<IVariableState>>;
|
||||
private procedureMap: IProcedureMap = new ObservableProcedureMap();
|
||||
private readOnly = false;
|
||||
|
||||
/**
|
||||
* Blocks in the flyout can refer to variables that don't exist in the main
|
||||
@@ -153,6 +154,8 @@ export class Workspace implements IASTNodeLocation {
|
||||
*/
|
||||
const VariableMap = this.getVariableMapClass();
|
||||
this.variableMap = new VariableMap(this);
|
||||
|
||||
this.setIsReadOnly(this.options.readOnly);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -947,4 +950,23 @@ export class Workspace implements IASTNodeLocation {
|
||||
}
|
||||
return VariableMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not this workspace is in readonly mode.
|
||||
*
|
||||
* @returns True if the workspace is readonly, otherwise false.
|
||||
*/
|
||||
isReadOnly(): boolean {
|
||||
return this.readOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not this workspace is in readonly mode.
|
||||
*
|
||||
* @param readOnly True to make the workspace readonly, otherwise false.
|
||||
*/
|
||||
setIsReadOnly(readOnly: boolean) {
|
||||
this.readOnly = readOnly;
|
||||
this.options.readOnly = readOnly;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1703,7 +1703,7 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg {
|
||||
* @internal
|
||||
*/
|
||||
showContextMenu(e: PointerEvent) {
|
||||
if (this.options.readOnly || this.isFlyout) {
|
||||
if (this.isReadOnly() || this.isFlyout) {
|
||||
return;
|
||||
}
|
||||
const menuOptions = ContextMenuRegistry.registry.getContextMenuOptions(
|
||||
@@ -2532,6 +2532,15 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg {
|
||||
dom.removeClass(this.injectionDiv, className);
|
||||
}
|
||||
}
|
||||
|
||||
override setIsReadOnly(readOnly: boolean) {
|
||||
super.setIsReadOnly(readOnly);
|
||||
if (readOnly) {
|
||||
this.addClass('blocklyReadOnly');
|
||||
} else {
|
||||
this.removeClass('blocklyReadOnly');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -42,7 +42,7 @@ suite('Key Down', function () {
|
||||
function runReadOnlyTest(keyEvent, opt_name) {
|
||||
const name = opt_name ? opt_name : 'Not called when readOnly is true';
|
||||
test(name, function () {
|
||||
this.workspace.options.readOnly = true;
|
||||
this.workspace.setIsReadOnly(true);
|
||||
this.injectionDiv.dispatchEvent(keyEvent);
|
||||
sinon.assert.notCalled(this.hideChaffSpy);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user