Skip to content

Commit

Permalink
Merge pull request #663 from exadel-inc/bugfix/update-isolation-behav…
Browse files Browse the repository at this point in the history
…iours

fix: update isolation behavior and overall bugfixes
  • Loading branch information
ala-n authored Feb 15, 2024
2 parents 1623de6 + 7c02be5 commit bc0b2f5
Show file tree
Hide file tree
Showing 13 changed files with 134 additions and 37 deletions.
2 changes: 1 addition & 1 deletion site/views/examples/example/form.njk
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,5 @@ title: Example with form
</uip-settings>

<uip-editor copy collapsible label="Markdown"></uip-editor>
<uip-editor script copy collapsible label="JavaScript"></uip-editor>
<uip-editor source="js" copy collapsible label="JavaScript"></uip-editor>
</uip-root>
39 changes: 39 additions & 0 deletions src/core/base/model.change.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {overrideEvent} from '@exadel/esl/modules/esl-utils/dom';

import type {UIPPlugin} from './plugin';
import type {UIPRoot} from './root';
import type {UIPStateModel} from './model';

export type UIPChangeInfo = {
modifier: UIPPlugin | UIPRoot;
type: 'html' | 'js';
};

export class UIPChangeEvent extends Event {
public readonly target: UIPRoot;

public constructor(
type: string,
target: UIPRoot,
public readonly changes: UIPChangeInfo[]
) {
super(type, {bubbles: false, cancelable: false});
overrideEvent(this, 'target', target);
}

public get model(): UIPStateModel {
return this.target.model;
}

public get jsChanges(): UIPChangeInfo[] {
return this.changes.filter((change) => change.type === 'js');
}

public get htmlChanges(): UIPChangeInfo[] {
return this.changes.filter((change) => change.type === 'html');
}

public isOnlyModifier(modifier: UIPPlugin | UIPRoot): boolean {
return this.changes.every((change) => change.modifier === modifier);
}
}
26 changes: 14 additions & 12 deletions src/core/base/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {UIPSnippetItem} from './snippet';
import type {UIPRoot} from './root';
import type {UIPPlugin} from './plugin';
import type {UIPSnippetTemplate} from './snippet';
import type {UIPChangeInfo} from './model.change';

/** Type for function to change attribute's current value */
export type TransformSignature = (
Expand Down Expand Up @@ -46,8 +47,9 @@ export class UIPStateModel extends SyntheticEventTarget {
private _js: string = '';
/** Current markup state */
private _html = new DOMParser().parseFromString('', 'text/html').body;
/** Last {@link UIPPlugin} element which changed markup */
private _lastModifier: UIPPlugin | UIPRoot;

/** Last changes history (used to dispatch changes) */
private _changes: UIPChangeInfo[] = [];

/**
* Sets current js state to the passed one
Expand All @@ -58,7 +60,7 @@ export class UIPStateModel extends SyntheticEventTarget {
const script = UIPJSNormalizationPreprocessors.preprocess(js);
if (this._js === script) return;
this._js = script;
this._lastModifier = modifier;
this._changes.push({modifier, type: 'js'});
this.dispatchChange();
}

Expand All @@ -72,7 +74,7 @@ export class UIPStateModel extends SyntheticEventTarget {
const root = new DOMParser().parseFromString(html, 'text/html').body;
if (!root || root.innerHTML.trim() !== this.html.trim()) {
this._html = root;
this._lastModifier = modifier;
this._changes.push({modifier, type: 'html'});
this.dispatchChange();
}
}
Expand All @@ -87,11 +89,6 @@ export class UIPStateModel extends SyntheticEventTarget {
return this._html ? this._html.innerHTML : '';
}

/** Last markup state modifier */
public get lastModifier(): UIPPlugin | UIPRoot {
return this._lastModifier;
}

/** Snippets template-holders getter */
public get snippets(): UIPSnippetItem[] {
return this._snippets;
Expand All @@ -105,11 +102,12 @@ export class UIPStateModel extends SyntheticEventTarget {
});
}

/** Current active {@link SnippetTemplate} getter */
/** Current active {@link UIPSnippetItem} getter */
public get activeSnippet(): UIPSnippetItem | undefined {
return this._snippets.find((snippet) => snippet.active);
}

/** Snippet that relates to current anchor */
public get anchorSnippet(): UIPSnippetItem | undefined {
const anchor = window.location.hash.slice(1);
return this._snippets.find((snippet) => snippet.anchor === anchor);
Expand Down Expand Up @@ -156,15 +154,19 @@ export class UIPStateModel extends SyntheticEventTarget {
attribute,
'transform' in cfg ? cfg.transform : cfg.value
);
this._lastModifier = modifier;
this._changes.push({modifier, type: 'html'});
this.dispatchChange();
}

/** Plans microtask to dispatch model change event */
@decorate(microtask)
protected dispatchChange(): void {
if (!this._changes.length) return;
const changes = this._changes;
this._changes = [];

this.dispatchEvent(
new CustomEvent('uip:model:change', {bubbles: true, detail: this})
new CustomEvent('uip:model:change', {bubbles: true, detail: changes})
);
}

Expand Down
5 changes: 3 additions & 2 deletions src/core/base/root.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import {UIPStateModel} from './model';

import type {UIPSnippetTemplate} from './snippet';
import {UIPChangeEvent, UIPChangeInfo} from './model.change';

/**
* UI Playground root custom element definition
Expand Down Expand Up @@ -87,8 +88,8 @@ export class UIPRoot extends ESLBaseElement {
}

@listen({event: 'uip:model:change', target: ($this: UIPRoot) => $this.model})
protected onModelChange({detail}: CustomEvent): void {
this.$$fire(this.CHANGE_EVENT, {detail, bubbles: false});
protected onModelChange({detail}: CustomEvent<UIPChangeInfo[]>): void {
this.dispatchEvent(new UIPChangeEvent(this.CHANGE_EVENT, this, detail));
}

@listen({
Expand Down
4 changes: 2 additions & 2 deletions src/core/base/snippet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ export class UIPSnippetItem {

@memoize()
public get $elementJS(): UIPSnippetTemplate | null {
const $root = this.$element.closest('uip-root') || document.body;
const $root: Element = this.$element.closest('uip-root') || document.body;
const selectors = [];
if (this.$element.id) selectors.push(`[uip-js-snippet="${this.$element.id}"]`);
if (this.label) selectors.push(`[uip-js-snippet][label="${this.label}"]`);
return $root.querySelector(selectors.join(',')) as UIPSnippetTemplate;
return $root.querySelector(selectors.join(','))!;
}

/** @returns snippet's label */
Expand Down
4 changes: 4 additions & 0 deletions src/core/panel/plugin-panel.less
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
display: none;
}

&[collapsed] .uip-plugin-inner {
visibility: hidden;
}

--uip-plugin-width: 250px;
--uip-plugin-height: 325px;
}
Expand Down
18 changes: 15 additions & 3 deletions src/core/preview/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {UIPPlugin} from '../base/plugin';
import {UIPRenderingTemplatesService} from '../processors/templates';
import {UIPJSRenderingPreprocessors, UIPHTMLRenderingPreprocessors} from '../processors/rendering';

import type {UIPChangeEvent} from '../base/model.change';
import type {ESLIntersectionEvent} from '@exadel/esl/modules/esl-event-listener/core';

/**
Expand All @@ -24,6 +25,8 @@ export class UIPPreview extends UIPPlugin {

/** Marker to use iframe isolated rendering */
@attr({parser: parseBoolean, serializer: toBooleanAttribute}) public isolation: boolean;
/** Marker to use iframe isolated rendering */
@attr({parser: parseBoolean, serializer: toBooleanAttribute}) public forceUpdate: boolean;
/** Template to use for isolated rendering */
@attr({defaultValue: 'default'}) public isolationTemplate: string;

