Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 0 additions & 49 deletions docs/examples.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,3 @@
## Quick Start

### Install Package
```csharp
dotnet add package Umbraco.Community.SimpleDashboards
```

### Register Dashboard

By default this will display in the content section for Admins only.
```csharp
using Umbraco.Community.SimpleDashboards.Web;
public class BasicDashboard : SimpleDashboard { }
```

### Create View

- Your view **must** go in `/Views/Dashboard`
- You view **must** be the name of your C# class (without `Dashboard`)
- For example: `BasicDashboard.cs` => `/Views/Dashboard/Basic.cshtml`
```csharp
@inherits Umbraco.Community.SimpleDashboards.DashboardViewPage

<uui-box headline="Hello Umbraco">
<p>My Dashboard is: @Model.Dashboard.Alias</p>
</uui-box>
```

## Detailed Register Dashboard

By adding a constructor you can define permissions, where to display and the name of the dashboard.
Expand All @@ -44,24 +16,3 @@ public class ExampleDashboard : SimpleDashboard
}

```

## View Component Example

- Your View Component should match the name of your C# class plus `ViewComponent.cs`
- For example: `BasicDashboard.cs` => `BasicDashboardViewComponent.cs`
- Your View Component **must** inherit either:
- `DashboardViewComponent`
- `DashboardAsyncViewComponent`

```csharp
public class ExampleDashboardViewComponent : DashboardAsyncViewComponent
{
public override Task<IViewComponentResult> InvokeAsync(DashboardViewModel model)
{
// Complex business logic
var viewModel = await _service.CreateViewModel(model);
// ...
return View("~/Views/MyPath/MyView.cshtml", viewModel);
}
}
```
20 changes: 20 additions & 0 deletions docs/view-components.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
## View Component Example

- Your View Component should match the name of your C# class plus `ViewComponent.cs`
- For example: `BasicDashboard.cs` => `BasicDashboardViewComponent.cs`
- Your View Component **must** inherit either:
- `DashboardViewComponent`
- `DashboardAsyncViewComponent`

```csharp
public class ExampleDashboardViewComponent : DashboardAsyncViewComponent
{
public override Task<IViewComponentResult> InvokeAsync(DashboardViewModel model)
{
// Complex business logic
var viewModel = await _service.CreateViewModel(model);
// ...
return View("~/Views/MyPath/MyView.cshtml", viewModel);
}
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {UUITextStyles} from "@umbraco-cms/backoffice/external/uui";
import {SIMPLE_DASHBOARDS_CONTEXT_TOKEN} from "../context/simple-dashboards.context";
import {unsafeHTML} from 'lit/directives/unsafe-html.js';
import {ManifestDashboard} from "@umbraco-cms/backoffice/dashboard";
import {HtmlScriptContentRuntime} from "../utils/html-script-content-runtime.ts";

@customElement('simple-dashboard')
export class SimpleDashboard extends UmbElementMixin(LitElement) {
Expand All @@ -16,6 +17,8 @@ export class SimpleDashboard extends UmbElementMixin(LitElement) {
@state()
dashboardAlias?: string;

private readonly runtime = new HtmlScriptContentRuntime();

constructor() {
super();
this.consumeContext(SIMPLE_DASHBOARDS_CONTEXT_TOKEN, async (context) => {
Expand All @@ -32,20 +35,24 @@ export class SimpleDashboard extends UmbElementMixin(LitElement) {
});
}

protected updated(): void {
void this.runtime.executeScripts(this.content, this.renderRoot);
}

render() {
if (this.loading) {
return nothing;
}

const body = this.runtime.extractHtml(this.content);
return html`
<div class="uui-text">
${this.content ? unsafeHTML(this.content) : html`<p>Dashboard not found</p>`}
${body ? unsafeHTML(body) : html`<p>Dashboard not found</p>`}
</div>
`
`;
}

static
styles = [
static styles = [
UUITextStyles,
css`
:host {
Expand Down
2 changes: 1 addition & 1 deletion src/Umbraco.Community.SimpleDashboards.Client/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {UMB_AUTH_CONTEXT} from "@umbraco-cms/backoffice/auth";
import {client} from './api';
import {UmbEntryPointOnInit} from "@umbraco-cms/backoffice/extension-api";
import type {UmbEntryPointOnInit} from "@umbraco-cms/backoffice/extension-api";
import './components/simple-dashboard.ts';
import {SimpleDashboardsContext} from "./context/simple-dashboards.context.ts";
import {ManifestLocalizations} from "./lang/manifests.ts";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
type ScriptInfo = {
src?: string;
text: string;
type?: string;
};

export class HtmlScriptContentRuntime {
private lastContent?: string;

private parse(content: string) {
const el = document.createElement('div');
el.innerHTML = content;
const scriptEls = Array.from(el.querySelectorAll('script'));
const scripts = scriptEls.map((s) => ({
src: s.getAttribute('src') ?? undefined,
text: s.textContent ?? '',
type: s.getAttribute('type') ?? undefined,
}));
scriptEls.forEach((s) => s.remove());
return { html: el.innerHTML, scripts };
}

extractHtml(content?: string): string {
return content ? this.parse(content).html : '';
}

async executeScripts(content: string | undefined, root: ParentNode = document): Promise<void> {
if (!content || content === this.lastContent) {
return;
}

this.lastContent = content;

for (const script of this.parse(content).scripts) {
try {
await this.run(script, root);
} catch (error) {
console.error(error);
}
}
}

private run(script: ScriptInfo, root: ParentNode): Promise<void> {
return new Promise<void>((resolve, reject) => {
if (!script.src) {
try {
new Function('document', 'window', script.text)(this.scopedDocument(root), window);
resolve();
} catch (error) {
reject(error);
}
return;
}

const el = Object.assign(document.createElement('script'), {
src: script.src,
type: script.type ?? 'text/javascript',
onload: resolve,
onerror: () => reject(new Error(`Failed to load script: ${script.src}`)),
});

document.head.appendChild(el);
});
}

private scopedDocument(root: ParentNode): Document {
return new Proxy(document, {
get: (target, prop, receiver) => {
if (prop === 'getElementById') {
return (id: string) => (root as any).getElementById?.(id) ?? target.getElementById(id);
}

if (prop === 'querySelector') {
return (sel: string) => root.querySelector(sel) ?? target.querySelector(sel);
}

if (prop === 'querySelectorAll') {
return (sel: string) => {
const local = root.querySelectorAll(sel);
return local.length ? local : target.querySelectorAll(sel);
};
}

return Reflect.get(target, prop, receiver);
},
}) as Document;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,4 @@

namespace Umbraco.Community.SimpleDashboards.TestSite.Dashboards;

public class BasicDashboard : SimpleDashboard
{
}
public class BasicDashboard : SimpleDashboard;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using Umbraco.Community.SimpleDashboards.Web;

namespace Umbraco.Community.SimpleDashboards.TestSite.Dashboards;

public class ScriptDashboard : SimpleDashboard;
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
@inherits Umbraco.Community.SimpleDashboards.Web.DashboardViewPage

<uui-box headline="Script Example">
<p>You can even include scripts!</p>
<button id="clicky">Count 0</button>
<div id="clock">
</div>
</uui-box>

<script>
let count = 0;
document.getElementById("clicky").addEventListener("click", function (e) {
count++;
e.target.textContent = `Count ${count}`;
});

const clock = document.getElementById("clock");
updateClock = () => {
const now = new Date();
clock.textContent = `The time is ${now.toLocaleTimeString()}`;
}
setInterval(() => {
updateClock();
}, 1000);

updateClock();
</script>
Loading