implement repositories for media
This commit is contained in:
@@ -10,5 +10,5 @@ export interface MetaDashboardCollection {
|
|||||||
pathname: string;
|
pathname: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
entityType: string;
|
entityType: string;
|
||||||
storeAlias: string;
|
repositoryAlias: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ import { UmbMediaTypeDetailStore } from './media/media-types/media-type.detail.s
|
|||||||
import { UmbMediaTypeTreeStore } from './media/media-types/media-type.tree.store';
|
import { UmbMediaTypeTreeStore } from './media/media-types/media-type.tree.store';
|
||||||
import { UmbDocumentDetailStore } from './documents/documents/repository/document.detail.store';
|
import { UmbDocumentDetailStore } from './documents/documents/repository/document.detail.store';
|
||||||
import { UmbDocumentTreeStore } from './documents/documents/repository/document.tree.store';
|
import { UmbDocumentTreeStore } from './documents/documents/repository/document.tree.store';
|
||||||
import { UmbMediaDetailStore } from './media/media/media.detail.store';
|
import { UmbMediaDetailStore } from './media/media/repository/media.detail.store';
|
||||||
import { UmbMediaTreeStore } from './media/media/media.tree.store';
|
import { UmbMediaTreeStore } from './media/media/repository/media.tree.store';
|
||||||
import { UmbMemberTypeDetailStore } from './members/member-types/member-type.detail.store';
|
import { UmbMemberTypeDetailStore } from './members/member-types/member-type.detail.store';
|
||||||
import { UmbMemberTypeTreeStore } from './members/member-types/member-type.tree.store';
|
import { UmbMemberTypeTreeStore } from './members/member-types/member-type.tree.store';
|
||||||
import { UmbMemberGroupStore } from './members/member-groups/member-group.details.store';
|
import { UmbMemberGroupStore } from './members/member-groups/member-group.details.store';
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { UMB_DOCUMENT_TREE_STORE_CONTEXT_TOKEN } from '../repository/document.tree.store';
|
|
||||||
import { UmbDocumentRepository } from '../repository/document.repository';
|
import { UmbDocumentRepository } from '../repository/document.repository';
|
||||||
import type { ManifestTree, ManifestTreeItemAction } from '@umbraco-cms/models';
|
import type { ManifestTree, ManifestTreeItemAction } from '@umbraco-cms/models';
|
||||||
|
|
||||||
@@ -9,8 +8,7 @@ const tree: ManifestTree = {
|
|||||||
alias: treeAlias,
|
alias: treeAlias,
|
||||||
name: 'Documents Tree',
|
name: 'Documents Tree',
|
||||||
meta: {
|
meta: {
|
||||||
storeAlias: UMB_DOCUMENT_TREE_STORE_CONTEXT_TOKEN.toString(),
|
repository: UmbDocumentRepository, // TODO: use alias instead of class
|
||||||
repository: UmbDocumentRepository,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
|
import { manifests as repositoryManifests } from './repository/manifests';
|
||||||
import { manifests as sidebarMenuItemManifests } from './sidebar-menu-item/manifests';
|
import { manifests as sidebarMenuItemManifests } from './sidebar-menu-item/manifests';
|
||||||
import { manifests as treeManifests } from './tree/manifests';
|
import { manifests as treeManifests } from './tree/manifests';
|
||||||
import { manifests as workspaceManifests } from './workspace/manifests';
|
import { manifests as workspaceManifests } from './workspace/manifests';
|
||||||
import { manifests as entityActionsManifests } from './entity-actions/manifests';
|
import { manifests as entityActionsManifests } from './entity-actions/manifests';
|
||||||
|
|
||||||
export const manifests = [
|
export const manifests = [
|
||||||
|
...repositoryManifests,
|
||||||
...sidebarMenuItemManifests,
|
...sidebarMenuItemManifests,
|
||||||
...treeManifests,
|
...treeManifests,
|
||||||
...workspaceManifests,
|
...workspaceManifests,
|
||||||
|
|||||||
@@ -1,107 +0,0 @@
|
|||||||
import type { MediaDetails } from '@umbraco-cms/models';
|
|
||||||
import { UmbContextToken } from '@umbraco-cms/context-api';
|
|
||||||
import { ArrayState } from '@umbraco-cms/observable-api';
|
|
||||||
import { UmbStoreBase, UmbContentStore } from '@umbraco-cms/store';
|
|
||||||
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
|
|
||||||
|
|
||||||
|
|
||||||
export const UMB_MEDIA_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbMediaDetailStore>('UmbMediaDetailStore');
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @export
|
|
||||||
* @class UmbMediaDetailStore
|
|
||||||
* @extends {UmbStoreBase}
|
|
||||||
* @description - Data Store for Media
|
|
||||||
*/
|
|
||||||
export class UmbMediaDetailStore extends UmbStoreBase implements UmbContentStore<MediaDetails> {
|
|
||||||
|
|
||||||
|
|
||||||
#data = new ArrayState<MediaDetails>([], (x) => x.key);
|
|
||||||
|
|
||||||
|
|
||||||
constructor(host: UmbControllerHostInterface) {
|
|
||||||
super(host, UMB_MEDIA_DETAIL_STORE_CONTEXT_TOKEN.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
getByKey(key: string) {
|
|
||||||
// TODO: use backend cli when available.
|
|
||||||
fetch(`/umbraco/management/api/v1/media/details/${key}`)
|
|
||||||
.then((res) => res.json())
|
|
||||||
.then((data) => {
|
|
||||||
this.#data.append(data);
|
|
||||||
});
|
|
||||||
|
|
||||||
return this.#data.getObservablePart((documents) =>
|
|
||||||
documents.find((document) => document.key === key)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getScaffold(entityType: string, parentKey: string | null) {
|
|
||||||
return {
|
|
||||||
key: '',
|
|
||||||
name: '',
|
|
||||||
icon: '',
|
|
||||||
type: '',
|
|
||||||
hasChildren: false,
|
|
||||||
parentKey: '',
|
|
||||||
isTrashed: false,
|
|
||||||
properties: [
|
|
||||||
{
|
|
||||||
alias: '',
|
|
||||||
label: '',
|
|
||||||
description: '',
|
|
||||||
dataTypeKey: '',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
alias: '',
|
|
||||||
value: '',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
} as MediaDetails;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: make sure UI somehow can follow the status of this action.
|
|
||||||
save(data: MediaDetails[]) {
|
|
||||||
// fetch from server and update store
|
|
||||||
// TODO: use Fetcher API.
|
|
||||||
let body: string;
|
|
||||||
|
|
||||||
try {
|
|
||||||
body = JSON.stringify(data);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
return Promise.reject();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: use backend cli when available.
|
|
||||||
return fetch('/umbraco/management/api/v1/media/save', {
|
|
||||||
method: 'POST',
|
|
||||||
body: body,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((res) => res.json())
|
|
||||||
.then((data: Array<MediaDetails>) => {
|
|
||||||
this.#data.append(data);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: how do we handle trashed items?
|
|
||||||
// TODO: How do we make trash available on details and tree store?
|
|
||||||
async trash(keys: Array<string>) {
|
|
||||||
// TODO: use backend cli when available.
|
|
||||||
const res = await fetch('/umbraco/management/api/v1/media/trash', {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify(keys),
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const data = await res.json();
|
|
||||||
this.#data.append(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
import type { Observable } from 'rxjs';
|
|
||||||
import { MediaResource, ContentTreeItem } from '@umbraco-cms/backend-api';
|
|
||||||
import { tryExecuteAndNotify } from '@umbraco-cms/resources';
|
|
||||||
import { UmbContextToken } from '@umbraco-cms/context-api';
|
|
||||||
import { ArrayState } from '@umbraco-cms/observable-api';
|
|
||||||
import { UmbStoreBase, UmbTreeStore } from '@umbraco-cms/store';
|
|
||||||
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
|
|
||||||
|
|
||||||
export const UMB_MEDIA_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbMediaTreeStore>('UmbMediaTreeStore');
|
|
||||||
|
|
||||||
// TODO: Stop using ContentTreeItem
|
|
||||||
export type MediaTreeItem = ContentTreeItem;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @export
|
|
||||||
* @class UmbMediaTreeStore
|
|
||||||
* @extends {UmbStoreBase}
|
|
||||||
* @description - Data Store for Media
|
|
||||||
*/
|
|
||||||
export class UmbMediaTreeStore extends UmbStoreBase implements UmbTreeStore<MediaTreeItem> {
|
|
||||||
|
|
||||||
#data = new ArrayState<MediaTreeItem>([], (x) => x.key);
|
|
||||||
|
|
||||||
constructor(host: UmbControllerHostInterface) {
|
|
||||||
super(host, UMB_MEDIA_TREE_STORE_CONTEXT_TOKEN.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: how do we handle trashed items?
|
|
||||||
// TODO: How do we make trash available on details and tree store?
|
|
||||||
async trash(keys: Array<string>) {
|
|
||||||
// TODO: use backend cli when available.
|
|
||||||
const res = await fetch('/umbraco/management/api/v1/media/trash', {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify(keys),
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const data = await res.json();
|
|
||||||
this.#data.append(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
async move(keys: Array<string>, destination: string) {
|
|
||||||
// TODO: use backend cli when available.
|
|
||||||
const res = await fetch('/umbraco/management/api/v1/media/move', {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify({ keys, destination }),
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const data = await res.json();
|
|
||||||
this.#data.append(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
getTreeRoot(): Observable<Array<MediaTreeItem>> {
|
|
||||||
tryExecuteAndNotify(this._host, MediaResource.getTreeMediaRoot({})).then(({ data }) => {
|
|
||||||
if (data) {
|
|
||||||
// TODO: how do we handle if an item has been removed during this session(like in another tab or by another user)?
|
|
||||||
this.#data.append(data.items);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: how do we handle trashed items?
|
|
||||||
// TODO: remove ignore when we know how to handle trashed items.
|
|
||||||
return this.#data.getObservablePart((items) =>
|
|
||||||
items.filter((item) => item.parentKey === null && !item.isTrashed)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getTreeItemChildren(key: string): Observable<Array<MediaTreeItem>> {
|
|
||||||
tryExecuteAndNotify(
|
|
||||||
this._host,
|
|
||||||
MediaResource.getTreeMediaChildren({
|
|
||||||
parentKey: key,
|
|
||||||
})
|
|
||||||
).then(({ data }) => {
|
|
||||||
if (data) {
|
|
||||||
// TODO: how do we handle if an item has been removed during this session(like in another tab or by another user)?
|
|
||||||
this.#data.append(data.items);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: how do we handle trashed items?
|
|
||||||
// TODO: remove ignore when we know how to handle trashed items.
|
|
||||||
return this.#data.getObservablePart((items) =>
|
|
||||||
items.filter((item) => item.parentKey === key && !item.isTrashed)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getTreeItems(keys: Array<string>): Observable<Array<MediaTreeItem>> {
|
|
||||||
if (keys?.length > 0) {
|
|
||||||
tryExecuteAndNotify(
|
|
||||||
this._host,
|
|
||||||
MediaResource.getTreeMediaItem({
|
|
||||||
key: keys,
|
|
||||||
})
|
|
||||||
).then(({ data }) => {
|
|
||||||
if (data) {
|
|
||||||
// TODO: how do we handle if an item has been removed during this session(like in another tab or by another user)?
|
|
||||||
this.#data.append(data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.#data.getObservablePart((items) => items.filter((item) => keys.includes(item.key ?? '')));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { UmbMediaRepository } from './media.repository';
|
||||||
|
import { ManifestRepository } from 'libs/extensions-registry/repository.models';
|
||||||
|
|
||||||
|
export const DOCUMENT_REPOSITORY_ALIAS = 'Umb.Repository.Media';
|
||||||
|
|
||||||
|
const repository: ManifestRepository = {
|
||||||
|
type: 'repository',
|
||||||
|
alias: DOCUMENT_REPOSITORY_ALIAS,
|
||||||
|
name: 'Media Repository',
|
||||||
|
class: UmbMediaRepository,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const manifests = [repository];
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import type { MediaDetails } from '@umbraco-cms/models';
|
||||||
|
import { UmbContextToken } from '@umbraco-cms/context-api';
|
||||||
|
import { ArrayState } from '@umbraco-cms/observable-api';
|
||||||
|
import { UmbStoreBase } from '@umbraco-cms/store';
|
||||||
|
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @export
|
||||||
|
* @class UmbMediaDetailStore
|
||||||
|
* @extends {UmbStoreBase}
|
||||||
|
* @description - Data Store for Template Details
|
||||||
|
*/
|
||||||
|
export class UmbMediaDetailStore extends UmbStoreBase {
|
||||||
|
#data = new ArrayState<MediaDetails>([], (x) => x.key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance of UmbMediaDetailStore.
|
||||||
|
* @param {UmbControllerHostInterface} host
|
||||||
|
* @memberof UmbMediaDetailStore
|
||||||
|
*/
|
||||||
|
constructor(host: UmbControllerHostInterface) {
|
||||||
|
super(host, UmbMediaDetailStore.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append a media to the store
|
||||||
|
* @param {MediaDetails} media
|
||||||
|
* @memberof UmbMediaDetailStore
|
||||||
|
*/
|
||||||
|
append(media: MediaDetails) {
|
||||||
|
this.#data.append([media]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes media in the store with the given uniques
|
||||||
|
* @param {string[]} uniques
|
||||||
|
* @memberof UmbMediaDetailStore
|
||||||
|
*/
|
||||||
|
remove(uniques: string[]) {
|
||||||
|
this.#data.remove(uniques);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UMB_MEDIA_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbMediaDetailStore>(UmbMediaDetailStore.name);
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
import type { RepositoryTreeDataSource } from '../../../../../libs/repository/repository-tree-data-source.interface';
|
||||||
|
import { MediaTreeServerDataSource } from './sources/media.tree.server.data';
|
||||||
|
import { UmbMediaTreeStore, UMB_MEDIA_TREE_STORE_CONTEXT_TOKEN } from './media.tree.store';
|
||||||
|
import { UmbMediaDetailStore, UMB_MEDIA_DETAIL_STORE_CONTEXT_TOKEN } from './media.detail.store';
|
||||||
|
import { UmbMediaDetailServerDataSource } from './sources/media.detail.server.data';
|
||||||
|
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
|
||||||
|
import { UmbContextConsumerController } from '@umbraco-cms/context-api';
|
||||||
|
import { ProblemDetails } from '@umbraco-cms/backend-api';
|
||||||
|
import type { UmbTreeRepository } from 'libs/repository/tree-repository.interface';
|
||||||
|
import { UmbDetailRepository } from '@umbraco-cms/repository';
|
||||||
|
import type { MediaDetails } from '@umbraco-cms/models';
|
||||||
|
import { UmbNotificationService, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/notification';
|
||||||
|
|
||||||
|
type ItemDetailType = MediaDetails;
|
||||||
|
|
||||||
|
// Move to documentation / JSdoc
|
||||||
|
/* We need to create a new instance of the repository from within the element context. We want the notifications to be displayed in the right context. */
|
||||||
|
// element -> context -> repository -> (store) -> data source
|
||||||
|
// All methods should be async and return a promise. Some methods might return an observable as part of the promise response.
|
||||||
|
export class UmbMediaRepository implements UmbTreeRepository, UmbDetailRepository<ItemDetailType> {
|
||||||
|
#init!: Promise<unknown>;
|
||||||
|
|
||||||
|
#host: UmbControllerHostInterface;
|
||||||
|
|
||||||
|
#treeSource: RepositoryTreeDataSource;
|
||||||
|
#treeStore?: UmbMediaTreeStore;
|
||||||
|
|
||||||
|
#detailDataSource: UmbMediaDetailServerDataSource;
|
||||||
|
#detailStore?: UmbMediaDetailStore;
|
||||||
|
|
||||||
|
#notificationService?: UmbNotificationService;
|
||||||
|
|
||||||
|
constructor(host: UmbControllerHostInterface) {
|
||||||
|
this.#host = host;
|
||||||
|
|
||||||
|
// TODO: figure out how spin up get the correct data source
|
||||||
|
this.#treeSource = new MediaTreeServerDataSource(this.#host);
|
||||||
|
this.#detailDataSource = new UmbMediaDetailServerDataSource(this.#host);
|
||||||
|
|
||||||
|
this.#init = Promise.all([
|
||||||
|
new UmbContextConsumerController(this.#host, UMB_MEDIA_TREE_STORE_CONTEXT_TOKEN, (instance) => {
|
||||||
|
this.#treeStore = instance;
|
||||||
|
}),
|
||||||
|
|
||||||
|
new UmbContextConsumerController(this.#host, UMB_MEDIA_DETAIL_STORE_CONTEXT_TOKEN, (instance) => {
|
||||||
|
this.#detailStore = instance;
|
||||||
|
}),
|
||||||
|
|
||||||
|
new UmbContextConsumerController(this.#host, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN, (instance) => {
|
||||||
|
this.#notificationService = instance;
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async requestRootTreeItems() {
|
||||||
|
await this.#init;
|
||||||
|
|
||||||
|
const { data, error } = await this.#treeSource.getRootItems();
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
this.#treeStore?.appendItems(data.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { data, error, asObservable: () => this.#treeStore!.rootItems };
|
||||||
|
}
|
||||||
|
|
||||||
|
async requestTreeItemsOf(parentKey: string | null) {
|
||||||
|
await this.#init;
|
||||||
|
|
||||||
|
if (!parentKey) {
|
||||||
|
const error: ProblemDetails = { title: 'Parent key is missing' };
|
||||||
|
return { data: undefined, error };
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data, error } = await this.#treeSource.getChildrenOf(parentKey);
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
this.#treeStore?.appendItems(data.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { data, error, asObservable: () => this.#treeStore!.childrenOf(parentKey) };
|
||||||
|
}
|
||||||
|
|
||||||
|
async requestTreeItems(keys: Array<string>) {
|
||||||
|
await this.#init;
|
||||||
|
|
||||||
|
if (!keys) {
|
||||||
|
const error: ProblemDetails = { title: 'Keys are missing' };
|
||||||
|
return { data: undefined, error };
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data, error } = await this.#treeSource.getItems(keys);
|
||||||
|
|
||||||
|
return { data, error, asObservable: () => this.#treeStore!.items(keys) };
|
||||||
|
}
|
||||||
|
|
||||||
|
async rootTreeItems() {
|
||||||
|
await this.#init;
|
||||||
|
return this.#treeStore!.rootItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
async treeItemsOf(parentKey: string | null) {
|
||||||
|
await this.#init;
|
||||||
|
return this.#treeStore!.childrenOf(parentKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
async treeItems(keys: Array<string>) {
|
||||||
|
await this.#init;
|
||||||
|
return this.#treeStore!.items(keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
// DETAILS:
|
||||||
|
|
||||||
|
async createDetailsScaffold(parentKey: string | null) {
|
||||||
|
await this.#init;
|
||||||
|
|
||||||
|
if (!parentKey) {
|
||||||
|
throw new Error('Parent key is missing');
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.#detailDataSource.createScaffold(parentKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
async requestDetails(key: string) {
|
||||||
|
await this.#init;
|
||||||
|
|
||||||
|
// TODO: should we show a notification if the key is missing?
|
||||||
|
// Investigate what is best for Acceptance testing, cause in that perspective a thrown error might be the best choice?
|
||||||
|
if (!key) {
|
||||||
|
const error: ProblemDetails = { title: 'Key is missing' };
|
||||||
|
return { error };
|
||||||
|
}
|
||||||
|
const { data, error } = await this.#detailDataSource.get(key);
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
this.#detailStore?.append(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { data, error };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Could potentially be general methods:
|
||||||
|
|
||||||
|
async createDetail(template: ItemDetailType) {
|
||||||
|
await this.#init;
|
||||||
|
|
||||||
|
if (!template || !template.key) {
|
||||||
|
throw new Error('Template is missing');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { error } = await this.#detailDataSource.insert(template);
|
||||||
|
|
||||||
|
if (!error) {
|
||||||
|
const notification = { data: { message: `Media created` } };
|
||||||
|
this.#notificationService?.peek('positive', notification);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: we currently don't use the detail store for anything.
|
||||||
|
// Consider to look up the data before fetching from the server
|
||||||
|
this.#detailStore?.append(template);
|
||||||
|
// TODO: Update tree store with the new item? or ask tree to request the new item?
|
||||||
|
|
||||||
|
return { error };
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveDetail(document: ItemDetailType) {
|
||||||
|
await this.#init;
|
||||||
|
|
||||||
|
if (!document || !document.key) {
|
||||||
|
throw new Error('Template is missing');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { error } = await this.#detailDataSource.update(document);
|
||||||
|
|
||||||
|
if (!error) {
|
||||||
|
const notification = { data: { message: `Document saved` } };
|
||||||
|
this.#notificationService?.peek('positive', notification);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: we currently don't use the detail store for anything.
|
||||||
|
// Consider to look up the data before fetching from the server
|
||||||
|
// Consider notify a workspace if a template is updated in the store while someone is editing it.
|
||||||
|
this.#detailStore?.append(document);
|
||||||
|
this.#treeStore?.updateItem(document.key, { name: document.name });
|
||||||
|
|
||||||
|
// TODO: would be nice to align the stores on methods/methodNames.
|
||||||
|
|
||||||
|
return { error };
|
||||||
|
}
|
||||||
|
|
||||||
|
// General:
|
||||||
|
async delete(key: string) {
|
||||||
|
await this.#init;
|
||||||
|
|
||||||
|
if (!key) {
|
||||||
|
throw new Error('Document key is missing');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { error } = await this.#detailDataSource.delete(key);
|
||||||
|
|
||||||
|
if (!error) {
|
||||||
|
const notification = { data: { message: `Document deleted` } };
|
||||||
|
this.#notificationService?.peek('positive', notification);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: we currently don't use the detail store for anything.
|
||||||
|
// Consider to look up the data before fetching from the server.
|
||||||
|
// Consider notify a workspace if a template is deleted from the store while someone is editing it.
|
||||||
|
this.#detailStore?.remove([key]);
|
||||||
|
this.#treeStore?.removeItem(key);
|
||||||
|
// TODO: would be nice to align the stores on methods/methodNames.
|
||||||
|
|
||||||
|
return { error };
|
||||||
|
}
|
||||||
|
|
||||||
|
async move() {
|
||||||
|
alert('move');
|
||||||
|
}
|
||||||
|
|
||||||
|
async copy() {
|
||||||
|
alert('copy');
|
||||||
|
}
|
||||||
|
|
||||||
|
async sortChildrenOf() {
|
||||||
|
alert('sort');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import { EntityTreeItem } from '@umbraco-cms/backend-api';
|
||||||
|
import { UmbContextToken } from '@umbraco-cms/context-api';
|
||||||
|
import { ArrayState } from '@umbraco-cms/observable-api';
|
||||||
|
import { UmbStoreBase } from '@umbraco-cms/store';
|
||||||
|
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @export
|
||||||
|
* @class UmbMediaTreeStore
|
||||||
|
* @extends {UmbStoreBase}
|
||||||
|
* @description - Tree Data Store for Templates
|
||||||
|
*/
|
||||||
|
// TODO: consider if tree store could be turned into a general EntityTreeStore class?
|
||||||
|
export class UmbMediaTreeStore extends UmbStoreBase {
|
||||||
|
#data = new ArrayState<EntityTreeItem>([], (x) => x.key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance of UmbMediaTreeStore.
|
||||||
|
* @param {UmbControllerHostInterface} host
|
||||||
|
* @memberof UmbMediaTreeStore
|
||||||
|
*/
|
||||||
|
constructor(host: UmbControllerHostInterface) {
|
||||||
|
super(host, UMB_MEDIA_TREE_STORE_CONTEXT_TOKEN.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends items to the store
|
||||||
|
* @param {Array<EntityTreeItem>} items
|
||||||
|
* @memberof UmbMediaTreeStore
|
||||||
|
*/
|
||||||
|
appendItems(items: Array<EntityTreeItem>) {
|
||||||
|
this.#data.append(items);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates an item in the store
|
||||||
|
* @param {string} key
|
||||||
|
* @param {Partial<EntityTreeItem>} data
|
||||||
|
* @memberof UmbMediaTreeStore
|
||||||
|
*/
|
||||||
|
updateItem(key: string, data: Partial<EntityTreeItem>) {
|
||||||
|
const entries = this.#data.getValue();
|
||||||
|
const entry = entries.find((entry) => entry.key === key);
|
||||||
|
|
||||||
|
if (entry) {
|
||||||
|
this.#data.appendOne({ ...entry, ...data });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes an item from the store
|
||||||
|
* @param {string} key
|
||||||
|
* @memberof UmbMediaTreeStore
|
||||||
|
*/
|
||||||
|
removeItem(key: string) {
|
||||||
|
const entries = this.#data.getValue();
|
||||||
|
const entry = entries.find((entry) => entry.key === key);
|
||||||
|
|
||||||
|
if (entry) {
|
||||||
|
this.#data.remove([key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An observable to observe the root items
|
||||||
|
* @memberof UmbMediaTreeStore
|
||||||
|
*/
|
||||||
|
rootItems = this.#data.getObservablePart((items) => items.filter((item) => item.parentKey === null));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an observable to observe the children of a given parent
|
||||||
|
* @param {(string | null)} parentKey
|
||||||
|
* @return {*}
|
||||||
|
* @memberof UmbMediaTreeStore
|
||||||
|
*/
|
||||||
|
childrenOf(parentKey: string | null) {
|
||||||
|
return this.#data.getObservablePart((items) => items.filter((item) => item.parentKey === parentKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an observable to observe the items with the given keys
|
||||||
|
* @param {Array<string>} keys
|
||||||
|
* @return {*}
|
||||||
|
* @memberof UmbMediaTreeStore
|
||||||
|
*/
|
||||||
|
items(keys: Array<string>) {
|
||||||
|
return this.#data.getObservablePart((items) => items.filter((item) => keys.includes(item.key ?? '')));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UMB_MEDIA_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbMediaTreeStore>(UmbMediaTreeStore.name);
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
import { RepositoryDetailDataSource } from '@umbraco-cms/repository';
|
||||||
|
import { ProblemDetails } from '@umbraco-cms/backend-api';
|
||||||
|
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
|
||||||
|
import { tryExecuteAndNotify } from '@umbraco-cms/resources';
|
||||||
|
import type { MediaDetails } from '@umbraco-cms/models';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A data source for the Template detail that fetches data from the server
|
||||||
|
* @export
|
||||||
|
* @class UmbTemplateDetailServerDataSource
|
||||||
|
* @implements {TemplateDetailDataSource}
|
||||||
|
*/
|
||||||
|
export class UmbMediaDetailServerDataSource implements RepositoryDetailDataSource<MediaDetails> {
|
||||||
|
#host: UmbControllerHostInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance of UmbMediaDetailServerDataSource.
|
||||||
|
* @param {UmbControllerHostInterface} host
|
||||||
|
* @memberof UmbMediaDetailServerDataSource
|
||||||
|
*/
|
||||||
|
constructor(host: UmbControllerHostInterface) {
|
||||||
|
this.#host = host;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches a Media with the given key from the server
|
||||||
|
* @param {string} key
|
||||||
|
* @return {*}
|
||||||
|
* @memberof UmbMediaDetailServerDataSource
|
||||||
|
*/
|
||||||
|
async get(key: string) {
|
||||||
|
if (!key) {
|
||||||
|
const error: ProblemDetails = { title: 'Key is missing' };
|
||||||
|
return { error };
|
||||||
|
}
|
||||||
|
|
||||||
|
return tryExecuteAndNotify(
|
||||||
|
this.#host,
|
||||||
|
// TODO: use backend cli when available.
|
||||||
|
fetch(`/umbraco/management/api/v1/media/details/${key}`)
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((res) => res[0] || undefined)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Media scaffold
|
||||||
|
* @param {(string | null)} parentKey
|
||||||
|
* @return {*}
|
||||||
|
* @memberof UmbMediaDetailServerDataSource
|
||||||
|
*/
|
||||||
|
async createScaffold(parentKey: string | null) {
|
||||||
|
const data: MediaDetails = {
|
||||||
|
key: '',
|
||||||
|
name: '',
|
||||||
|
icon: '',
|
||||||
|
type: '',
|
||||||
|
hasChildren: false,
|
||||||
|
parentKey: parentKey ?? '',
|
||||||
|
isTrashed: false,
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
alias: '',
|
||||||
|
label: '',
|
||||||
|
description: '',
|
||||||
|
dataTypeKey: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
alias: '',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
name: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
return { data };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a new Media on the server
|
||||||
|
* @param {Media} media
|
||||||
|
* @return {*}
|
||||||
|
* @memberof UmbMediaDetailServerDataSource
|
||||||
|
*/
|
||||||
|
async insert(media: MediaDetails) {
|
||||||
|
if (!media.key) {
|
||||||
|
//const error: ProblemDetails = { title: 'Media key is missing' };
|
||||||
|
return Promise.reject();
|
||||||
|
}
|
||||||
|
//const payload = { key: media.key, requestBody: media };
|
||||||
|
|
||||||
|
let body: string;
|
||||||
|
|
||||||
|
try {
|
||||||
|
body = JSON.stringify(media);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return Promise.reject();
|
||||||
|
}
|
||||||
|
//return tryExecuteAndNotify(this.#host, MediaResource.postMedia(payload));
|
||||||
|
return tryExecuteAndNotify<MediaDetails>(
|
||||||
|
this.#host,
|
||||||
|
fetch('/umbraco/management/api/v1/media/save', {
|
||||||
|
method: 'POST',
|
||||||
|
body: body,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
}) as any
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a Media on the server
|
||||||
|
* @param {Media} Media
|
||||||
|
* @return {*}
|
||||||
|
* @memberof UmbMediaDetailServerDataSource
|
||||||
|
*/
|
||||||
|
// TODO: Error mistake in this:
|
||||||
|
async update(media: MediaDetails) {
|
||||||
|
if (!media.key) {
|
||||||
|
const error: ProblemDetails = { title: 'Media key is missing' };
|
||||||
|
return { error };
|
||||||
|
}
|
||||||
|
//const payload = { key: media.key, requestBody: media };
|
||||||
|
|
||||||
|
let body: string;
|
||||||
|
|
||||||
|
try {
|
||||||
|
body = JSON.stringify(media);
|
||||||
|
} catch (error) {
|
||||||
|
const myError: ProblemDetails = { title: 'JSON could not parse' };
|
||||||
|
return { error: myError };
|
||||||
|
}
|
||||||
|
|
||||||
|
return tryExecuteAndNotify<MediaDetails>(
|
||||||
|
this.#host,
|
||||||
|
fetch('/umbraco/management/api/v1/media/save', {
|
||||||
|
method: 'POST',
|
||||||
|
body: body,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
}) as any
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trash a Media on the server
|
||||||
|
* @param {Media} Media
|
||||||
|
* @return {*}
|
||||||
|
* @memberof UmbMediaDetailServerDataSource
|
||||||
|
*/
|
||||||
|
async trash(key: string) {
|
||||||
|
if (!key) {
|
||||||
|
const error: ProblemDetails = { title: 'Key is missing' };
|
||||||
|
return { error };
|
||||||
|
}
|
||||||
|
|
||||||
|
return tryExecuteAndNotify<MediaDetails>(
|
||||||
|
this.#host,
|
||||||
|
fetch('/umbraco/management/api/v1/media/trash', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify([key]),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
}) as any
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a Template on the server
|
||||||
|
* @param {string} key
|
||||||
|
* @return {*}
|
||||||
|
* @memberof UmbTemplateDetailServerDataSource
|
||||||
|
*/
|
||||||
|
// TODO: Error mistake in this:
|
||||||
|
async delete(key: string) {
|
||||||
|
if (!key) {
|
||||||
|
const error: ProblemDetails = { title: 'Key is missing' };
|
||||||
|
return { error };
|
||||||
|
}
|
||||||
|
|
||||||
|
return tryExecuteAndNotify(
|
||||||
|
this.#host,
|
||||||
|
fetch('/umbraco/management/api/v1/media/delete', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify([key]),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
import type { RepositoryTreeDataSource } from '../../../../../../libs/repository/repository-tree-data-source.interface';
|
||||||
|
import { ProblemDetails, MediaResource } from '@umbraco-cms/backend-api';
|
||||||
|
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
|
||||||
|
import { tryExecuteAndNotify } from '@umbraco-cms/resources';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A data source for the Media tree that fetches data from the server
|
||||||
|
* @export
|
||||||
|
* @class MediaTreeServerDataSource
|
||||||
|
* @implements {MediaTreeDataSource}
|
||||||
|
*/
|
||||||
|
export class MediaTreeServerDataSource implements RepositoryTreeDataSource {
|
||||||
|
#host: UmbControllerHostInterface;
|
||||||
|
|
||||||
|
// TODO: how do we handle trashed items?
|
||||||
|
async trashItems(keys: Array<string>) {
|
||||||
|
// TODO: use backend cli when available.
|
||||||
|
return tryExecuteAndNotify(
|
||||||
|
this.#host,
|
||||||
|
fetch('/umbraco/management/api/v1/media/trash', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(keys),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async moveItems(keys: Array<string>, destination: string) {
|
||||||
|
// TODO: use backend cli when available.
|
||||||
|
return tryExecuteAndNotify(
|
||||||
|
this.#host,
|
||||||
|
fetch('/umbraco/management/api/v1/media/move', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ keys, destination }),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance of MediaTreeServerDataSource.
|
||||||
|
* @param {UmbControllerHostInterface} host
|
||||||
|
* @memberof MediaTreeServerDataSource
|
||||||
|
*/
|
||||||
|
constructor(host: UmbControllerHostInterface) {
|
||||||
|
this.#host = host;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the root items for the tree from the server
|
||||||
|
* @return {*}
|
||||||
|
* @memberof MediaTreeServerDataSource
|
||||||
|
*/
|
||||||
|
async getRootItems() {
|
||||||
|
return tryExecuteAndNotify(this.#host, MediaResource.getTreeMediaRoot({}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the children of a given parent key from the server
|
||||||
|
* @param {(string | null)} parentKey
|
||||||
|
* @return {*}
|
||||||
|
* @memberof MediaTreeServerDataSource
|
||||||
|
*/
|
||||||
|
async getChildrenOf(parentKey: string | null) {
|
||||||
|
if (!parentKey) {
|
||||||
|
const error: ProblemDetails = { title: 'Parent key is missing' };
|
||||||
|
return { error };
|
||||||
|
}
|
||||||
|
|
||||||
|
return tryExecuteAndNotify(
|
||||||
|
this.#host,
|
||||||
|
MediaResource.getTreeMediaChildren({
|
||||||
|
parentKey,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the items for the given keys from the server
|
||||||
|
* @param {Array<string>} keys
|
||||||
|
* @return {*}
|
||||||
|
* @memberof MediaTreeServerDataSource
|
||||||
|
*/
|
||||||
|
async getItems(keys: Array<string>) {
|
||||||
|
if (keys) {
|
||||||
|
const error: ProblemDetails = { title: 'Keys are missing' };
|
||||||
|
return { error };
|
||||||
|
}
|
||||||
|
|
||||||
|
return tryExecuteAndNotify(
|
||||||
|
this.#host,
|
||||||
|
MediaResource.getTreeMediaItem({
|
||||||
|
key: keys,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { UMB_MEDIA_TREE_STORE_CONTEXT_TOKEN } from '../media.tree.store';
|
import { UmbMediaRepository } from '../repository/media.repository';
|
||||||
import type { ManifestTree, ManifestTreeItemAction } from '@umbraco-cms/models';
|
import type { ManifestTree, ManifestTreeItemAction } from '@umbraco-cms/models';
|
||||||
|
|
||||||
const treeAlias = 'Umb.Tree.Media';
|
const treeAlias = 'Umb.Tree.Media';
|
||||||
@@ -8,7 +8,7 @@ const tree: ManifestTree = {
|
|||||||
alias: treeAlias,
|
alias: treeAlias,
|
||||||
name: 'Media Tree',
|
name: 'Media Tree',
|
||||||
meta: {
|
meta: {
|
||||||
storeAlias: UMB_MEDIA_TREE_STORE_CONTEXT_TOKEN.toString(),
|
repository: UmbMediaRepository, // TODO: use alias instead of class
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { UMB_MEDIA_TREE_STORE_CONTEXT_TOKEN } from '../media.tree.store';
|
|
||||||
import type {
|
import type {
|
||||||
ManifestWorkspace,
|
ManifestWorkspace,
|
||||||
ManifestWorkspaceAction,
|
ManifestWorkspaceAction,
|
||||||
@@ -59,7 +58,7 @@ const workspaceViewCollections: Array<ManifestWorkspaceViewCollection> = [
|
|||||||
pathname: 'collection',
|
pathname: 'collection',
|
||||||
icon: 'umb:grid',
|
icon: 'umb:grid',
|
||||||
entityType: 'media',
|
entityType: 'media',
|
||||||
storeAlias: UMB_MEDIA_TREE_STORE_CONTEXT_TOKEN.toString(),
|
repositoryAlias: 'Umb.Repository.Media',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,31 +1,87 @@
|
|||||||
import { UMB_MEDIA_DETAIL_STORE_CONTEXT_TOKEN } from "../media.detail.store";
|
import { UmbWorkspaceContext } from '../../../shared/components/workspace/workspace-context/workspace-context';
|
||||||
import { UmbEntityWorkspaceManager } from "../../../shared/components/workspace/workspace-context/entity-manager-controller";
|
import { UmbMediaRepository } from '../repository/media.repository';
|
||||||
import { UmbWorkspaceContext } from "../../../shared/components/workspace/workspace-context/workspace-context";
|
import type { UmbWorkspaceEntityContextInterface } from '../../../shared/components/workspace/workspace-context/workspace-entity-context.interface';
|
||||||
import { UmbWorkspaceEntityContextInterface } from "../../../shared/components/workspace/workspace-context/workspace-entity-context.interface";
|
import { appendToFrozenArray, ObjectState } from '@umbraco-cms/observable-api';
|
||||||
import type { MediaDetails } from "@umbraco-cms/models";
|
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
|
||||||
|
import type { MediaDetails } from '@umbraco-cms/models';
|
||||||
|
|
||||||
export class UmbWorkspaceMediaContext extends UmbWorkspaceContext implements UmbWorkspaceEntityContextInterface<MediaDetails | undefined> {
|
type EntityType = MediaDetails;
|
||||||
|
export class UmbMediaWorkspaceContext
|
||||||
|
extends UmbWorkspaceContext
|
||||||
|
implements UmbWorkspaceEntityContextInterface<EntityType | undefined>
|
||||||
|
{
|
||||||
|
#isNew = false;
|
||||||
|
#host: UmbControllerHostInterface;
|
||||||
|
#detailRepository: UmbMediaRepository;
|
||||||
|
|
||||||
|
#data = new ObjectState<EntityType | undefined>(undefined);
|
||||||
|
data = this.#data.asObservable();
|
||||||
|
name = this.#data.getObservablePart((data) => data?.name);
|
||||||
|
|
||||||
#manager = new UmbEntityWorkspaceManager(this._host, 'media', UMB_MEDIA_DETAIL_STORE_CONTEXT_TOKEN);
|
constructor(host: UmbControllerHostInterface) {
|
||||||
|
super(host);
|
||||||
|
this.#host = host;
|
||||||
|
this.#detailRepository = new UmbMediaRepository(this.#host);
|
||||||
|
}
|
||||||
|
|
||||||
public readonly data = this.#manager.state.asObservable();
|
getData() {
|
||||||
public readonly name = this.#manager.state.getObservablePart((state) => state?.name);
|
return this.#data.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
getEntityKey() {
|
||||||
|
return this.getData()?.key || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
getEntityType() {
|
||||||
|
return 'media';
|
||||||
|
}
|
||||||
|
|
||||||
setName(name: string) {
|
setName(name: string) {
|
||||||
this.#manager.state.update({name: name})
|
this.#data.update({ name });
|
||||||
}
|
}
|
||||||
getEntityType = this.#manager.getEntityType;
|
|
||||||
getUnique = this.#manager.getEntityKey;
|
|
||||||
getEntityKey = this.#manager.getEntityKey;
|
|
||||||
getStore = this.#manager.getStore;
|
|
||||||
getData = this.#manager.getData;
|
|
||||||
load = this.#manager.load;
|
|
||||||
create = this.#manager.create;
|
|
||||||
save = this.#manager.save;
|
|
||||||
destroy = this.#manager.destroy;
|
|
||||||
|
|
||||||
public setPropertyValue(alias: string, value: unknown) {
|
setPropertyValue(alias: string, value: unknown) {
|
||||||
throw new Error('setPropertyValue is not implemented for UmbWorkspaceMediaContext');
|
const entry = { alias: alias, value: value };
|
||||||
|
|
||||||
|
const currentData = this.#data.value;
|
||||||
|
if (currentData) {
|
||||||
|
const newDataSet = appendToFrozenArray(currentData.data, entry, (x) => x.alias);
|
||||||
|
|
||||||
|
this.#data.update({ data: newDataSet });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async load(entityKey: string) {
|
||||||
|
const { data } = await this.#detailRepository.requestDetails(entityKey);
|
||||||
|
if (data) {
|
||||||
|
this.#isNew = false;
|
||||||
|
this.#data.next(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async createScaffold(parentKey: string | null) {
|
||||||
|
const { data } = await this.#detailRepository.createDetailsScaffold(parentKey);
|
||||||
|
if (!data) return;
|
||||||
|
this.#isNew = true;
|
||||||
|
this.#data.next(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
async save() {
|
||||||
|
if (!this.#data.value) return;
|
||||||
|
if (this.#isNew) {
|
||||||
|
await this.#detailRepository.createDetail(this.#data.value);
|
||||||
|
} else {
|
||||||
|
await this.#detailRepository.saveDetail(this.#data.value);
|
||||||
|
}
|
||||||
|
// If it went well, then its not new anymore?.
|
||||||
|
this.#isNew = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(key: string) {
|
||||||
|
await this.#detailRepository.delete(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public destroy(): void {
|
||||||
|
this.#data.complete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||||
import { css, html } from 'lit';
|
import { css, html, nothing } from 'lit';
|
||||||
import { customElement, property } from 'lit/decorators.js';
|
import { customElement, state } from 'lit/decorators.js';
|
||||||
import { UmbWorkspaceMediaContext } from './media-workspace.context';
|
import type { UmbWorkspaceEntityElement } from '../../../shared/components/workspace/workspace-entity-element.interface';
|
||||||
|
import { UmbMediaWorkspaceContext } from './media-workspace.context';
|
||||||
import { UmbLitElement } from '@umbraco-cms/element';
|
import { UmbLitElement } from '@umbraco-cms/element';
|
||||||
|
|
||||||
@customElement('umb-media-workspace')
|
@customElement('umb-document-workspace')
|
||||||
export class UmbMediaWorkspaceElement extends UmbLitElement {
|
export class UmbMediaWorkspaceElement extends UmbLitElement implements UmbWorkspaceEntityElement {
|
||||||
static styles = [
|
static styles = [
|
||||||
UUITextStyles,
|
UUITextStyles,
|
||||||
css`
|
css`
|
||||||
@@ -17,19 +18,31 @@ export class UmbMediaWorkspaceElement extends UmbLitElement {
|
|||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
private _workspaceContext: UmbMediaWorkspaceContext = new UmbMediaWorkspaceContext(this);
|
||||||
|
|
||||||
private _workspaceContext: UmbWorkspaceMediaContext = new UmbWorkspaceMediaContext(this);
|
@state()
|
||||||
|
_unique?: string;
|
||||||
|
|
||||||
public load(value: string) {
|
public load(entityKey: string) {
|
||||||
this._workspaceContext?.load(value);
|
this._workspaceContext.load(entityKey);
|
||||||
|
this._unique = entityKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public create(parentKey: string | null) {
|
public create(parentKey: string | null) {
|
||||||
this._workspaceContext?.create(parentKey);
|
this._workspaceContext.createScaffold(parentKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`<umb-workspace-content alias="Umb.Workspace.Media"></umb-workspace-content>`;
|
return html`<umb-workspace-content alias="Umb.Workspace.Media">
|
||||||
|
${this._unique
|
||||||
|
? html`
|
||||||
|
<umb-workspace-action-menu
|
||||||
|
slot="action-menu"
|
||||||
|
entity-type="media"
|
||||||
|
unique="${this._unique}"></umb-workspace-action-menu>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
</umb-workspace-content>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { UMB_MEDIA_TREE_STORE_CONTEXT_TOKEN } from './media/media.tree.store';
|
|
||||||
import type { ManifestDashboardCollection, ManifestSection } from '@umbraco-cms/models';
|
import type { ManifestDashboardCollection, ManifestSection } from '@umbraco-cms/models';
|
||||||
|
|
||||||
const sectionAlias = 'Umb.Section.Media';
|
const sectionAlias = 'Umb.Section.Media';
|
||||||
@@ -25,7 +24,7 @@ const dashboards: Array<ManifestDashboardCollection> = [
|
|||||||
sections: [sectionAlias],
|
sections: [sectionAlias],
|
||||||
pathname: 'media-management',
|
pathname: 'media-management',
|
||||||
entityType: 'media',
|
entityType: 'media',
|
||||||
storeAlias: UMB_MEDIA_TREE_STORE_CONTEXT_TOKEN.toString(),
|
repositoryAlias: 'Umb.Repository.Media',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -156,16 +156,6 @@ export class UmbCollectionContext<
|
|||||||
this.#selection.next(value);
|
this.#selection.next(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Not all can trash, so maybe we need to differentiate on collection contexts or fix it with another architecture.
|
|
||||||
public trash(keys: string[]) {
|
|
||||||
this._store?.trash(keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Not all can move, so maybe we need to differentiate on collection contexts or fix it with another architecture.
|
|
||||||
public move(keys: string[], destination: string) {
|
|
||||||
this._store?.move(keys, destination);
|
|
||||||
}
|
|
||||||
|
|
||||||
public clearSelection() {
|
public clearSelection() {
|
||||||
this.#selection.next([]);
|
this.#selection.next([]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { map } from 'rxjs';
|
|||||||
import './collection-selection-actions.element';
|
import './collection-selection-actions.element';
|
||||||
import './collection-toolbar.element';
|
import './collection-toolbar.element';
|
||||||
import { UmbCollectionContext, UMB_COLLECTION_CONTEXT_TOKEN } from './collection.context';
|
import { UmbCollectionContext, UMB_COLLECTION_CONTEXT_TOKEN } from './collection.context';
|
||||||
import { createExtensionElement , umbExtensionsRegistry } from '@umbraco-cms/extensions-api';
|
import { createExtensionElement, umbExtensionsRegistry } from '@umbraco-cms/extensions-api';
|
||||||
import type { ManifestCollectionView, MediaDetails } from '@umbraco-cms/models';
|
import type { ManifestCollectionView, MediaDetails } from '@umbraco-cms/models';
|
||||||
import { UmbLitElement } from '@umbraco-cms/element';
|
import { UmbLitElement } from '@umbraco-cms/element';
|
||||||
import type { UmbObserverController } from '@umbraco-cms/observable-api';
|
import type { UmbObserverController } from '@umbraco-cms/observable-api';
|
||||||
@@ -38,7 +38,7 @@ export class UmbCollectionElement extends UmbLitElement {
|
|||||||
private _collectionContext?: UmbCollectionContext<MediaDetails>;
|
private _collectionContext?: UmbCollectionContext<MediaDetails>;
|
||||||
|
|
||||||
private _entityType!: string;
|
private _entityType!: string;
|
||||||
@property()
|
@property({ type: String, attribute: 'entity-type' })
|
||||||
public get entityType(): string {
|
public get entityType(): string {
|
||||||
return this._entityType;
|
return this._entityType;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { css, html } from 'lit';
|
|||||||
import { customElement, state } from 'lit/decorators.js';
|
import { customElement, state } from 'lit/decorators.js';
|
||||||
import '../collection.element';
|
import '../collection.element';
|
||||||
import { ifDefined } from 'lit-html/directives/if-defined.js';
|
import { ifDefined } from 'lit-html/directives/if-defined.js';
|
||||||
import { UmbMediaTreeStore } from '../../../media/media/media.tree.store';
|
|
||||||
import { UmbCollectionContext, UMB_COLLECTION_CONTEXT_TOKEN } from '../../../shared/collection/collection.context';
|
import { UmbCollectionContext, UMB_COLLECTION_CONTEXT_TOKEN } from '../../../shared/collection/collection.context';
|
||||||
import type { ManifestDashboardCollection } from '@umbraco-cms/models';
|
import type { ManifestDashboardCollection } from '@umbraco-cms/models';
|
||||||
import type { FolderTreeItem } from '@umbraco-cms/backend-api';
|
import type { FolderTreeItem } from '@umbraco-cms/backend-api';
|
||||||
@@ -25,7 +24,7 @@ export class UmbDashboardCollectionElement extends UmbLitElement {
|
|||||||
];
|
];
|
||||||
|
|
||||||
// TODO: Use the right type here:
|
// TODO: Use the right type here:
|
||||||
private _collectionContext?: UmbCollectionContext<FolderTreeItem, UmbMediaTreeStore>;
|
private _collectionContext?: UmbCollectionContext<FolderTreeItem, any>;
|
||||||
|
|
||||||
public manifest!: ManifestDashboardCollection;
|
public manifest!: ManifestDashboardCollection;
|
||||||
|
|
||||||
@@ -37,14 +36,15 @@ export class UmbDashboardCollectionElement extends UmbLitElement {
|
|||||||
|
|
||||||
if (!this._collectionContext) {
|
if (!this._collectionContext) {
|
||||||
const manifestMeta = this.manifest.meta;
|
const manifestMeta = this.manifest.meta;
|
||||||
this._entityType = manifestMeta.entityType as string;
|
const repositoryAlias = manifestMeta.repositoryAlias;
|
||||||
this._collectionContext = new UmbCollectionContext(this, null, null, manifestMeta.storeAlias);
|
this._entityType = manifestMeta.entityType;
|
||||||
|
this._collectionContext = new UmbCollectionContext(this, this._entityType, null, '', repositoryAlias);
|
||||||
this.provideContext(UMB_COLLECTION_CONTEXT_TOKEN, this._collectionContext);
|
this.provideContext(UMB_COLLECTION_CONTEXT_TOKEN, this._collectionContext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`<umb-collection entityType=${ifDefined(this._entityType)}></umb-collection>`;
|
return html`<umb-collection entity-type=${ifDefined(this._entityType)}></umb-collection>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user