Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/optimize morph derivation from component #1696

Merged
merged 3 commits into from
Feb 5, 2025
Merged
Show file tree
Hide file tree
Changes from 2 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
74 changes: 69 additions & 5 deletions lively.morphic/components/policy.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,10 @@ function mergeInHierarchy (
if (specOrPolicyToAdd.isPolicy) specOrPolicyToAdd = specOrPolicyToAdd.spec;

if (morphToReplace) {
if (!specOrPolicyToAdd.position) { specOrPolicyToAdd.position = morphToReplace.spec?.position || morphToReplace.position; }
if (!specOrPolicyToAdd.rotation) { specOrPolicyToAdd.rotation = morphToReplace.spec?.rotation || morphToReplace.rotation; }
if (!specOrPolicyToAdd.hasOwnProperty('position')) { specOrPolicyToAdd.position = morphToReplace.spec?.position || morphToReplace.position; }
if (!specOrPolicyToAdd.hasOwnProperty('rotation')) { specOrPolicyToAdd.rotation = morphToReplace.spec?.rotation || morphToReplace.rotation; }
if (typeof specOrPolicyToAdd.position === 'undefined') delete specOrPolicyToAdd.position;
if (typeof specOrPolicyToAdd.rotation === 'undefined') delete specOrPolicyToAdd.rotation;
specOrPolicyToAdd.name = morphToReplace.name;
addFn(root, cmd.props, morphToReplace);
removeFn(root, morphToReplace);
Expand Down Expand Up @@ -188,7 +190,6 @@ function getEventState (targetMorph, breakpointStore, localComponentStates) {
}

export function withAllViewModelsDo (inst, cb) {
if (inst.master) inst.master.applyIfNeeded(true);
const toAttach = [];
inst.withAllSubmorphsDo(m => {
if (m.viewModel) toAttach.unshift(m);
Expand Down Expand Up @@ -940,12 +941,75 @@ export class StylePolicy {
instantiate (props = {}) {
// we may be able to avoid this explicit wrapping of the policies
// by moving that logic into the master setter at a later stage
const inst = morph(new PolicyApplicator(props, this).asBuildSpec()); // eslint-disable-line no-use-before-define
const inst = morph(new PolicyApplicator(props, this).asFullySynthesizedSpec()); // eslint-disable-line no-use-before-define
// FIXME: This is temporary and should be moved into the viewModel setter after transition is complete.
withAllViewModelsDo(inst, m => m.viewModel.attach(m));
withAllViewModelsDo(inst, m => {
m.viewModel.attach(m); // as fully synthesized spec does not seem to assign all viewModels
});
return inst;
}

asFullySynthesizedSpec () {
// caching does not work that way, since it requires more dynamicity depending on the event/breakpoint state of each morph in the morph hierarchy. Instead we can only cache on a per morph x state basis, always having to assemble the spec ad hoc.
// if (this._cachedFullySynthesized) return this._cachedFullySynthesized;
linusha marked this conversation as resolved.
Show resolved Hide resolved
const extractBuildSpecs = (specOrPolicy, submorphs) => {
if (specOrPolicy.COMMAND === 'add') {
specOrPolicy = specOrPolicy.props;
}
if (specOrPolicy.COMMAND === 'remove') return null; // target is already removed so just ignore the command
if (specOrPolicy.isPolicy) return specOrPolicy.asFullySynthesizedSpec();
const modelClass = specOrPolicy.defaultViewModel || specOrPolicy.viewModelClass;
linusha marked this conversation as resolved.
Show resolved Hide resolved
const modelParams = { ...specOrPolicy.viewModel } || {}; // accumulate the derivation chain for the viewModel
const synthesized = this.synthesizeSubSpec(specOrPolicy === this.spec ? null : specOrPolicy.name);
if (specOrPolicy.name) synthesized.name = specOrPolicy.name;
// remove the props that are equal to the default value
getStylePropertiesFor(specOrPolicy.type).forEach(prop => {
if (synthesized[prop]?.isDefaultValue) {
delete synthesized[prop];
}
if (prop === 'layout' && synthesized[prop]?.isLayout) synthesized[prop] = synthesized[prop].copy();
});
if (synthesized.textAndAttributes) {
synthesized.textAndAttributes = synthesized.textAndAttributes.map(textOrAttr => {
if (textOrAttr?.__isSpec__) return morph(tree.mapTree(textOrAttr, extractBuildSpecs, node => node.submorphs)); // ensure sub build specs...
if (textOrAttr?.isPolicy) return textOrAttr.instantiate();
return textOrAttr;
});
}
if (synthesized.layout) synthesized.layout = synthesized.layout.copy();
if (submorphs.length > 0) {
let transformedSubmorphs = submorphs.filter(spec => spec && !spec.__before__);
for (let spec of submorphs) {
if (spec?.__before__ !== undefined) {
{
const idx = transformedSubmorphs.findIndex(m => m.name === spec.__before__);
arr.pushAt(transformedSubmorphs, spec, idx);
delete spec.__before__;
}
}
}
synthesized.submorphs = transformedSubmorphs;
}
if (modelClass) synthesized.viewModel = new modelClass(modelParams);
else delete synthesized.viewModel;

if (!synthesized.isPolicy) {
return sanitizeSpec(synthesized);
}

return synthesized;
};
const buildSpec = tree.mapTree(this.spec, extractBuildSpecs, node => node.props?.submorphs || node.submorphs);
// do not add a master, since that would tigger an application
linusha marked this conversation as resolved.
Show resolved Hide resolved
const self = this;
buildSpec.onLoad = function () {
const policy = self; // PolicyApplicator.for(this, {}, self); // eslint-disable-line no-use-before-define
linusha marked this conversation as resolved.
Show resolved Hide resolved
this.setProperty('master', policy);
policy.attach(this);
};
return buildSpec;
}

/**
* Synthesizes all contained sub specs up to the first level overridden props,
* in order to create a build spec that can be used to create a new morph hierarchy.
Expand Down
11 changes: 7 additions & 4 deletions lively.morphic/morph.js
Original file line number Diff line number Diff line change
Expand Up @@ -886,6 +886,7 @@ export class Morph {
if (props.__wasAddedToDerived__) this.__wasAddedToDerived__ = true;

if (typeof this.onLoad === 'function' && !this.isComponent) this.onLoad();
if (typeof props.onLoad === 'function' && !this.isComponent) props.onLoad.bind(this)();
}

get __serialization_id_property__ () { return '_id'; }
Expand Down Expand Up @@ -1657,11 +1658,13 @@ export class Morph {
this.makeDirty();
submorph.resumeSteppingAll();

submorph.withAllSubmorphsDo(ea => ea.onOwnerChanged(this));
submorph.withAllSubmorphsDo(ea => {
ea.onOwnerChanged(this);
});

if (submorph._requestMasterStyling) {
submorph.master && submorph.master.applyIfNeeded(true);
submorph._requestMasterStyling = false;
if (this.world() && !this.isText) {
this.env.forceUpdate(this);
this.withAllSubmorphsDo(m => m.makeDirty());
}
});

Expand Down
2 changes: 2 additions & 0 deletions lively.morphic/rendering/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ export default class Renderer {
}
}

this.worldMorph.applyLayoutIfNeeded();

// handling these first allows us to assume correct wrapping, when we have submorphs already!
for (let morph of morphsToHandle) {
if (morph.renderingState.hasCSSLayoutChange) this.renderLayoutChange(morph);
Expand Down