Skip to content

V16: Moving from readOnlyGuard/readOnlyState to propertyWriteGuard does not make the node name readonly #19586

@warrenbuckley

Description

@warrenbuckley

Which Umbraco version are you using?

16.0.0

Bug summary

I am upgrading from Umbraco 15.0 and have learnt readOnlyState is no longer a thing and discovered readOnlyGuard but from reading docs it states the following

readOnlyGuard (This will be removed in the future. Use propertyWriteGuard instead)

So after porting my code to use propertyWriteGuard I have noticed the node name is still editable and no longer displays the label of readonly.

Specifics

Screenshot

Image

Steps to reproduce

My code for my workspace context as follows, but its pretty much the same as the one in the documentation
https://docs.umbraco.com/umbraco-cms/customizing/property-level-ui-permissions#write-a-general-rule

Code Sample

import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
import { type UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UMB_DOCUMENT_WORKSPACE_CONTEXT, UmbDocumentWorkspaceContext } from '@umbraco-cms/backoffice/document';
import { observeMultiple, UmbBooleanState, UmbStringState } from '@umbraco-cms/backoffice/observable-api';
import { UmbEntityUnique } from '@umbraco-cms/backoffice/entity';
import ContentLockSignalrContext, { CONTENTLOCK_SIGNALR_CONTEXT } from '../globalContexts/contentlock.signalr.context';
import { UMB_CURRENT_USER_CONTEXT } from '@umbraco-cms/backoffice/current-user';
import { UMB_CONFIRM_MODAL, umbOpenModal } from '@umbraco-cms/backoffice/modal';
import { UmbLocalizationController } from '@umbraco-cms/backoffice/localization-api';

export class ContentLockWorkspaceContext extends UmbContextBase {

    #docWorkspaceCtx?: UmbDocumentWorkspaceContext;
    #unique: UmbEntityUnique | undefined;
  
    #isLocked = new UmbBooleanState(false);
    isLocked = this.#isLocked.asObservable();

    #isLockedBySelf = new UmbBooleanState(false);
    isLockedBySelf = this.#isLockedBySelf.asObservable();

    #lockedByName = new UmbStringState('');
    lockedByName = this.#lockedByName.asObservable();

    #signalRContext?: ContentLockSignalrContext;

    #currentUserKey?: string;

    #localize = new UmbLocalizationController(this);

	constructor(host: UmbControllerHost) {
		super(host, CONTENTLOCK_WORKSPACE_CONTEXT.toString());

        this.consumeContext(CONTENTLOCK_SIGNALR_CONTEXT, (signalRContext) => {
           this.#signalRContext = signalRContext;
        });

        this.consumeContext(UMB_CURRENT_USER_CONTEXT, (currentUserCtx) => {
            this.observe(currentUserCtx?.unique, (currentUserKey) => {
                this.#currentUserKey = currentUserKey;

                this.checkContentLockState();
            });
        });

        this.consumeContext(UMB_DOCUMENT_WORKSPACE_CONTEXT, (docWorkspaceCtx) => {
            this.#docWorkspaceCtx = docWorkspaceCtx;

            this.#docWorkspaceCtx?.observe(this.#docWorkspaceCtx?.unique, (unique) => {
                this.#unique = unique;
                this.checkContentLockState();
            });
        });
	}

    public async checkContentLockState() {
        if(!this.#unique) return;
        if(!this.#currentUserKey) return;

        if (this.#signalRContext) {

            let previousState = { isLocked: false, isLockedBySelf: false }; 
            
            // Observe the 3 states from the SignalR context
            // isLocked: boolean - Is the item locked by anyone
            // isLockedBySelf: boolean - Is the item locked by the current user
            // lockInfo: object - Contains the lock info, including who locked it
            this.observe(observeMultiple([
                this.#signalRContext.isNodeLocked(this.#unique.toString()),
                this.#signalRContext.isNodeLockedByMe(this.#unique.toString(), this.#currentUserKey),
                this.#signalRContext.getLock(this.#unique.toString())
            ]), async ([isLocked, isLockedBySelf, lockInfo]) => {

                // Set the observables
                this.setIsLocked(isLocked);
                this.setIsLockedBySelf(isLockedBySelf);
                if (lockInfo) {
                    this.setLockedByName(lockInfo.checkedOutBy);
                }

                if(isLocked && isLockedBySelf === false){
                    // Page is locked by someone else - set the propertyWriteGuard
                    this.#docWorkspaceCtx?.propertyWriteGuard.addRule({
                        unique: `ContentLock-${this.#unique!.toString()}`,
                        permitted: false,
                        message: `This page is locked by ${lockInfo?.checkedOutBy}`
                    });
                }
                else {
                    // Page is not locked or its locked by self - remove the readonly (propertyWriteGuard)
                    this.#docWorkspaceCtx?.propertyWriteGuard.removeRule(`ContentLock-${this.#unique!.toString()}`);
                }

                // If previously the page was locked by someone else, we can alert the user its now unlocked
                if (previousState.isLocked && !previousState.isLockedBySelf && !isLocked && !isLockedBySelf) {
                    umbOpenModal(this, UMB_CONFIRM_MODAL,
                        {
                            data: {
                                headline: this.#localize.term('contentUnlockedModal_modalHeader'),
                                content: this.#localize.term('contentUnlockedModal_modalContent'),
                                color: "positive",
                                confirmLabel: this.#localize.term('contentUnlockedModal_reload'),
                            }
                        }
                    )
                    .then(async () => {
                        // This will reload the entire page, so the user can see the latest version of the content
                        // Might be nice if we can just ask a context to reload the node or something?

                        // There is reload on Workspace context
                        await this.#docWorkspaceCtx?.reload();
                    })
                    .catch(() => {
                        // Do nothing if the user cancels the modal or presses escape etc
                    });
                }

                // Update the previous state
                previousState = { isLocked, isLockedBySelf };
            });
        }
    }
    
	public getIsLocked() {
		return this.#isLocked.getValue();
	}
	
	public setIsLocked(isLocked: boolean) {
		this.#isLocked.setValue(isLocked);
	}

    public getIsLockedBySelf() {
		return this.#isLockedBySelf.getValue();
	}
	
	public setIsLockedBySelf(isLockedBySelf: boolean) {
		this.#isLockedBySelf.setValue(isLockedBySelf);
	}

    public getLockedByName() {
		return this.#lockedByName.getValue();
	}
	
	public setLockedByName(lockedBy: string) {
		this.#lockedByName.setValue(lockedBy);
	}
}

// Declare a api export, so Extension Registry can initialize this class:
export const api = ContentLockWorkspaceContext;

// Declare a Context Token that other elements can use to request the WorkspaceContextCounter:
export const CONTENTLOCK_WORKSPACE_CONTEXT = new UmbContextToken<ContentLockWorkspaceContext>('UmbWorkspaceContext', 'contentlock.workspacecontext');

Expected result / actual result

For the node name to not be editable and for the label towards the end of the input field to have the read only text like it did with readOnlyGuard/readOnlyState

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions