Merge pull request #2474 from umbraco/v15/feature/disable-readonly-languages-when-unpublishing
Feature: Add read only tag for read only languages in app language select
This commit is contained in:
@@ -901,6 +901,7 @@ export default {
|
||||
avatar: 'Avatar til',
|
||||
header: 'Overskrift',
|
||||
systemField: 'system felt',
|
||||
readOnly: 'Skrivebeskyttet',
|
||||
restore: 'Genskab',
|
||||
generic: 'Generic',
|
||||
media: 'Media',
|
||||
|
||||
@@ -936,6 +936,7 @@ export default {
|
||||
skipToMenu: 'Skip to menu',
|
||||
skipToContent: 'Skip to content',
|
||||
restore: 'Restore',
|
||||
readOnly: 'Read-only',
|
||||
newVersionAvailable: 'New version available',
|
||||
},
|
||||
colors: {
|
||||
|
||||
@@ -47,6 +47,7 @@ export default {
|
||||
notify: 'Notifications',
|
||||
protect: 'Public Access',
|
||||
publish: 'Publish',
|
||||
readOnly: 'Read-only',
|
||||
refreshNode: 'Reload',
|
||||
remove: 'Remove',
|
||||
rename: 'Rename',
|
||||
|
||||
@@ -307,7 +307,9 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement {
|
||||
|
||||
#renderReadOnlyTag(culture?: string | null) {
|
||||
if (!culture) return nothing;
|
||||
return this.#isReadOnly(culture) ? html`<uui-tag look="secondary">Read-only</uui-tag>` : nothing;
|
||||
return this.#isReadOnly(culture)
|
||||
? html`<uui-tag look="secondary">${this.localize.term('general_readOnly')}</uui-tag>`
|
||||
: nothing;
|
||||
}
|
||||
|
||||
#renderSplitViewButton(variant: UmbDocumentVariantOptionModel) {
|
||||
|
||||
@@ -2,8 +2,18 @@ import { UmbLanguageCollectionRepository } from '../collection/index.js';
|
||||
import type { UmbLanguageDetailModel } from '../types.js';
|
||||
import { type UmbAppLanguageContext, UMB_APP_LANGUAGE_CONTEXT } from '../global-contexts/index.js';
|
||||
import type { UUIMenuItemEvent, UUIPopoverContainerElement } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { css, html, customElement, state, repeat, ifDefined, query } from '@umbraco-cms/backoffice/external/lit';
|
||||
import {
|
||||
css,
|
||||
html,
|
||||
customElement,
|
||||
state,
|
||||
repeat,
|
||||
ifDefined,
|
||||
query,
|
||||
nothing,
|
||||
} from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { UMB_CURRENT_USER_CONTEXT } from '@umbraco-cms/backoffice/current-user';
|
||||
|
||||
@customElement('umb-app-language-select')
|
||||
export class UmbAppLanguageSelectElement extends UmbLitElement {
|
||||
@@ -16,6 +26,9 @@ export class UmbAppLanguageSelectElement extends UmbLitElement {
|
||||
@state()
|
||||
private _appLanguage?: UmbLanguageDetailModel;
|
||||
|
||||
@state()
|
||||
private _appLanguageIsReadOnly = false;
|
||||
|
||||
@state()
|
||||
private _isOpen = false;
|
||||
|
||||
@@ -23,6 +36,12 @@ export class UmbAppLanguageSelectElement extends UmbLitElement {
|
||||
#appLanguageContext?: UmbAppLanguageContext;
|
||||
#languagesObserver?: any;
|
||||
|
||||
#currentUserAllowedLanguages?: Array<string>;
|
||||
#currentUserHasAccessToAllLanguages?: boolean;
|
||||
|
||||
@state()
|
||||
_disallowedLanguages: Array<UmbLanguageDetailModel> = [];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
@@ -30,6 +49,29 @@ export class UmbAppLanguageSelectElement extends UmbLitElement {
|
||||
this.#appLanguageContext = instance;
|
||||
this.#observeAppLanguage();
|
||||
});
|
||||
|
||||
this.consumeContext(UMB_CURRENT_USER_CONTEXT, (context) => {
|
||||
this.observe(context.languages, (languages) => {
|
||||
this.#currentUserAllowedLanguages = languages;
|
||||
this.#checkForLanguageAccess();
|
||||
});
|
||||
|
||||
this.observe(context.hasAccessToAllLanguages, (hasAccessToAllLanguages) => {
|
||||
this.#currentUserHasAccessToAllLanguages = hasAccessToAllLanguages;
|
||||
this.#checkForLanguageAccess();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#checkForLanguageAccess() {
|
||||
// find all disallowed languages
|
||||
this._disallowedLanguages = this._languages?.filter((language) => {
|
||||
if (this.#currentUserHasAccessToAllLanguages) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !this.#currentUserAllowedLanguages?.includes(language.unique);
|
||||
});
|
||||
}
|
||||
|
||||
async #observeAppLanguage() {
|
||||
@@ -38,6 +80,10 @@ export class UmbAppLanguageSelectElement extends UmbLitElement {
|
||||
this.observe(this.#appLanguageContext.appLanguage, (language) => {
|
||||
this._appLanguage = language;
|
||||
});
|
||||
|
||||
this.observe(this.#appLanguageContext.appLanguageReadOnlyState.isReadOnly, (isReadOnly) => {
|
||||
this._appLanguageIsReadOnly = isReadOnly;
|
||||
});
|
||||
}
|
||||
|
||||
async #observeLanguages() {
|
||||
@@ -46,6 +92,7 @@ export class UmbAppLanguageSelectElement extends UmbLitElement {
|
||||
// TODO: listen to changes
|
||||
if (data) {
|
||||
this._languages = data.items;
|
||||
this.#checkForLanguageAccess();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +130,11 @@ export class UmbAppLanguageSelectElement extends UmbLitElement {
|
||||
|
||||
#renderTrigger() {
|
||||
return html`<button id="toggle" popovertarget="dropdown-popover">
|
||||
${this._appLanguage?.name} <uui-symbol-expand .open=${this._isOpen}></uui-symbol-expand>
|
||||
<span
|
||||
>${this._appLanguage?.name}
|
||||
${this._appLanguageIsReadOnly ? this.#renderReadOnlyTag(this._appLanguage?.unique) : nothing}</span
|
||||
>
|
||||
<uui-symbol-expand .open=${this._isOpen}></uui-symbol-expand>
|
||||
</button>`;
|
||||
}
|
||||
|
||||
@@ -98,13 +149,25 @@ export class UmbAppLanguageSelectElement extends UmbLitElement {
|
||||
label=${ifDefined(language.name)}
|
||||
@click-label=${this.#onLabelClick}
|
||||
data-unique=${ifDefined(language.unique)}
|
||||
?active=${language.unique === this._appLanguage?.unique}></uui-menu-item>
|
||||
?active=${language.unique === this._appLanguage?.unique}>
|
||||
${this.#isLanguageReadOnly(language.unique) ? this.#renderReadOnlyTag(language.unique) : nothing}
|
||||
</uui-menu-item>
|
||||
`,
|
||||
)}
|
||||
</umb-popover-layout>
|
||||
</uui-popover-container>`;
|
||||
}
|
||||
|
||||
#isLanguageReadOnly(culture?: string) {
|
||||
if (!culture) return false;
|
||||
return this._disallowedLanguages.find((language) => language.unique === culture) ? true : false;
|
||||
}
|
||||
|
||||
#renderReadOnlyTag(culture?: string) {
|
||||
if (!culture) return nothing;
|
||||
return html`<uui-tag slot="badge" look="secondary">${this.localize.term('general_readOnly')}</uui-tag>`;
|
||||
}
|
||||
|
||||
static override styles = [
|
||||
css`
|
||||
:host {
|
||||
|
||||
@@ -6,30 +6,35 @@ import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
|
||||
import { UMB_AUTH_CONTEXT } from '@umbraco-cms/backoffice/auth';
|
||||
import { UmbReadOnlyStateManager } from '@umbraco-cms/backoffice/utils';
|
||||
import { UMB_CURRENT_USER_CONTEXT } from '@umbraco-cms/backoffice/current-user';
|
||||
|
||||
// TODO: Make a store for the App Languages.
|
||||
// TODO: Implement default language end-point, in progress at backend team, so we can avoid getting all languages.
|
||||
export class UmbAppLanguageContext extends UmbContextBase<UmbAppLanguageContext> implements UmbApi {
|
||||
#languageCollectionRepository: UmbLanguageCollectionRepository;
|
||||
#languages = new UmbArrayState<UmbLanguageDetailModel>([], (x) => x.unique);
|
||||
moreThanOneLanguage = this.#languages.asObservablePart((x) => x.length > 1);
|
||||
|
||||
#appLanguage = new UmbObjectState<UmbLanguageDetailModel | undefined>(undefined);
|
||||
appLanguage = this.#appLanguage.asObservable();
|
||||
public readonly appLanguage = this.#appLanguage.asObservable();
|
||||
public readonly appLanguageCulture = this.#appLanguage.asObservablePart((x) => x?.unique);
|
||||
|
||||
appLanguageCulture = this.#appLanguage.asObservablePart((x) => x?.unique);
|
||||
public readonly appLanguageReadOnlyState = new UmbReadOnlyStateManager(this);
|
||||
|
||||
appDefaultLanguage = createObservablePart(this.#languages.asObservable(), (languages) =>
|
||||
public readonly appDefaultLanguage = createObservablePart(this.#languages.asObservable(), (languages) =>
|
||||
languages.find((language) => language.isDefault),
|
||||
);
|
||||
|
||||
getAppCulture() {
|
||||
return this.#appLanguage.getValue()?.unique;
|
||||
}
|
||||
public readonly moreThanOneLanguage = this.#languages.asObservablePart((x) => x.length > 1);
|
||||
|
||||
#languageCollectionRepository = new UmbLanguageCollectionRepository(this);
|
||||
#currentUserAllowedLanguages: Array<string> = [];
|
||||
#currentUserHasAccessToAllLanguages = false;
|
||||
|
||||
#readOnlyStateIdentifier = 'UMB_LANGUAGE_PERMISSION_';
|
||||
#localStorageKey = 'umb:appLanguage';
|
||||
|
||||
constructor(host: UmbControllerHost) {
|
||||
super(host, UMB_APP_LANGUAGE_CONTEXT);
|
||||
this.#languageCollectionRepository = new UmbLanguageCollectionRepository(this);
|
||||
|
||||
// TODO: We need to ensure this request is called every time the user logs in, but this should be done somewhere across the app and not here [JOV]
|
||||
this.consumeContext(UMB_AUTH_CONTEXT, (authContext) => {
|
||||
@@ -38,12 +43,46 @@ export class UmbAppLanguageContext extends UmbContextBase<UmbAppLanguageContext>
|
||||
this.#observeLanguages();
|
||||
});
|
||||
});
|
||||
|
||||
this.consumeContext(UMB_CURRENT_USER_CONTEXT, (context) => {
|
||||
this.observe(context.languages, (languages) => {
|
||||
this.#currentUserAllowedLanguages = languages || [];
|
||||
this.#setIsReadOnly();
|
||||
});
|
||||
|
||||
this.observe(context.hasAccessToAllLanguages, (hasAccessToAllLanguages) => {
|
||||
this.#currentUserHasAccessToAllLanguages = hasAccessToAllLanguages || false;
|
||||
this.#setIsReadOnly();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getAppCulture() {
|
||||
return this.#appLanguage.getValue()?.unique;
|
||||
}
|
||||
|
||||
setLanguage(unique: string) {
|
||||
const languages = this.#languages.getValue();
|
||||
const language = languages.find((x) => x.unique === unique);
|
||||
// clear the previous read-only state
|
||||
const appLanguage = this.#appLanguage.getValue();
|
||||
if (appLanguage?.unique) {
|
||||
this.appLanguageReadOnlyState.removeState(this.#readOnlyStateIdentifier + appLanguage.unique);
|
||||
}
|
||||
|
||||
// find the language
|
||||
const language = this.#findLanguage(unique);
|
||||
|
||||
if (!language) {
|
||||
throw new Error(`Language with unique ${unique} not found`);
|
||||
}
|
||||
|
||||
// set the new language
|
||||
this.#appLanguage.update(language);
|
||||
|
||||
// store the new language in local storage
|
||||
localStorage.setItem(this.#localStorageKey, language?.unique);
|
||||
|
||||
// set the new read-only state
|
||||
this.#setIsReadOnly();
|
||||
}
|
||||
|
||||
async #observeLanguages() {
|
||||
@@ -61,6 +100,17 @@ export class UmbAppLanguageContext extends UmbContextBase<UmbAppLanguageContext>
|
||||
}
|
||||
|
||||
#initAppLanguage() {
|
||||
// get the selected language from local storage
|
||||
const uniqueFromLocalStorage = localStorage.getItem(this.#localStorageKey);
|
||||
|
||||
if (uniqueFromLocalStorage) {
|
||||
const language = this.#findLanguage(uniqueFromLocalStorage);
|
||||
if (language) {
|
||||
this.setLanguage(language.unique);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const defaultLanguage = this.#languages.getValue().find((x) => x.isDefault);
|
||||
// TODO: do we always have a default language?
|
||||
// do we always get the default language on the first request, or could it be on page 2?
|
||||
@@ -68,6 +118,37 @@ export class UmbAppLanguageContext extends UmbContextBase<UmbAppLanguageContext>
|
||||
if (!defaultLanguage?.unique) return;
|
||||
this.setLanguage(defaultLanguage.unique);
|
||||
}
|
||||
|
||||
#findLanguage(unique: string) {
|
||||
return this.#languages.getValue().find((x) => x.unique === unique);
|
||||
}
|
||||
|
||||
#setIsReadOnly() {
|
||||
const appLanguage = this.#appLanguage.getValue();
|
||||
|
||||
if (!appLanguage) {
|
||||
this.appLanguageReadOnlyState.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
const unique = this.#readOnlyStateIdentifier + appLanguage.unique;
|
||||
this.appLanguageReadOnlyState.removeState(unique);
|
||||
|
||||
if (this.#currentUserHasAccessToAllLanguages) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isReadOnly = !this.#currentUserAllowedLanguages.includes(appLanguage.unique);
|
||||
|
||||
if (isReadOnly) {
|
||||
const readOnlyState = {
|
||||
unique,
|
||||
message: 'You do not have permission to edit to this culture',
|
||||
};
|
||||
|
||||
this.appLanguageReadOnlyState.addState(readOnlyState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default export to enable this as a globalContext extension js:
|
||||
|
||||
Reference in New Issue
Block a user