Expand Down Expand Up @@ -73,6 +76,12 @@ export class UIPPreview extends UIPPlugin {
}
}

protected update(e?: UIPChangeEvent): void {
if (!this.isolation) return this.writeContent();
if (!e || e.jsChanges.length || this.forceUpdate) this.writeContentIsolated();
this._onIframeLoad();
}

/** Writes the content directly to the inner area (non-isolated frame) */
protected writeContent(): void {
this.$inner.innerHTML = UIPHTMLRenderingPreprocessors.preprocess(this.model!.html);
Expand All @@ -97,8 +106,11 @@ export class UIPPreview extends UIPPlugin {
if (this._iframeResizeRAF) cancelAnimationFrame(this._iframeResizeRAF);
// Addition loop fallback for iframe removal
if (this.$iframe.parentElement !== this.$inner) return;
const $document = this.$iframe.contentWindow?.document;
const $root = $document?.querySelector('[uip-content-root]') || $document?.body;
if (!$root) return;
// Reflect iframe height with inner content
this.$iframe.style.height = `${this.$iframe.contentWindow?.document.body.scrollHeight}px`;
this.$iframe.style.height = `${$root.scrollHeight}px`;
this._iframeResizeRAF = requestAnimationFrame(this.startIframeResizeLoop.bind(this));
}

Expand Down Expand Up @@ -140,9 +152,9 @@ export class UIPPreview extends UIPPlugin {

/** Updates preview content from the model state changes */
@listen({event: 'uip:change', target: ($this: UIPPreview) => $this.$root})
protected _onRootStateChange(): void {
protected _onRootStateChange(e?: UIPChangeEvent): void {
this.$container.style.minHeight = `${this.$inner.offsetHeight}px`;
this.isolation ? this.writeContentIsolated() : this.writeContent();
this.update(e);

afterNextRender(() => this.$container.style.minHeight = '0px');
skipOneRender(() => {
Expand Down
4 changes: 3 additions & 1 deletion src/core/processors/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ UIPRenderingTemplatesService.add('default', `
<base href="${location.origin}"/>
<script type="module">{script}</script>
</head>
<body>{content}</body>
<body style="overflow: hidden;">
<div uip-content-root>{content}</div>
</body>
</html>
`);
1 change: 1 addition & 0 deletions src/plugins/copy/copy-button.shape.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {ESLBaseElementShape} from '@exadel/esl/modules/esl-base-element/cor
import type {UIPCopy} from './copy-button';

export interface UIPCopyShape extends ESLBaseElementShape<UIPCopy> {
source?: 'javascript' | 'js' | 'html';
children?: any;
}

Expand Down
22 changes: 19 additions & 3 deletions src/plugins/copy/copy-button.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,35 @@
import './copy-button.shape';

import {attr} from '@exadel/esl/modules/esl-utils/decorators';
import {UIPPluginButton} from '../../core/button/plugin-button';

import type {AlertActionParams} from '@exadel/esl/modules/esl-alert/core';
import type {ESLAlertActionParams} from '@exadel/esl/modules/esl-alert/core';

/** Button-plugin to copy snippet to clipboard */
export class UIPCopy extends UIPPluginButton {
public static override is = 'uip-copy';
public static override defaultTitle = 'Copy to clipboard';

public static msgConfig: AlertActionParams = {
/** Source type to copy (html | js) */
@attr({defaultValue: 'html'}) public source: string;

public static msgConfig: ESLAlertActionParams = {
text: 'Playground content copied to clipboard',
cls: 'uip-copy-alert'
};

/** Content to copy */
protected get content(): string | undefined {
switch (this.source) {
case 'js':
case 'javascript':
return this.model?.js;
case 'html':
default:
return this.model?.html;
}
}

protected override connectedCallback(): void {
if (!navigator.clipboard) this.hidden = true;
super.connectedCallback();
Expand All @@ -31,6 +47,6 @@ export class UIPCopy extends UIPPluginButton {

/** Copy model content to clipboard */
public copy(): Promise<void> {
return navigator.clipboard.writeText(this.model!.html);
return navigator.clipboard.writeText(this.content || '');
}
}
2 changes: 1 addition & 1 deletion src/plugins/editor/editor.less
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.uip-editor {
grid-area: editor;

&[script] {
&:is([source='js'], [source='javascript']) {
grid-area: editor-js;
}

Expand Down
37 changes: 27 additions & 10 deletions src/plugins/editor/editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import Prism from 'prismjs';
import {CodeJar} from 'codejar';

import {debounce} from '@exadel/esl/modules/esl-utils/async/debounce';
import {boolAttr, decorate, listen, memoize} from '@exadel/esl/modules/esl-utils/decorators';
import {attr, boolAttr, decorate, listen, memoize} from '@exadel/esl/modules/esl-utils/decorators';

import {UIPPluginPanel} from '../../core/panel/plugin-panel';
import {CopyIcon} from '../copy/copy-button.icon';

import {EditorIcon} from './editor.icon';

import type {UIPChangeEvent} from '../../core/base/model.change';

/**
* Editor {@link UIPPlugin} custom element definition
* Uses Codejar code editor to provide an ability to modify UIP state markup
Expand All @@ -26,8 +28,8 @@ export class UIPEditor extends UIPPluginPanel {
/** Highlight method declaration */
public static highlight = (editor: HTMLElement): void => Prism.highlightElement(editor, false);

/** Marker of JS Editor */
@boolAttr() public script: boolean;
/** Source for Editor plugin (default: 'html') */
@attr({defaultValue: 'html'}) public source: 'js' | 'javascript' | 'html';

/** Marker to display copy widget */
@boolAttr({name: 'copy'}) public showCopy: boolean;
Expand All @@ -41,7 +43,7 @@ export class UIPEditor extends UIPPluginPanel {
const type = this.constructor as typeof UIPEditor;
return (
<div class={type.is + '-toolbar uip-plugin-header-toolbar'}>
{this.showCopy ? <uip-copy class={type.is + '-header-copy'}><CopyIcon/></uip-copy> : ''}
{this.showCopy ? <uip-copy class={type.is + '-header-copy'} source={this.source}><CopyIcon/></uip-copy> : ''}
</div>
) as HTMLElement;
}
Expand All @@ -64,7 +66,7 @@ export class UIPEditor extends UIPPluginPanel {
@memoize()
protected get $code(): HTMLElement {
const type = this.constructor as typeof UIPEditor;
const lang = this.script ? 'javascript' : 'html';
const lang = this.source === 'js' ? 'javascript' : this.source;
return (<pre class={type.is + '-code language-' + lang}><code/></pre>) as HTMLElement;
}

Expand Down Expand Up @@ -116,14 +118,29 @@ export class UIPEditor extends UIPPluginPanel {
/** Callback to call on an editor's content changes */
@decorate(debounce, 2000)
protected _onChange(): void {
if (this.script) this.model!.setJS(this.value, this);
else this.model!.setHtml(this.value, this);
switch (this.source) {
case 'js':
case 'javascript':
this.model!.setJS(this.value, this);
break;
case 'html':
this.model!.setHtml(this.value, this);
}
}

/** Change editor's markup from markup state changes */
@listen({event: 'uip:change', target: ($this: UIPEditor) => $this.$root})
protected _onRootStateChange(): void {
if (this.model!.lastModifier === this) return;
this.value = this.script ? this.model!.js : this.model!.html;
protected _onRootStateChange(e?: UIPChangeEvent): void {
if (e && e.isOnlyModifier(this)) return;
switch (this.source) {
case 'js':
case 'javascript':
if (e && !e.jsChanges.length) return;
this.value = this.model!.js;
break;
case 'html':
if (e && !e.htmlChanges.length) return;
this.value = this.model!.html;
}
}
}
Loading

0 comments on commit bc0b2f5

Please sign in to comment.