Merge branch 'main' into Feature-Collection-Manifest
This commit is contained in:
@@ -6,3 +6,4 @@ schemas
|
||||
temp-schema-generator
|
||||
APP_PLUGINS
|
||||
/src/external/router-slot
|
||||
/examples
|
||||
|
||||
49
src/Umbraco.Web.UI.Client/devops/example-runner/index.js
Normal file
49
src/Umbraco.Web.UI.Client/devops/example-runner/index.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import * as readline from 'readline';
|
||||
import { execSync } from 'child_process';
|
||||
import { readdir } from 'fs/promises';
|
||||
|
||||
const exampleDirectory = 'examples';
|
||||
|
||||
const getDirectories = async (source) =>
|
||||
(await readdir(source, { withFileTypes: true }))
|
||||
.filter(dirent => dirent.isDirectory())
|
||||
.map(dirent => dirent.name)
|
||||
|
||||
async function pickExampleUI(){
|
||||
|
||||
// Find sub folder:
|
||||
const exampleFolderNames = await getDirectories(`${exampleDirectory}`);
|
||||
|
||||
// Create UI:
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
// List examples:
|
||||
console.log('Please select an example by entering the corresponding number:');
|
||||
exampleFolderNames.forEach((folder, index) => {
|
||||
console.log(`[${index + 1}] ${folder}`);
|
||||
});
|
||||
|
||||
// Ask user to select an example:
|
||||
rl.question('Enter your selection: ', (answer) => {
|
||||
|
||||
// User picked an example:
|
||||
const selectedFolder = exampleFolderNames[parseInt(answer) - 1];
|
||||
console.log(`You selected: ${selectedFolder}`);
|
||||
|
||||
process.env['VITE_EXAMPLE_PATH'] = `${exampleDirectory}/${selectedFolder}`;
|
||||
|
||||
// Start vite server:
|
||||
try {
|
||||
execSync('npm run dev', {stdio: 'inherit'});
|
||||
} catch (error) {
|
||||
// Nothing, cause this is most likely just the server begin stopped.
|
||||
//console.log(error);
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
pickExampleUI();
|
||||
7
src/Umbraco.Web.UI.Client/examples/README.md
Normal file
7
src/Umbraco.Web.UI.Client/examples/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Backoffice Examples
|
||||
|
||||
This folder contains example packages showcasing the usage of extensions in Backoffice.
|
||||
|
||||
The purpose of these projects includes serving as demonstration or example for
|
||||
packages, as well as testing to make sure the extension points continue
|
||||
to work in these situations and to assist in developing new integrations.
|
||||
0
src/Umbraco.Web.UI.Client/examples/index.js
Normal file
0
src/Umbraco.Web.UI.Client/examples/index.js
Normal file
@@ -0,0 +1,8 @@
|
||||
# Workspace Context Counter Example
|
||||
|
||||
This example demonstrates the essence of the Workspace Context.
|
||||
|
||||
The Workspace Context is available for everything within the Workspace, giving any extension within the ability to communicate through this.
|
||||
In this example, the Workspace Context houses a counter, which can be incremented by a Workspace Action and shown in the Workspace View.
|
||||
|
||||
To demonstrate this, the example comes with: A Workspace Context, A Workspace Action and a Workspace View.
|
||||
@@ -0,0 +1,29 @@
|
||||
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
|
||||
import { UmbBaseController, type UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UmbNumberState } from '@umbraco-cms/backoffice/observable-api';
|
||||
|
||||
// The Example Workspace Context Controller:
|
||||
export class WorkspaceContextCounter extends UmbBaseController {
|
||||
|
||||
// We always keep our states private, and expose the values as observables:
|
||||
#counter = new UmbNumberState(0);
|
||||
readonly counter = this.#counter.asObservable();
|
||||
|
||||
constructor(host: UmbControllerHost) {
|
||||
super(host);
|
||||
this.provideContext(EXAMPLE_COUNTER_CONTEXT, this);
|
||||
}
|
||||
|
||||
// Lets expose methods to update the state:
|
||||
increment() {
|
||||
this.#counter.next(this.#counter.value + 1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Declare a api export, so Extension Registry can initialize this class:
|
||||
export const api = WorkspaceContextCounter;
|
||||
|
||||
|
||||
// Declare a Context Token that other elements can use to request the WorkspaceContextCounter:
|
||||
export const EXAMPLE_COUNTER_CONTEXT = new UmbContextToken<WorkspaceContextCounter>('example.workspaceContext.counter');
|
||||
@@ -0,0 +1,60 @@
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { UMB_CURRENT_USER_CONTEXT } from '@umbraco-cms/backoffice/current-user';
|
||||
import { css, html, customElement, state, LitElement } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api';
|
||||
import { EXAMPLE_COUNTER_CONTEXT } from './counter-workspace-context';
|
||||
|
||||
@customElement('example-counter-workspace-view')
|
||||
export class ExampleCounterWorkspaceView extends UmbElementMixin(LitElement) {
|
||||
#counterContext?: typeof EXAMPLE_COUNTER_CONTEXT.TYPE;
|
||||
|
||||
@state()
|
||||
private count = '';
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.consumeContext(EXAMPLE_COUNTER_CONTEXT, (instance) => {
|
||||
this.#counterContext = instance;
|
||||
this.#observeCounter();
|
||||
});
|
||||
}
|
||||
|
||||
#observeCounter(): void {
|
||||
if (!this.#counterContext) return;
|
||||
this.observe(this.#counterContext.counter, (count) => {
|
||||
this.count = count;
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<uui-box class="uui-text">
|
||||
<h1 class="uui-h2" style="margin-top: var(--uui-size-layout-1);">Counter Example</h1>
|
||||
<p class="uui-lead">
|
||||
Current count value: ${this.count}
|
||||
</p>
|
||||
<p>
|
||||
This is a Workspace View, that consumes the Counter Context, and displays the current count.
|
||||
</p>
|
||||
</uui-box>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
UmbTextStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
padding: var(--uui-size-layout-1);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
export default ExampleCounterWorkspaceView;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'example-counter-workspace-view': ExampleCounterWorkspaceView;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { UmbBaseController } from '@umbraco-cms/backoffice/controller-api';
|
||||
import type { UmbWorkspaceAction } from '@umbraco-cms/backoffice/workspace';
|
||||
import { EXAMPLE_COUNTER_CONTEXT } from './counter-workspace-context';
|
||||
|
||||
// The Example Incrementor Workspace Action Controller:
|
||||
export class ExampleIncrementorWorkspaceAction extends UmbBaseController implements UmbWorkspaceAction {
|
||||
|
||||
// This method is executed
|
||||
async execute() {
|
||||
await this.consumeContext(EXAMPLE_COUNTER_CONTEXT, (context) => {
|
||||
context.increment();
|
||||
}).asPromise();
|
||||
}
|
||||
}
|
||||
|
||||
// Declare a api export, so Extension Registry can initialize this class:
|
||||
export const api = ExampleIncrementorWorkspaceAction;
|
||||
@@ -0,0 +1,52 @@
|
||||
import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
export const manifests: Array<ManifestTypes> = [
|
||||
{
|
||||
type: 'workspaceContext',
|
||||
name: 'Example Counter Workspace Context',
|
||||
alias: 'example.workspaceCounter.counter',
|
||||
js: () => import('./counter-workspace-context.js'),
|
||||
conditions: [
|
||||
{
|
||||
alias: 'Umb.Condition.WorkspaceAlias',
|
||||
match: 'Umb.Workspace.Document',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'workspaceAction',
|
||||
name: 'Example Count Incerementor Workspace Action',
|
||||
alias: 'example.workspaceAction.incrementor',
|
||||
weight: 1000,
|
||||
api: () => import('./incrementor-workspace-action.js'),
|
||||
meta: {
|
||||
label: 'Increment',
|
||||
look: 'primary',
|
||||
color: 'danger',
|
||||
},
|
||||
conditions: [
|
||||
{
|
||||
alias: 'Umb.Condition.WorkspaceAlias',
|
||||
match: 'Umb.Workspace.Document',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'workspaceEditorView',
|
||||
name: 'Example Counter Workspace View',
|
||||
alias: 'example.workspaceView.counter',
|
||||
element: () => import('./counter-workspace-view.js'),
|
||||
weight: 900,
|
||||
meta: {
|
||||
label: 'Counter',
|
||||
pathname: 'counter',
|
||||
icon: 'icon-lab',
|
||||
},
|
||||
conditions: [
|
||||
{
|
||||
alias: 'Umb.Condition.WorkspaceAlias',
|
||||
match: 'Umb.Workspace.Document',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
@@ -1,5 +1,6 @@
|
||||
import { UmbAppElement } from './src/apps/app/app.element.js';
|
||||
import { startMockServiceWorker } from './src/mocks/index.js';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
if (import.meta.env.VITE_UMBRACO_USE_MSW === 'on') {
|
||||
startMockServiceWorker();
|
||||
@@ -18,4 +19,24 @@ if (import.meta.env.DEV) {
|
||||
|
||||
appElement.bypassAuth = isMocking;
|
||||
|
||||
|
||||
document.body.appendChild(appElement);
|
||||
|
||||
|
||||
// Example injector:
|
||||
if(import.meta.env.VITE_EXAMPLE_PATH) {
|
||||
import(/* @vite-ignore */ './'+import.meta.env.VITE_EXAMPLE_PATH+'/index.ts').then((js) => {
|
||||
if (js) {
|
||||
Object.keys(js).forEach((key) => {
|
||||
const value = js[key];
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
umbExtensionsRegistry.registerMany(value);
|
||||
} else if (typeof value === 'object') {
|
||||
umbExtensionsRegistry.register(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -71,10 +71,13 @@
|
||||
"./user": "./dist-cms/packages/user/user/index.js",
|
||||
"./user-permission": "./dist-cms/packages/user/user-permission/index.js",
|
||||
"./code-editor": "./dist-cms/packages/templating/code-editor/index.js",
|
||||
"./external/*": "./dist-cms/external/*/index.js"
|
||||
"./external/*": "./dist-cms/external/*/index.js",
|
||||
"./examples/*": "./examples/*/index.js",
|
||||
"./examples": "./examples/index.js"
|
||||
},
|
||||
"files": [
|
||||
"dist-cms",
|
||||
"examples",
|
||||
"README.md"
|
||||
],
|
||||
"repository": {
|
||||
@@ -120,7 +123,8 @@
|
||||
"new-extension": "plop --plopfile ./devops/plop/plop.js",
|
||||
"compile": "tsc",
|
||||
"check": "npm run lint:errors && npm run compile && npm run build-storybook && npm run generate:jsonschema:dist",
|
||||
"prepublishOnly": "node ./devops/publish/cleanse-pkg.js"
|
||||
"prepublishOnly": "node ./devops/publish/cleanse-pkg.js",
|
||||
"example": "node ./devops/example-runner/index.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.9 <21",
|
||||
|
||||
@@ -17,7 +17,7 @@ import { UmbObserverController } from '@umbraco-cms/backoffice/observable-api';
|
||||
|
||||
type UmbClassMixinConstructor = new (
|
||||
host: UmbControllerHost,
|
||||
controllerAlias: UmbControllerAlias
|
||||
controllerAlias: UmbControllerAlias,
|
||||
) => UmbClassMixinDeclaration;
|
||||
|
||||
declare class UmbClassMixinDeclaration implements UmbClassMixinInterface {
|
||||
@@ -25,16 +25,19 @@ declare class UmbClassMixinDeclaration implements UmbClassMixinInterface {
|
||||
observe<T>(
|
||||
source: Observable<T>,
|
||||
callback: (_value: T) => void,
|
||||
controllerAlias?: UmbControllerAlias
|
||||
controllerAlias?: UmbControllerAlias,
|
||||
): UmbObserverController<T>;
|
||||
provideContext<
|
||||
BaseType = unknown,
|
||||
ResultType extends BaseType = BaseType,
|
||||
InstanceType extends ResultType = ResultType
|
||||
>(alias: string | UmbContextToken<BaseType, ResultType>, instance: InstanceType): UmbContextProviderController<BaseType, ResultType, InstanceType>;
|
||||
InstanceType extends ResultType = ResultType,
|
||||
>(
|
||||
alias: string | UmbContextToken<BaseType, ResultType>,
|
||||
instance: InstanceType,
|
||||
): UmbContextProviderController<BaseType, ResultType, InstanceType>;
|
||||
consumeContext<BaseType = unknown, ResultType extends BaseType = BaseType>(
|
||||
alias: string | UmbContextToken<BaseType, ResultType>,
|
||||
callback: UmbContextCallback<ResultType>
|
||||
callback: UmbContextCallback<ResultType>,
|
||||
): UmbContextConsumerController<BaseType, ResultType>;
|
||||
hasController(controller: UmbController): boolean;
|
||||
getControllers(filterMethod: (ctrl: UmbController) => boolean): UmbController[];
|
||||
@@ -86,15 +89,13 @@ export const UmbClassMixin = <T extends ClassConstructor>(superClass: T) => {
|
||||
* @return {UmbContextProviderController} Reference to a Context Provider Controller instance
|
||||
* @memberof UmbElementMixin
|
||||
*/
|
||||
provideContext
|
||||
<
|
||||
provideContext<
|
||||
BaseType = unknown,
|
||||
ResultType extends BaseType = BaseType,
|
||||
InstanceType extends ResultType = ResultType
|
||||
>
|
||||
(
|
||||
InstanceType extends ResultType = ResultType,
|
||||
>(
|
||||
contextAlias: string | UmbContextToken<BaseType, ResultType>,
|
||||
instance: InstanceType
|
||||
instance: InstanceType,
|
||||
): UmbContextProviderController {
|
||||
return new UmbContextProviderController<BaseType, ResultType, InstanceType>(this, contextAlias, instance);
|
||||
}
|
||||
@@ -108,8 +109,8 @@ export const UmbClassMixin = <T extends ClassConstructor>(superClass: T) => {
|
||||
*/
|
||||
consumeContext<BaseType = unknown, ResultType extends BaseType = BaseType>(
|
||||
contextAlias: string | UmbContextToken<BaseType, ResultType>,
|
||||
callback: UmbContextCallback<ResultType>
|
||||
): UmbContextConsumerController<BaseType, ResultType> {
|
||||
callback: UmbContextCallback<ResultType>,
|
||||
): UmbContextConsumerController<BaseType, ResultType> {
|
||||
return new UmbContextConsumerController(this, contextAlias, callback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,6 +195,7 @@ export class UmbDataTypePickerFlowModalElement extends UmbLitElement {
|
||||
|
||||
private _renderFilter() {
|
||||
return html` <uui-input
|
||||
type="search"
|
||||
id="filter"
|
||||
@input="${this._handleFilterInput}"
|
||||
placeholder="Type to filter..."
|
||||
|
||||
@@ -111,6 +111,7 @@ export class UmbPropertyEditorUIPickerModalElement extends UmbLitElement {
|
||||
|
||||
private _renderFilter() {
|
||||
return html` <uui-input
|
||||
type="search"
|
||||
id="filter"
|
||||
@input="${this._handleFilterInput}"
|
||||
placeholder="Type to filter..."
|
||||
|
||||
@@ -106,10 +106,11 @@ export class UmbIconPickerModalElement extends UmbModalBaseElement<UmbIconPicker
|
||||
|
||||
renderSearchbar() {
|
||||
return html` <uui-input
|
||||
@keyup="${this.#filterIcons}"
|
||||
type="search"
|
||||
placeholder="Type to filter..."
|
||||
label="Type to filter icons"
|
||||
id="searchbar">
|
||||
id="searchbar"
|
||||
@keyup="${this.#filterIcons}">
|
||||
<uui-icon name="search" slot="prepend" id="searchbar_icon"></uui-icon>
|
||||
</uui-input>`;
|
||||
}
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
import { UmbWorkspaceContextInterface, UMB_WORKSPACE_CONTEXT } from '../workspace-context/index.js';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api';
|
||||
import { UmbBaseController, type UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
|
||||
|
||||
export interface UmbWorkspaceAction<WorkspaceType = unknown> extends UmbApi {
|
||||
host: UmbControllerHost;
|
||||
workspaceContext?: WorkspaceType;
|
||||
export interface UmbWorkspaceAction extends UmbApi {
|
||||
execute(): Promise<void>;
|
||||
}
|
||||
|
||||
export abstract class UmbWorkspaceActionBase<WorkspaceContextType extends UmbWorkspaceContextInterface>
|
||||
implements UmbWorkspaceAction<WorkspaceContextType>
|
||||
export abstract class UmbWorkspaceActionBase<WorkspaceContextType extends UmbWorkspaceContextInterface> extends UmbBaseController
|
||||
implements UmbWorkspaceAction
|
||||
{
|
||||
host: UmbControllerHost;
|
||||
workspaceContext?: WorkspaceContextType;
|
||||
constructor(host: UmbControllerHost) {
|
||||
this.host = host;
|
||||
super(host);
|
||||
|
||||
new UmbContextConsumerController(this.host, UMB_WORKSPACE_CONTEXT, (instance) => {
|
||||
// TODO, we most likely should require a context token here in this type, and mane it specifically for workspace actions with context workspace request.
|
||||
this.consumeContext(UMB_WORKSPACE_CONTEXT, (instance) => {
|
||||
// TODO: Be aware we are casting here. We should consider a better solution for typing the contexts. (But notice we still want to capture the first workspace...)
|
||||
this.workspaceContext = instance as unknown as WorkspaceContextType;
|
||||
});
|
||||
|
||||
@@ -129,10 +129,11 @@ export class UmbDashboardTranslationDictionaryElement extends UmbLitElement {
|
||||
${this.localize.term('dictionary_createNew')}
|
||||
</uui-button>
|
||||
<uui-input
|
||||
@keyup="${this.#filter}"
|
||||
type="search"
|
||||
id="searchbar"
|
||||
placeholder=${this.localize.term('placeholders_filter')}
|
||||
label=${this.localize.term('placeholders_filter')}
|
||||
id="searchbar">
|
||||
@keyup="${this.#filter}">
|
||||
<div slot="prepend">
|
||||
<uui-icon name="search" id="searchbar_icon"></uui-icon>
|
||||
</div>
|
||||
|
||||
@@ -54,6 +54,7 @@ export class UmbDocumentWorkspaceElement extends UmbLitElement {
|
||||
},
|
||||
];
|
||||
|
||||
// TODO: We need to recreate when ID changed?
|
||||
new UmbExtensionsApiInitializer(this, umbExtensionsRegistry, 'workspaceContext', [this, this.#workspaceContext]);
|
||||
}
|
||||
|
||||
|
||||
@@ -112,6 +112,7 @@ export class UmbDashboardExamineSearcherElement extends UmbLitElement {
|
||||
<p>Search the ${this.searcherName} and view the results</p>
|
||||
<div class="flex">
|
||||
<uui-input
|
||||
type="search"
|
||||
id="search-input"
|
||||
placeholder="Type to filter..."
|
||||
label="Type to filter"
|
||||
|
||||
@@ -126,7 +126,7 @@
|
||||
"@umbraco-cms/internal/test-utils": ["utils/test-utils.ts"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*.ts", "apps/**/*.ts", "e2e/**/*.ts", "index.ts", "storybook/stories/**/*.ts"],
|
||||
"include": ["src/**/*.ts", "apps/**/*.ts", "e2e/**/*.ts", "index.ts", "storybook/stories/**/*.ts", "examples/**/*.ts", ],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
|
||||
Reference in New Issue
Block a user