diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/sections/packages/packages-installed-item.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/sections/packages/packages-installed-item.element.ts
new file mode 100644
index 0000000000..c87bee2d30
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/sections/packages/packages-installed-item.element.ts
@@ -0,0 +1,94 @@
+import { html, LitElement, nothing } from 'lit';
+import { customElement, property, state } from 'lit/decorators.js';
+import { firstValueFrom, map } from 'rxjs';
+
+import { UmbContextConsumerMixin } from '../../../core/context';
+import { createExtensionElement, UmbExtensionRegistry } from '../../../core/extension';
+
+import type { ManifestPackageView, PackageInstalled } from '../../../core/models';
+import type { UmbModalService } from '../../../core/services/modal';
+
+@customElement('umb-packages-installed-item')
+export class UmbPackagesInstalledItem extends UmbContextConsumerMixin(LitElement) {
+ @property({ type: Object })
+ package!: PackageInstalled;
+
+ @state()
+ private _packageView?: ManifestPackageView;
+
+ private _umbExtensionRegistry?: UmbExtensionRegistry;
+ private _umbModalService?: UmbModalService;
+
+ constructor() {
+ super();
+
+ this.consumeContext('umbExtensionRegistry', (umbExtensionRegistry: UmbExtensionRegistry) => {
+ this._umbExtensionRegistry = umbExtensionRegistry;
+
+ this.findPackageView(this.package.alias);
+ });
+
+ this.consumeContext('umbModalService', (modalService: UmbModalService) => {
+ this._umbModalService = modalService;
+ });
+ }
+
+ private async findPackageView(alias: string) {
+ const observable = this._umbExtensionRegistry
+ ?.extensionsOfType('packageView')
+ .pipe(map((e) => e.filter((m) => m.meta.packageAlias === alias)));
+
+ if (!observable) {
+ return;
+ }
+
+ const views = await firstValueFrom(observable);
+ if (!views.length) {
+ return;
+ }
+
+ this._packageView = views[0];
+ }
+
+ render() {
+ return html`
+
+
+ ${this._packageView
+ ? html``
+ : nothing}
+
+
+ `;
+ }
+
+ private async _onConfigure() {
+ if (!this._packageView?.elementName) {
+ console.warn('Tried to configure package without view');
+ return;
+ }
+
+ const element = await createExtensionElement(this._packageView);
+
+ if (!element) {
+ console.warn('Failed to create package view element');
+ return;
+ }
+
+ this._umbModalService?.open(element, { data: this.package, size: 'small', type: 'sidebar' });
+ }
+
+ private _onClick() {
+ window.history.pushState({}, '', `/section/packages/view/repo/${this.package.id}`);
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'umb-packages-installed-item': UmbPackagesInstalledItem;
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/sections/packages/packages-installed.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/sections/packages/packages-installed.element.ts
index 6c5a921116..ae4321a076 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/sections/packages/packages-installed.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/sections/packages/packages-installed.element.ts
@@ -1,30 +1,23 @@
+import './packages-installed-item.element';
+
import { html, LitElement, nothing } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import { repeat } from 'lit/directives/repeat.js';
-import { until } from 'lit/directives/until.js';
-import { firstValueFrom, map } from 'rxjs';
import { getPackagesInstalled } from '../../../core/api/fetcher';
-import { UmbContextConsumerMixin } from '../../../core/context';
-import { UmbExtensionRegistry } from '../../../core/extension';
import type { PackageInstalled } from '../../../core/models';
+
@customElement('umb-packages-installed')
-export class UmbPackagesInstalled extends UmbContextConsumerMixin(LitElement) {
+export class UmbPackagesInstalled extends LitElement {
@state()
private _installedPackages: PackageInstalled[] = [];
@state()
private _errorMessage = '';
- private umbExtensionRegistry?: UmbExtensionRegistry;
-
constructor() {
super();
-
- this.consumeContext('umbExtensionRegistry', (umbExtensionRegistry: UmbExtensionRegistry) => {
- this.umbExtensionRegistry = umbExtensionRegistry;
- });
}
connectedCallback(): void {
@@ -52,24 +45,6 @@ export class UmbPackagesInstalled extends UmbContextConsumerMixin(LitElement) {
}
}
- private findPackageView(alias: string) {
- const observable = this.umbExtensionRegistry
- ?.extensionsOfType('packageView')
- .pipe(map((e) => e.filter((m) => m.meta.packageAlias === alias)));
- return observable ? firstValueFrom(observable) : undefined;
- }
-
- async renderPackage(p: PackageInstalled) {
- const packageView = await this.findPackageView(p.alias);
- return html`
-
-
- ${packageView?.length ? html`` : nothing}
-
-
- `;
- }
-
render() {
return html`
@@ -79,7 +54,7 @@ export class UmbPackagesInstalled extends UmbContextConsumerMixin(LitElement) {
${repeat(
this._installedPackages,
(item) => item.id,
- (item) => until(this.renderPackage(item), 'Loading...')
+ (item) => html``
)}