Browse Source

Add custom wrapper support to frames and refComponent

improve-page-frames
Artur Arseniev 2 months ago
parent
commit
3ec9d705aa
  1. 19
      packages/core/src/canvas/model/Frame.ts
  2. 53
      packages/core/src/canvas/view/FrameView.ts
  3. 1
      packages/core/src/dom_components/index.ts
  4. 92
      packages/core/test/specs/pages/index.ts

19
packages/core/src/canvas/model/Frame.ts

@ -2,6 +2,7 @@ import { forEach, isEmpty, isNumber, isString, keys, result } from 'underscore';
import CanvasModule from '..';
import { ModuleModel } from '../../abstract';
import { BoxRect, PrevToNewIdMap } from '../../common';
import type Component from '../../dom_components/model/Component';
import ComponentWrapper from '../../dom_components/model/ComponentWrapper';
import { ComponentDefinition } from '../../dom_components/model/types';
import Page from '../../pages/model/Page';
@ -16,7 +17,7 @@ const keyAutoH = '__ah';
export interface FrameProperties {
id?: string;
component?: string | ComponentDefinition | ComponentDefinition[] | ComponentWrapper;
component?: string | ComponentDefinition | ComponentDefinition[] | Component;
width?: string | number | null;
height?: string | number | null;
x?: number;
@ -25,6 +26,7 @@ export interface FrameProperties {
head?: { tag: string; attributes: any }[];
styles?: string | CssRuleJSON[];
refFrame?: string | Frame | null;
refComponent?: string | Component | null;
skipFromStorage?: boolean;
[key: string]: unknown;
}
@ -87,7 +89,7 @@ export default class Frame extends ModuleModel<CanvasModule> {
if (!isComponent(component)) {
const wrp = isObject(component) ? component : { components: component };
!wrp.type && (wrp.type = 'wrapper');
const Wrapper = domc.getType('wrapper')!.model;
const Wrapper = (domc.getType(wrp.type as string) || domc.getType('wrapper')!).model;
this.set('component', new Wrapper(wrp, modOpts));
}
@ -128,17 +130,25 @@ export default class Frame extends ModuleModel<CanvasModule> {
return this.get('refFrame');
}
get refComponent(): Component | undefined {
return this.get('refComponent');
}
get root() {
const { refFrame } = this;
return refFrame?.getComponent() || this.getComponent();
}
initRefs() {
const { refFrame } = this;
const { refFrame, refComponent, em } = this;
if (isString(refFrame)) {
const frame = this.module.framesById[refFrame];
frame && this.set({ refFrame: frame }, { silent: true });
}
if (isString(refComponent)) {
const component = em.Components.getById(refComponent);
component && this.set({ refComponent: component }, { silent: true });
}
}
getBoxRect(): BoxRect {
@ -154,7 +164,7 @@ export default class Frame extends ModuleModel<CanvasModule> {
}
onRemove() {
!this.refFrame && this.getComponent().remove({ root: 1 });
!this.refFrame && !this.refComponent && this.getComponent().remove({ root: 1 });
}
changesUp(opt: any = {}) {
@ -271,6 +281,7 @@ export default class Frame extends ModuleModel<CanvasModule> {
if (opts.fromUndo) delete obj.component;
delete obj.skipFromStorage;
delete obj.refComponent;
delete obj.styles;
delete obj.changesCount;
obj[keyAutoW] && delete obj.width;

53
packages/core/src/canvas/view/FrameView.ts

@ -1,6 +1,6 @@
import { bindAll, debounce, isFunction, isString } from 'underscore';
import { ModuleView } from '../../abstract';
import { BoxRect, ObjectAny } from '../../common';
import { BoxRect } from '../../common';
import CssRulesView from '../../css_composer/view/CssRulesView';
import { type as typeHead } from '../../dom_components/model/ComponentHead';
import ComponentView from '../../dom_components/view/ComponentView';
@ -36,6 +36,7 @@ export default class FrameView extends ModuleView<Frame, HTMLIFrameElement> {
private tools: { [key: string]: HTMLElement } = {};
private wrapper?: ComponentWrapperView;
private headView?: ComponentView;
private refComponentView?: ComponentView;
private frameWrapView?: FrameWrapView;
constructor(model: Frame, view?: FrameWrapView) {
@ -145,8 +146,12 @@ export default class FrameView extends ModuleView<Frame, HTMLIFrameElement> {
return this.getDoc().querySelector('body') as HTMLBodyElement;
}
getRootType() {
return this.model.root.get('type') || 'wrapper';
}
getWrapper() {
return this.getBody().querySelector('[data-gjs-type=wrapper]') as HTMLElement;
return (this.wrapper?.el || this.getBody().querySelector(`[data-gjs-type="${this.getRootType()}"]`)) as HTMLElement;
}
getJsContainer() {
@ -177,6 +182,19 @@ export default class FrameView extends ModuleView<Frame, HTMLIFrameElement> {
return this._getTool('[data-offset]');
}
getComponentView(component: any) {
const { Components } = this.em;
const type = component.get('type') || 'default';
const dt = Components.getTypes();
const { view = ComponentView } = Components.getType(type) || Components.getType('default') || {};
if (!view.getEvents) {
view.getEvents = ComponentView.getEvents;
}
return { dt, view };
}
getRect() {
if (!this.rect) {
this.rect = this.el.getBoundingClientRect();
@ -220,6 +238,8 @@ export default class FrameView extends ModuleView<Frame, HTMLIFrameElement> {
remove(...args: any) {
this._toggleEffects(false);
this.tools = {};
this.refComponentView?.remove();
this.refComponentView = undefined;
this.wrapper?.remove();
ModuleView.prototype.remove.apply(this, args);
return this;
@ -350,6 +370,7 @@ export default class FrameView extends ModuleView<Frame, HTMLIFrameElement> {
this.renderStyles({ prev: [] });
const colorWarn = '#ffca6f';
const rootType = this.getRootType();
append(
body,
@ -358,7 +379,7 @@ export default class FrameView extends ModuleView<Frame, HTMLIFrameElement> {
${hasAutoHeight ? 'body { overflow: hidden }' : ''}
[data-gjs-type="wrapper"] {
[data-gjs-type="${rootType}"] {
${!hasAutoHeight ? 'min-height: 100vh;' : ''}
padding-top: 0.001em;
}
@ -426,7 +447,7 @@ export default class FrameView extends ModuleView<Frame, HTMLIFrameElement> {
</style>`,
);
const { root } = model;
const { view } = em?.Components?.getType('wrapper') || {};
const { view } = this.getComponentView(root);
if (!view) return;
if (isFunction(config.customRenderer)) {
@ -497,10 +518,34 @@ export default class FrameView extends ModuleView<Frame, HTMLIFrameElement> {
this.droppable = new Droppable(em, this.wrapper?.el);
}
this.renderRefComponent(rootView);
this.loaded = true;
model.trigger('loaded');
}
renderRefComponent(rootView: ComponentView) {
const frame = this.model;
const refComponent = frame.refComponent;
if (!refComponent) return;
const { dt, view: viewClass } = this.getComponentView(refComponent);
const view =
refComponent.getView(frame) ||
new viewClass({
model: refComponent,
config: {
...refComponent.config,
em: this.em,
frameView: this,
},
componentTypes: dt,
});
this.refComponentView = view.render();
rootView.getChildrenContainer().appendChild(view.el);
}
_toggleEffects(enable: boolean) {
const method = enable ? on : off;
const win = this.getWindow();

1
packages/core/src/dom_components/index.ts

@ -610,6 +610,7 @@ export default class ComponentManager extends ItemManagerModule<DomComponentsCon
...view,
...getExtendedObj(extendFnView, view, viewToExt),
});
// Object.setPrototypeOf(methods.view, viewToExt);
}
if (compType) {

92
packages/core/test/specs/pages/index.ts

@ -382,8 +382,18 @@ describe('Managing pages', () => {
} as any);
expect(pm.getAll().map((page) => page.getId())).toEqual(['page-stored', 'page-skipped']);
expect(pm.get('page-stored')?.getFrames().map((frame) => frame.id)).toEqual(['frame-stored', 'frame-skipped']);
expect(pm.get('page-skipped')?.getFrames().map((frame) => frame.id)).toEqual(['frame-on-skipped-page']);
expect(
pm
.get('page-stored')
?.getFrames()
.map((frame) => frame.id),
).toEqual(['frame-stored', 'frame-skipped']);
expect(
pm
.get('page-skipped')
?.getFrames()
.map((frame) => frame.id),
).toEqual(['frame-on-skipped-page']);
const storedPages = editor.getProjectData().pages;
expect(storedPages.map((page: any) => page.id)).toEqual(['page-stored']);
@ -474,4 +484,82 @@ describe('Pages in canvas', () => {
await waitEditorEvent(em, CanvasEvents.frameLoadBody);
expect(getPageContent()).toEqual('Page 2');
});
test('Page with refComponent renders the same model and keeps original ownership', async () => {
const mainPage = pm.getMain();
const mainWrapper = mainPage.getMainComponent();
const target = mainWrapper.append({
attributes: { id: 'isolated-component' },
content: 'Original content',
})[0];
const tempPage = pm.add(
{
id: 'temp-page',
frames: [{ refComponent: target }],
skipFromStorage: true,
},
{ select: true },
)!;
await waitEditorEvent(em, CanvasEvents.frameLoadBody);
expect(canvas.getBody().querySelector('#isolated-component')?.textContent).toBe('Original content');
expect(target.parent()).toBe(mainWrapper);
target.set('content', 'Updated content');
expect(canvas.getBody().querySelector('#isolated-component')?.textContent).toBe('Updated content');
pm.select(mainPage);
await waitEditorEvent(em, CanvasEvents.frameLoadBody);
expect(canvas.getBody().querySelector('#isolated-component')?.textContent).toBe('Updated content');
pm.remove(tempPage);
expect(mainWrapper.components().models).toContain(target);
expect(target.parent()).toBe(mainWrapper);
});
test('Page supports a custom wrapper type for frames', async () => {
editor.Components.addType('wrapper-component', {
extend: 'wrapper',
model: {
defaults: { customWrapperFlag: true },
getCustomWrapperFlag() {
return this.get('customWrapperFlag');
},
},
view: {
onRender() {
this.el.setAttribute('data-custom-wrapper', 'true');
},
},
});
const mainWrapper = pm.getMain().getMainComponent();
const target = mainWrapper.append({
attributes: { id: 'custom-wrapper-target' },
content: 'Custom wrapper target',
})[0];
const tempPage = pm.add(
{
id: 'temp-page-custom-wrapper',
skipFromStorage: true,
frames: [
{
component: { type: 'wrapper-component' },
refComponent: target,
},
],
},
{ select: true },
)!;
await waitEditorEvent(em, CanvasEvents.frameLoadBody);
const tempWrapper = tempPage.getMainComponent() as any;
expect(tempWrapper.is('wrapper-component')).toBe(true);
expect(tempWrapper.getCustomWrapperFlag()).toBe(true);
expect(canvas.getBody().querySelector('[data-custom-wrapper="true"]')).toBeTruthy();
expect(canvas.getBody().querySelector('#custom-wrapper-target')?.textContent).toBe('Custom wrapper target');
});
});

Loading…
Cancel
Save