Merge remote-tracking branch 'origin/main' into feature/dictionary-export-import

This commit is contained in:
Lone Iversen
2023-11-15 19:22:42 +01:00
39 changed files with 331 additions and 236 deletions

View File

@@ -87,7 +87,7 @@ h1 {
path: 'Folder 1/Stylesheet File 3.css',
name: 'Stylesheet File 3.css',
type: 'stylesheet',
hasChildren: true,
hasChildren: false,
isFolder: false,
content: `h1 {
color: pink;
@@ -225,9 +225,10 @@ ${rule.selector} {
}
insertStyleSheet(item: CreateTextFileViewModelBaseModel) {
const parentPath = item.parentPath ? `${item.parentPath}/` : '';
const newItem: StylesheetDBItem = {
...item,
path: `${item.parentPath}/${item.name}.css`,
path: `${parentPath}${item.name}`,
isFolder: false,
hasChildren: false,
type: 'stylesheet',
@@ -238,7 +239,6 @@ ${rule.selector} {
return newItem;
}
insert(item: StylesheetDBItem) {
const exits = this.data.find((i) => i.path === item.path);

View File

@@ -39,8 +39,8 @@ const detailHandlers = [
const response = umbStylesheetData.getStylesheet(path);
return res(ctx.status(200), ctx.json(response));
}),
rest.post(umbracoPath('/stylesheet'), (req, res, ctx) => {
const requestBody = req.json() as CreateTextFileViewModelBaseModel;
rest.post(umbracoPath('/stylesheet'), async (req, res, ctx) => {
const requestBody = (await req.json()) as CreateTextFileViewModelBaseModel;
if (!requestBody) return res(ctx.status(400, 'no body found'));
const response = umbStylesheetData.insertStyleSheet(requestBody);
return res(ctx.status(200), ctx.json(response));
@@ -53,7 +53,7 @@ const detailHandlers = [
return res(ctx.status(200), ctx.json(response));
}),
rest.put(umbracoPath('/stylesheet'), async (req, res, ctx) => {
const requestBody = await req.json() as UpdateStylesheetRequestModel;
const requestBody = (await req.json()) as UpdateStylesheetRequestModel;
if (!requestBody) return res(ctx.status(400, 'no body found'));
umbStylesheetData.updateData(requestBody);
return res(ctx.status(200));
@@ -75,7 +75,6 @@ const detailHandlers = [
}),
];
const rulesHandlers = [
rest.post(umbracoPath('/stylesheet/rich-text/extract-rules'), async (req, res, ctx) => {
const requestBody = req.json() as ExtractRichTextStylesheetRulesRequestModel;

View File

@@ -1,4 +1,4 @@
import { UmbEntityTreeRepositoryBase } from '../../tree/entity-tree.repository.js';
import { UmbTreeRepositoryBase } from '../../tree/tree-repository-base.js';
import { DATA_TYPE_ROOT_ENTITY_TYPE } from '../entities.js';
import { UmbDataTypeTreeServerDataSource } from './data-type.tree.server.data.js';
import { UMB_DATA_TYPE_TREE_STORE_CONTEXT } from './data-type.tree.store.js';
@@ -7,7 +7,7 @@ import { type UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbApi } from '@umbraco-cms/backoffice/extension-api';
export class UmbDataTypeTreeRepository
extends UmbEntityTreeRepositoryBase<UmbDataTypeTreeItemModel, UmbDataTypeTreeRootModel>
extends UmbTreeRepositoryBase<UmbDataTypeTreeItemModel, UmbDataTypeTreeRootModel>
implements UmbApi
{
constructor(host: UmbControllerHost) {

View File

@@ -1,4 +1,4 @@
import { UmbTextStyles } from "@umbraco-cms/backoffice/style";
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { css, html, nothing, customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
import type { UmbRoute, UmbRouterSlotChangeEvent, UmbRouterSlotInitEvent } from '@umbraco-cms/backoffice/router';
import {
@@ -110,8 +110,8 @@ export class UmbSectionMainViewElement extends UmbLitElement {
const dashboardPath = this.#constructDashboardPath(dashboard);
return html`
<uui-tab
.label="${dashboardName}"
href="${this._routerPath}/${dashboardPath}"
label="${dashboardName}"
?active="${this._activePath === dashboardPath}"></uui-tab>
`;
})}
@@ -129,8 +129,8 @@ export class UmbSectionMainViewElement extends UmbLitElement {
const viewPath = this.#constructViewPath(view);
return html`
<uui-tab
.label="${viewName}"
href="${this._routerPath}/${viewPath}"
label="${viewName}"
?active="${this._activePath === viewPath}">
<uui-icon slot="icon" name=${view.meta.icon}></uui-icon>
${viewName}

View File

@@ -0,0 +1,31 @@
import { UmbStoreBase } from './store-base.js';
import { UmbItemStore } from './item-store.interface.js';
import type { FileItemResponseModelBaseModel } from '@umbraco-cms/backoffice/backend-api';
import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api';
import { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
/**
* @export
* @class UmbFileSystemItemStore
* @extends {UmbStoreBase}
* @description - Data Store for File system items
*/
export class UmbFileSystemItemStore<T extends FileItemResponseModelBaseModel>
extends UmbStoreBase<T>
implements UmbItemStore<T>
{
constructor(host: UmbControllerHost, storeAlias: string) {
super(host, storeAlias, new UmbArrayState<T>([], (x) => x.path));
}
/**
* Return an observable to observe file system items
* @param {Array<string>} paths
* @return {*}
* @memberof UmbFileSystemItemStore
*/
items(paths: Array<string>) {
return this._data.asObservablePart((items) => items.filter((item) => paths.includes(item.path ?? '')));
}
}

View File

@@ -3,3 +3,4 @@ export * from './store-base.js';
export * from './store.interface.js';
export * from './store.js';
export * from './entity-item.store.js';
export * from './file-system-item.store.js';

View File

@@ -31,9 +31,11 @@ export class UmbFileSystemTreeStore
* @memberof UmbFileSystemTreeStore
*/
childrenOf(parentPath: string | null) {
return this._data.asObservablePart((items) =>
items.filter((item) => item.path?.startsWith(parentPath + '/') || parentPath === null),
);
if (parentPath === null) {
return this.rootItems;
}
return this._data.asObservablePart((items) => items.filter((item) => item.path?.startsWith(parentPath + '/')));
}
/**

View File

@@ -13,4 +13,4 @@ export * from './tree-store.interface.js';
export * from './entity-tree-store.js';
export * from './file-system-tree.store.js';
export { UmbEntityTreeRepositoryBase } from './entity-tree.repository.js';
export { UmbTreeRepositoryBase } from './tree-repository-base.js';

View File

@@ -7,7 +7,7 @@ import { type UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbApi } from '@umbraco-cms/backoffice/extension-api';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
export class UmbEntityTreeRepositoryBase<
export class UmbTreeRepositoryBase<
TreeItemType extends UmbEntityTreeItemModel,
TreeRootType extends UmbEntityTreeRootModel,
>
@@ -34,7 +34,7 @@ export class UmbEntityTreeRepositoryBase<
/**
* Request the tree root item
* @return {*}
* @memberof UmbEntityTreeRepositoryBase
* @memberof UmbTreeRepositoryBase
*/
async requestTreeRoot() {
if (!this.#treeSource.getTreeRoot?.()) {
@@ -47,7 +47,7 @@ export class UmbEntityTreeRepositoryBase<
/**
* Requests root items of a tree
* @return {*}
* @memberof UmbEntityTreeRepositoryBase
* @memberof UmbTreeRepositoryBase
*/
async requestRootTreeItems() {
await this._init;
@@ -63,27 +63,27 @@ export class UmbEntityTreeRepositoryBase<
/**
* Requests tree items of a given parent
* @param {(string | null)} parentId
* @param {(string | null)} parentUnique
* @return {*}
* @memberof UmbEntityTreeRepositoryBase
* @memberof UmbTreeRepositoryBase
*/
async requestTreeItemsOf(parentId: string | null) {
if (parentId === undefined) throw new Error('Parent id is missing');
async requestTreeItemsOf(parentUnique: string | null) {
if (parentUnique === undefined) throw new Error('Parent unique is missing');
await this._init;
const { data, error } = await this.#treeSource.getChildrenOf(parentId);
const { data, error } = await this.#treeSource.getChildrenOf(parentUnique);
if (data) {
this._treeStore!.appendItems(data.items);
}
return { data, error, asObservable: () => this._treeStore!.childrenOf(parentId) };
return { data, error, asObservable: () => this._treeStore!.childrenOf(parentUnique) };
}
/**
* Returns a promise with an observable of tree root items
* @return {*}
* @memberof UmbEntityTreeRepositoryBase
* @memberof UmbTreeRepositoryBase
*/
async rootTreeItems() {
await this._init;
@@ -92,13 +92,13 @@ export class UmbEntityTreeRepositoryBase<
/**
* Returns a promise with an observable of children items of a given parent
* @param {(string | null)} parentId
* @param {(string | null)} parentUnique
* @return {*}
* @memberof UmbEntityTreeRepositoryBase
* @memberof UmbTreeRepositoryBase
*/
async treeItemsOf(parentId: string | null) {
if (parentId === undefined) throw new Error('Parent id is missing');
async treeItemsOf(parentUnique: string | null) {
if (parentUnique === undefined) throw new Error('Parent unique is missing');
await this._init;
return this._treeStore!.childrenOf(parentId);
return this._treeStore!.childrenOf(parentUnique);
}
}

View File

@@ -13,17 +13,26 @@ export interface UmbTreeRepository<
error?: ProblemDetails;
}>;
requestTreeItemsOf: (parentUnique: string | null) => Promise<{
data?: UmbPagedData<TreeItemType>;
error?: ProblemDetails;
asObservable?: () => Observable<TreeItemType[]>;
}>;
treeItemsOf: (parentUnique: string | null) => Promise<Observable<TreeItemType[]>>;
/* TODO: remove this. It is not used client side.
Logic to call the root endpoint should be in the data source
because it is a server decision to split them
*/
requestRootTreeItems: () => Promise<{
data?: UmbPagedData<TreeItemType>;
error?: ProblemDetails;
asObservable?: () => Observable<TreeItemType[]>;
}>;
requestTreeItemsOf: (parentUnique: string | null) => Promise<{
data?: UmbPagedData<TreeItemType>;
error?: ProblemDetails;
asObservable?: () => Observable<TreeItemType[]>;
}>;
// TODO: remove
rootTreeItems: () => Promise<Observable<TreeItemType[]>>;
// TODO: remove this when all repositories are migrated to the new interface items interface
requestItemsLegacy?: (uniques: string[]) => Promise<{
@@ -32,10 +41,6 @@ export interface UmbTreeRepository<
asObservable?: () => Observable<any[]>;
}>;
rootTreeItems: () => Promise<Observable<TreeItemType[]>>;
treeItemsOf: (parentUnique: string | null) => Promise<Observable<TreeItemType[]>>;
// TODO: remove this when all repositories are migrated to the new items interface
itemsLegacy?: (uniques: string[]) => Promise<Observable<any[]>>;
}

View File

@@ -1,54 +1,16 @@
import { UmbDictionaryRepository } from '../../repository/dictionary.repository.js';
import { UmbTextStyles } from "@umbraco-cms/backoffice/style";
import { UmbSectionSidebarContext, UMB_SECTION_SIDEBAR_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/section';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
import {
UmbModalManagerContext,
UMB_MODAL_MANAGER_CONTEXT_TOKEN,
UMB_CREATE_DICTIONARY_MODAL,
} from '@umbraco-cms/backoffice/modal';
export default class UmbCreateDictionaryEntityAction extends UmbEntityActionBase<UmbDictionaryRepository> {
static styles = [UmbTextStyles];
#modalContext?: UmbModalManagerContext;
#sectionSidebarContext!: UmbSectionSidebarContext;
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) {
super(host, repositoryAlias, unique);
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT_TOKEN, (instance) => {
this.#modalContext = instance;
});
this.consumeContext(UMB_SECTION_SIDEBAR_CONTEXT_TOKEN, (instance) => {
this.#sectionSidebarContext = instance;
});
}
async execute() {
// TODO: what to do if modal service is not available?
if (!this.#modalContext) return;
if (!this.repository) return;
// TODO: how can we get the current entity detail in the modal? Passing the observable
// feels a bit hacky. Works, but hacky.
const modalContext = this.#modalContext?.open(UMB_CREATE_DICTIONARY_MODAL, {
parentId: this.unique,
parentName: this.#sectionSidebarContext.headline,
});
const { name, parentId } = await modalContext.onSubmit();
if (!name || parentId === undefined) return;
const { data: url } = await this.repository.create({ name, parentId });
if (!url) return;
//TODO: Why do we need to extract the id like this?
const id = url.substring(url.lastIndexOf('/') + 1);
history.pushState({}, '', `/section/dictionary/workspace/dictionary-item/edit/${id}`);
history.pushState({}, '', `/section/dictionary/workspace/dictionary-item/create/${this.unique ?? 'null'}`);
}
}

View File

@@ -111,6 +111,20 @@ export class UmbDictionaryRepository
return { data, error, asObservable: () => this.#treeStore!.items(ids) };
}
async requestItems(ids: Array<string>) {
// TODO: There is a bug where the item gets removed from the tree before we confirm the delete via the modal. It doesn't delete the item unless we confirm the delete.
if (!ids) throw new Error('Dictionary Ids are missing');
await this.#init;
const { data, error } = await this.#treeSource.getItems(ids);
if (data) {
this.#treeStore?.appendItems(data);
}
return { data, error, asObservable: () => this.#treeStore!.items(ids) };
}
async rootTreeItems() {
await this.#init;
return this.#treeStore!.rootItems;

View File

@@ -68,9 +68,8 @@ export class UmbDictionaryWorkspaceContext
const { data } = await this.repository.createScaffold(parentId);
if (!data) return;
this.setIsNew(true);
// TODO: This is a hack to get around the fact that the data is not typed correctly.
// Create and response models are different. We need to look into this.
this.#data.next(data as unknown as DictionaryItemResponseModel);
this.#data.next(data as DictionaryItemResponseModel);
}
async save() {

View File

@@ -26,7 +26,6 @@ export class UmbWorkspaceDictionaryElement extends UmbLitElement {
component: () => this.#element,
setup: async (_component, info) => {
const parentId = info.match.params.parentId === 'null' ? null : info.match.params.parentId;
await this.#workspaceContext.create(parentId);
new UmbWorkspaceIsNewRedirectController(

View File

@@ -2,12 +2,12 @@ import { DOCUMENT_TYPE_ROOT_ENTITY_TYPE } from '../index.js';
import { UmbDocumentTypeTreeServerDataSource } from './document-type.tree.server.data-source.js';
import { UMB_DOCUMENT_TYPE_TREE_STORE_CONTEXT } from './document-type.tree.store.js';
import { UmbDocumentTypeTreeItemModel, UmbDocumentTypeTreeRootModel } from './types.js';
import { UmbEntityTreeRepositoryBase } from '@umbraco-cms/backoffice/tree';
import { UmbTreeRepositoryBase } from '@umbraco-cms/backoffice/tree';
import { type UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbApi } from '@umbraco-cms/backoffice/extension-api';
export class UmbDocumentTypeTreeRepository
extends UmbEntityTreeRepositoryBase<UmbDocumentTypeTreeItemModel, UmbDocumentTypeTreeRootModel>
extends UmbTreeRepositoryBase<UmbDocumentTypeTreeItemModel, UmbDocumentTypeTreeRootModel>
implements UmbApi
{
constructor(host: UmbControllerHost) {

View File

@@ -1,15 +0,0 @@
export const STYLESHEET_ENTITY_TYPE = 'stylesheet';
export const STYLESHEET_ROOT_ENTITY_TYPE = 'stylesheet-root';
export const STYLESHEET_FOLDER_ENTITY_TYPE = 'stylesheet-folder';
export const STYLESHEET_FOLDER_EMPTY_ENTITY_TYPE = 'stylesheet-folder-empty';
export const STYLESHEET_REPOSITORY_ALIAS = 'Umb.Repository.Stylesheet';
export const STYLESHEET_TREE_ALIAS = 'Umb.Tree.Stylesheet';
export const UMB_STYLESHEET_TREE_STORE_CONTEXT_TOKEN_ALIAS = 'Umb.Store.Stylesheet.Tree';
export const UMB_STYLESHEET_STORE_CONTEXT_TOKEN_ALIAS = 'Umb.Store.Stylesheet';
export const STYLESHEET_STORE_ALIAS = 'Umb.Store.Stylesheet';
export const STYLESHEET_TREE_STORE_ALIAS = 'Umb.Store.StylesheetTree';

View File

@@ -7,6 +7,11 @@ export class UmbCreateRTFStylesheetAction<T extends { copy(): Promise<void> }> e
}
async execute() {
if (this.unique !== null) {
// Note: %2F is a slash (/)
this.unique = this.unique.replace(/\//g, '%2F');
}
history.pushState(
null,
'',

View File

@@ -7,6 +7,11 @@ export class UmbCreateStylesheetAction<T extends { copy(): Promise<void> }> exte
}
async execute() {
if (this.unique !== null) {
// Note: %2F is a slash (/)
this.unique = this.unique.replace(/\//g, '%2F');
}
history.pushState(null, '', `section/settings/workspace/stylesheet/create/${this.unique ?? 'null'}/view/code`);
}
}

View File

@@ -1,10 +1,11 @@
import {
STYLESHEET_ENTITY_TYPE,
STYLESHEET_FOLDER_EMPTY_ENTITY_TYPE,
STYLESHEET_FOLDER_ENTITY_TYPE,
STYLESHEET_REPOSITORY_ALIAS,
STYLESHEET_ROOT_ENTITY_TYPE,
} from '../config.js';
UMB_STYLESHEET_ENTITY_TYPE,
UMB_STYLESHEET_FOLDER_EMPTY_ENTITY_TYPE,
UMB_STYLESHEET_FOLDER_ENTITY_TYPE,
UMB_STYLESHEET_ROOT_ENTITY_TYPE,
} from '../entity-type.js';
import { UMB_STYLESHEET_REPOSITORY_ALIAS } from '../repository/index.js';
import { UmbCreateRTFStylesheetAction } from './create/create-rtf.action.js';
import { UmbCreateStylesheetAction } from './create/create.action.js';
import {
@@ -26,8 +27,8 @@ const stylesheetActions: Array<ManifestEntityAction> = [
meta: {
icon: 'icon-trash',
label: 'Delete',
repositoryAlias: STYLESHEET_REPOSITORY_ALIAS,
entityTypes: [STYLESHEET_ENTITY_TYPE],
repositoryAlias: UMB_STYLESHEET_REPOSITORY_ALIAS,
entityTypes: [UMB_STYLESHEET_ENTITY_TYPE],
},
},
];
@@ -43,8 +44,12 @@ const stylesheetFolderActions: Array<ManifestEntityAction> = [
meta: {
icon: 'icon-script',
label: 'New stylesheet file',
repositoryAlias: STYLESHEET_REPOSITORY_ALIAS,
entityTypes: [STYLESHEET_FOLDER_ENTITY_TYPE, STYLESHEET_FOLDER_EMPTY_ENTITY_TYPE, STYLESHEET_ROOT_ENTITY_TYPE],
repositoryAlias: UMB_STYLESHEET_REPOSITORY_ALIAS,
entityTypes: [
UMB_STYLESHEET_FOLDER_ENTITY_TYPE,
UMB_STYLESHEET_FOLDER_EMPTY_ENTITY_TYPE,
UMB_STYLESHEET_ROOT_ENTITY_TYPE,
],
},
},
{
@@ -55,8 +60,12 @@ const stylesheetFolderActions: Array<ManifestEntityAction> = [
meta: {
icon: 'icon-script',
label: 'New Rich Text Editor style sheet file',
repositoryAlias: STYLESHEET_REPOSITORY_ALIAS,
entityTypes: [STYLESHEET_FOLDER_ENTITY_TYPE, STYLESHEET_FOLDER_EMPTY_ENTITY_TYPE, STYLESHEET_ROOT_ENTITY_TYPE],
repositoryAlias: UMB_STYLESHEET_REPOSITORY_ALIAS,
entityTypes: [
UMB_STYLESHEET_FOLDER_ENTITY_TYPE,
UMB_STYLESHEET_FOLDER_EMPTY_ENTITY_TYPE,
UMB_STYLESHEET_ROOT_ENTITY_TYPE,
],
},
},
{
@@ -67,8 +76,8 @@ const stylesheetFolderActions: Array<ManifestEntityAction> = [
meta: {
icon: 'icon-trash',
label: 'Remove folder',
repositoryAlias: STYLESHEET_REPOSITORY_ALIAS,
entityTypes: [STYLESHEET_FOLDER_EMPTY_ENTITY_TYPE],
repositoryAlias: UMB_STYLESHEET_REPOSITORY_ALIAS,
entityTypes: [UMB_STYLESHEET_FOLDER_EMPTY_ENTITY_TYPE],
},
},
{
@@ -79,8 +88,12 @@ const stylesheetFolderActions: Array<ManifestEntityAction> = [
meta: {
icon: 'icon-add',
label: 'Create folder',
repositoryAlias: STYLESHEET_REPOSITORY_ALIAS,
entityTypes: [STYLESHEET_FOLDER_EMPTY_ENTITY_TYPE, STYLESHEET_FOLDER_ENTITY_TYPE, STYLESHEET_ROOT_ENTITY_TYPE],
repositoryAlias: UMB_STYLESHEET_REPOSITORY_ALIAS,
entityTypes: [
UMB_STYLESHEET_FOLDER_EMPTY_ENTITY_TYPE,
UMB_STYLESHEET_FOLDER_ENTITY_TYPE,
UMB_STYLESHEET_ROOT_ENTITY_TYPE,
],
},
},
];

View File

@@ -0,0 +1,5 @@
export const UMB_STYLESHEET_ENTITY_TYPE = 'stylesheet';
export const UMB_STYLESHEET_ROOT_ENTITY_TYPE = 'stylesheet-root';
export const UMB_STYLESHEET_FOLDER_ENTITY_TYPE = 'stylesheet-folder';
export const UMB_STYLESHEET_FOLDER_EMPTY_ENTITY_TYPE = 'stylesheet-folder-empty';

View File

@@ -3,3 +3,4 @@ import { StylesheetResponseModel } from '@umbraco-cms/backoffice/backend-api';
export type StylesheetDetails = StylesheetResponseModel;
export * from './repository/index.js';
export { UmbStylesheetTreeRepository } from './tree/index.js';

View File

@@ -1,4 +1,4 @@
import { STYLESHEET_TREE_ALIAS } from '../tree/manifests.js';
import { UMB_STYLESHEET_TREE_ALIAS } from '../tree/manifests.js';
import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry';
const menuItem: ManifestTypes = {
@@ -10,7 +10,7 @@ const menuItem: ManifestTypes = {
meta: {
label: 'Stylesheets',
icon: 'icon-folder',
treeAlias: STYLESHEET_TREE_ALIAS,
treeAlias: UMB_STYLESHEET_TREE_ALIAS,
menus: ['Umb.Menu.Templating'],
},
};

View File

@@ -1 +1,3 @@
export * from './stylesheet.repository.js';
export * from './item/index.js';
export { UMB_STYLESHEET_REPOSITORY_ALIAS } from './manifests.js';

View File

@@ -0,0 +1,3 @@
export { UmbStylesheetItemRepository } from './stylesheet-item.repository.js';
export { UMB_STYLESHEET_ITEM_REPOSITORY_ALIAS, UMB_STYLESHEET_ITEM_STORE_ALIAS } from './manifests.js';
export { UMB_STYLESHEET_ITEM_STORE_CONTEXT } from './stylesheet-item.store.js';

View File

@@ -0,0 +1,22 @@
import { UmbStylesheetItemStore } from './stylesheet-item.store.js';
import { UmbStylesheetItemRepository } from './stylesheet-item.repository.js';
import type { ManifestRepository, ManifestItemStore } from '@umbraco-cms/backoffice/extension-registry';
export const UMB_STYLESHEET_ITEM_REPOSITORY_ALIAS = 'Umb.Repository.Stylesheet.Item';
export const UMB_STYLESHEET_ITEM_STORE_ALIAS = 'Umb.ItemStore.Stylesheet';
const repository: ManifestRepository = {
type: 'repository',
alias: UMB_STYLESHEET_ITEM_REPOSITORY_ALIAS,
name: 'Stylesheet Item Repository',
api: UmbStylesheetItemRepository,
};
const itemStore: ManifestItemStore = {
type: 'itemStore',
alias: 'Umb.ItemStore.Stylesheet',
name: 'Stylesheet Item Store',
api: UmbStylesheetItemStore,
};
export const manifests = [repository, itemStore];

View File

@@ -0,0 +1,11 @@
import { UmbStylesheetItemServerDataSource } from './stylesheet-item.server.data-source.js';
import { UMB_STYLESHEET_ITEM_STORE_CONTEXT } from './stylesheet-item.store.js';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { StylesheetItemResponseModel } from '@umbraco-cms/backoffice/backend-api';
import { UmbItemRepositoryBase } from '@umbraco-cms/backoffice/repository';
export class UmbStylesheetItemRepository extends UmbItemRepositoryBase<StylesheetItemResponseModel> {
constructor(host: UmbControllerHost) {
super(host, UmbStylesheetItemServerDataSource, UMB_STYLESHEET_ITEM_STORE_CONTEXT);
}
}

View File

@@ -0,0 +1,40 @@
import type { UmbItemDataSource } from '@umbraco-cms/backoffice/repository';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources';
import { StylesheetItemResponseModel, StylesheetResource } from '@umbraco-cms/backoffice/backend-api';
/**
* A data source for stylesheet items that fetches data from the server
* @export
* @class UmbStylesheetItemServerDataSource
* @implements {UmbItemDataSource}
*/
export class UmbStylesheetItemServerDataSource implements UmbItemDataSource<StylesheetItemResponseModel> {
#host: UmbControllerHost;
/**
* Creates an instance of UmbStylesheetItemServerDataSource.
* @param {UmbControllerHost} host
* @memberof UmbStylesheetItemServerDataSource
*/
constructor(host: UmbControllerHost) {
this.#host = host;
}
/**
* Fetches the items for the given paths from the server
* @param {Array<string>} paths
* @return {*}
* @memberof UmbStylesheetItemServerDataSource
*/
async getItems(paths: Array<string>) {
if (!paths) throw new Error('Paths are missing');
return tryExecuteAndNotify(
this.#host,
StylesheetResource.getStylesheetItem({
path: paths,
}),
);
}
}

View File

@@ -0,0 +1,24 @@
import type { StylesheetItemResponseModel } from '@umbraco-cms/backoffice/backend-api';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
import { UmbFileSystemItemStore } from '@umbraco-cms/backoffice/store';
/**
* @export
* @class UmbStylesheetItemStore
* @extends {UmbFileSystemItemStore}
* @description - Data Store for Stylesheet items
*/
export class UmbStylesheetItemStore extends UmbFileSystemItemStore<StylesheetItemResponseModel> {
/**
* Creates an instance of UmbStylesheetItemStore.
* @param {UmbControllerHostElement} host
* @memberof UmbStylesheetItemStore
*/
constructor(host: UmbControllerHostElement) {
super(host, UMB_STYLESHEET_ITEM_STORE_CONTEXT.toString());
}
}
export const UMB_STYLESHEET_ITEM_STORE_CONTEXT = new UmbContextToken<UmbStylesheetItemStore>('UmbStylesheetItemStore');

View File

@@ -1,22 +1,14 @@
import { STYLESHEET_REPOSITORY_ALIAS, STYLESHEET_TREE_STORE_ALIAS } from '../config.js';
import { UmbStylesheetRepository } from './stylesheet.repository.js';
import { UmbStylesheetTreeStore } from './stylesheet.tree.store.js';
import { ManifestRepository, ManifestTreeStore } from '@umbraco-cms/backoffice/extension-registry';
import { manifests as itemManifests } from './item/manifests.js';
import { ManifestRepository } from '@umbraco-cms/backoffice/extension-registry';
export const UMB_STYLESHEET_REPOSITORY_ALIAS = 'Umb.Repository.Stylesheet';
const repository: ManifestRepository = {
type: 'repository',
alias: STYLESHEET_REPOSITORY_ALIAS,
alias: UMB_STYLESHEET_REPOSITORY_ALIAS,
name: 'Stylesheet Repository',
api: UmbStylesheetRepository,
};
const treeStore: ManifestTreeStore = {
type: 'treeStore',
alias: STYLESHEET_TREE_STORE_ALIAS,
name: 'Stylesheet Tree Store',
api: UmbStylesheetTreeStore,
};
export const manifests = [treeStore, repository];
export const manifests = [repository, ...itemManifests];

View File

@@ -1,6 +1,4 @@
import { StylesheetDetails } from '../index.js';
import { UmbStylesheetTreeStore, UMB_STYLESHEET_TREE_STORE_CONTEXT_TOKEN } from './stylesheet.tree.store.js';
import { UmbStylesheetTreeServerDataSource } from './sources/stylesheet.tree.server.data.js';
import { UmbStylesheetServerDataSource } from './sources/stylesheet.server.data.js';
import {
StylesheetGetFolderResponse,
@@ -33,34 +31,24 @@ import {
UpdateStylesheetRequestModel,
UpdateTextFileViewModelBaseModel,
} from '@umbraco-cms/backoffice/backend-api';
import type { UmbFileSystemTreeRootModel } from '@umbraco-cms/backoffice/tree';
import { UmbApi } from '@umbraco-cms/backoffice/extension-api';
export class UmbStylesheetRepository
extends UmbBaseController
implements
UmbTreeRepository<FileSystemTreeItemPresentationModel, UmbFileSystemTreeRootModel>,
UmbDetailRepository<CreateStylesheetRequestModel, string, UpdateStylesheetRequestModel, StylesheetDetails>,
UmbFolderRepository,
UmbApi
{
#dataSource;
#treeDataSource;
#treeStore?: UmbStylesheetTreeStore;
#folderDataSource;
#init;
constructor(host: UmbControllerHostElement) {
super(host);
// TODO: figure out how spin up get the correct data source
this.#dataSource = new UmbStylesheetServerDataSource(this);
this.#treeDataSource = new UmbStylesheetTreeServerDataSource(this);
this.#folderDataSource = new UmbStylesheetFolderServerDataSource(this);
this.#init = this.consumeContext(UMB_STYLESHEET_TREE_STORE_CONTEXT_TOKEN, (instance) => {
this.#treeStore = instance;
}).asPromise();
}
//#region FOLDER:
@@ -78,34 +66,34 @@ export class UmbStylesheetRepository
async createFolder(
folderRequest: CreateFolderRequestModel,
): Promise<{ data?: string | undefined; error?: ProblemDetails | undefined }> {
await this.#init;
const req = {
parentPath: folderRequest.parentId,
name: folderRequest.name,
};
const promise = this.#folderDataSource.insert(req);
await promise;
this.requestTreeItemsOf(folderRequest.parentId ? folderRequest.parentId : null);
//this.requestTreeItemsOf(folderRequest.parentId ? folderRequest.parentId : null);
return promise;
}
async requestFolder(
unique: string,
): Promise<{ data?: StylesheetGetFolderResponse | undefined; error?: ProblemDetails | undefined }> {
await this.#init;
return this.#folderDataSource.get(unique);
}
updateFolder(
unique: string,
folder: FolderModelBaseModel,
): Promise<{ data?: FolderModelBaseModel | undefined; error?: ProblemDetails | undefined }> {
throw new Error('Method not implemented.');
}
async deleteFolder(path: string): Promise<{ error?: ProblemDetails | undefined }> {
await this.#init;
const { data } = await this.requestFolder(path);
const promise = this.#folderDataSource.delete(path);
await promise;
this.requestTreeItemsOf(data?.parentPath ? data?.parentPath : null);
//this.requestTreeItemsOf(data?.parentPath ? data?.parentPath : null);
return promise;
}
@@ -122,26 +110,29 @@ export class UmbStylesheetRepository
async requestById(id: string): Promise<DataSourceResponse<TextFileResponseModelBaseModel | undefined>> {
if (!id) throw new Error('id is missing');
await this.#init;
const { data, error } = await this.#dataSource.get(id);
return { data, error };
}
byId(id: string): Promise<Observable<TextFileResponseModelBaseModel | undefined>> {
throw new Error('Method not implemented.');
}
async create(data: CreateTextFileViewModelBaseModel): Promise<DataSourceResponse<string>> {
const promise = this.#dataSource.insert(data);
await promise;
this.requestTreeItemsOf(data.parentPath ? data.parentPath : null);
//this.requestTreeItemsOf(data.parentPath ? data.parentPath : null);
return promise;
}
save(id: string, data: UpdateTextFileViewModelBaseModel): Promise<UmbDataSourceErrorResponse> {
return this.#dataSource.update(id, data);
}
delete(id: string): Promise<UmbDataSourceErrorResponse> {
const promise = this.#dataSource.delete(id);
const parentPath = id.substring(0, id.lastIndexOf('/'));
this.requestTreeItemsOf(parentPath ? parentPath : null);
//this.requestTreeItemsOf(parentPath ? parentPath : null);
return promise;
}
@@ -180,71 +171,4 @@ export class UmbStylesheetRepository
}
//#endregion
//#region TREE:
async requestTreeRoot() {
await this.#init;
const data = {
path: null,
type: 'stylesheet-root',
name: 'Stylesheets',
icon: 'icon-folder',
hasChildren: true,
};
return { data };
}
async requestRootTreeItems() {
await this.#init;
const { data, error } = await this.#treeDataSource.getRootItems();
if (data) {
this.#treeStore?.appendItems(data.items);
}
return { data, error };
}
async requestTreeItemsOf(path: string | null) {
if (path === undefined) throw new Error('Cannot request tree item with missing path');
await this.#init;
const { data, error } = await this.#treeDataSource.getChildrenOf(path);
if (data) {
this.#treeStore!.appendItems(data.items);
}
return { data, error, asObservable: () => this.#treeStore!.childrenOf(path) };
}
async requestItems(paths: Array<string>) {
if (!paths) throw new Error('Paths are missing');
await this.#init;
const { data, error } = await this.#treeDataSource.getItems(paths);
return { data, error };
}
async rootTreeItems() {
await this.#init;
return this.#treeStore!.rootItems;
}
async treeItemsOf(parentPath: string | null) {
if (!parentPath) throw new Error('Parent Path is missing');
await this.#init;
return this.#treeStore!.childrenOf(parentPath);
}
async itemsLegacy(paths: Array<string>) {
if (!paths) throw new Error('Paths are missing');
await this.#init;
return this.#treeStore!.items(paths);
}
//#endregion
}

View File

@@ -0,0 +1 @@
export { UmbStylesheetTreeRepository } from './stylesheet-tree.repository.js';

View File

@@ -1,15 +1,38 @@
import { STYLESHEET_ENTITY_TYPE, STYLESHEET_REPOSITORY_ALIAS } from '../config.js';
import type { ManifestTree, ManifestTreeItem } from '@umbraco-cms/backoffice/extension-registry';
import { UMB_STYLESHEET_ENTITY_TYPE } from '../entity-type.js';
import { UmbStylesheetTreeRepository } from './stylesheet-tree.repository.js';
import { UmbStylesheetTreeStore } from './stylesheet-tree.store.js';
import type {
ManifestRepository,
ManifestTree,
ManifestTreeItem,
ManifestTreeStore,
} from '@umbraco-cms/backoffice/extension-registry';
export const STYLESHEET_TREE_ALIAS = 'Umb.Tree.Stylesheet';
export const UMB_STYLESHEET_TREE_ALIAS = 'Umb.Tree.Stylesheet';
export const UMB_STYLESHEET_TREE_REPOSITORY_ALIAS = 'Umb.Repository.StylesheetTree';
export const UMB_STYLESHEET_TREE_STORE_ALIAS = 'Umb.Store.StylesheetTree';
const treeRepository: ManifestRepository = {
type: 'repository',
alias: UMB_STYLESHEET_TREE_REPOSITORY_ALIAS,
name: 'Stylesheet Tree Repository',
api: UmbStylesheetTreeRepository,
};
const treeStore: ManifestTreeStore = {
type: 'treeStore',
alias: UMB_STYLESHEET_TREE_STORE_ALIAS,
name: 'Stylesheet Tree Store',
api: UmbStylesheetTreeStore,
};
const tree: ManifestTree = {
type: 'tree',
alias: STYLESHEET_TREE_ALIAS,
alias: UMB_STYLESHEET_TREE_ALIAS,
name: 'Stylesheet Tree',
weight: 10,
meta: {
repositoryAlias: STYLESHEET_REPOSITORY_ALIAS,
repositoryAlias: UMB_STYLESHEET_TREE_REPOSITORY_ALIAS,
},
};
@@ -19,8 +42,8 @@ const treeItem: ManifestTreeItem = {
alias: 'Umb.TreeItem.Stylesheet',
name: 'Stylesheet Tree Item',
meta: {
entityTypes: ['stylesheet-root', STYLESHEET_ENTITY_TYPE],
entityTypes: ['stylesheet-root', UMB_STYLESHEET_ENTITY_TYPE],
},
};
export const manifests = [tree, treeItem];
export const manifests = [treeRepository, treeStore, tree, treeItem];

View File

@@ -0,0 +1,22 @@
import { UmbStylesheetTreeServerDataSource } from './stylesheet-tree.server.data-source.js';
import { UMB_STYLESHEET_TREE_STORE_CONTEXT_TOKEN } from './stylesheet-tree.store.js';
import { UmbTreeRepositoryBase } from '@umbraco-cms/backoffice/tree';
import { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
export class UmbStylesheetTreeRepository extends UmbTreeRepositoryBase<any, any> {
constructor(host: UmbControllerHost) {
super(host, UmbStylesheetTreeServerDataSource, UMB_STYLESHEET_TREE_STORE_CONTEXT_TOKEN);
}
async requestTreeRoot() {
const data = {
path: null,
type: 'stylesheet-root',
name: 'Stylesheets',
icon: 'icon-folder',
hasChildren: true,
};
return { data };
}
}

View File

@@ -48,7 +48,11 @@ export class UmbStylesheetWorkspaceEditorElement extends UmbLitElement {
this.#workspaceContext.path,
(path) => {
this._path = path;
this._dirName = this._path?.substring(0, this._path?.lastIndexOf('\\') + 1)?.replace(/\\/g, '/');
if (this._path?.includes('.css')) {
this._dirName = this._path?.substring(0, this._path?.lastIndexOf('\\') + 1)?.replace(/\\/g, '/');
} else {
this._dirName = path + '/';
}
},
'_observeStylesheetPath',
);

View File

@@ -2,11 +2,7 @@ import { UmbStylesheetRepository } from '../repository/stylesheet.repository.js'
import { StylesheetDetails } from '../index.js';
import { UmbSaveableWorkspaceContextInterface, UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
import {
UmbArrayState,
UmbBooleanState,
UmbObjectState
} from '@umbraco-cms/backoffice/observable-api';
import { UmbArrayState, UmbBooleanState, UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
import { loadCodeEditor } from '@umbraco-cms/backoffice/code-editor';
import { RichTextRuleModel, UpdateStylesheetRequestModel } from '@umbraco-cms/backoffice/backend-api';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
@@ -48,10 +44,10 @@ export class UmbStylesheetWorkspaceContext
}
getEntityId() {
const path = this.getData()?.path;
const path = this.getData()?.path?.replace(/\//g, '%2F');
const name = this.getData()?.name;
// TODO: %2F is a slash (/). Should we make it an actual slash in the URL? (%2F for now so that the server can find the correct stylesheet via URL)
// Note: %2F is a slash (/)
return path && name ? `${path}%2F${name}` : name || '';
}

View File

@@ -19,6 +19,7 @@ export class UmbStylesheetWorkspaceElement extends UmbLitElement {
const path = info.match.params.path === 'null' ? null : info.match.params.path;
const serverPath = path === null ? null : serverFilePathFromUrlFriendlyPath(path);
await this.#workspaceContext.create(serverPath);
await this.#workspaceContext.setRules([]);
new UmbWorkspaceIsNewRedirectController(
this,

View File

@@ -47,8 +47,12 @@ export class UmbCurrentUserHeaderAppElement extends UmbLitElement {
render() {
return html`
<uui-button @click=${this._handleUserClick} look="primary" label="${this._currentUser?.name || ''}" compact>
<uui-avatar name="${this._currentUser?.name || ''}"></uui-avatar>
<uui-button
@click=${this._handleUserClick}
look="primary"
label="${this.localize.term('visuallyHiddenTexts_openCloseBackofficeProfileOptions')}"
compact>
<uui-avatar name="${this._currentUser?.name || 'Unknown'}"></uui-avatar>
</uui-button>
`;
}