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

chore(TS): controls #8400

Merged
merged 115 commits into from
Oct 30, 2022
Merged

chore(TS): controls #8400

merged 115 commits into from
Oct 30, 2022

Conversation

ShaMan123
Copy link
Contributor

@ShaMan123 ShaMan123 commented Oct 29, 2022

Motivation

Description

Migrating controls:

  • modularize
  • type
  • consumed normalizePoint into getLocalPoint

Proposed Changes for Follow Up

  • As discussed previously I want to make the Control class bound to an Object. This will make existing Control signatures redundant since fabricObject will be a class property. This means controls will no longer be shared, another over discussed topic I advocate. In purpose I didn't do it in this PR (as @asturur requested not to over refactor) so we can migrate most of controls with no major disagreements. Something worth looking at is that there are 2 signatures XXX(eventData, control, fabricObject) and XXX(eventData, fabricObject, control). That is weird. But if we agree on bound controls both will become XXX(eventData). If not we should change them for consistency.
  • Refactor wrappers to behave like middleware? mutating context and passing it on to the next action. This will remove any need to create wrappers in run time, seems to be easier to understand and reduces code.
const skewX = (...params) => [
   isLocked, // flag context somehow to disable next calls
   saveOrigin, // stores origin on context,
   prepareSkewingTransform, // mutates context
   skewObject, // uses the mutated context and set the result value
   restoreOrigin, // uses the mutated context
   fireEvent // uses the action result
].reduce((context, action) => action(context), prepareContext(...params))

return this.canvas.viewportTransform;
}
return iMatrix.concat() as TMat2D;
return this.canvas?.viewportTransform || (iMatrix.concat() as TMat2D);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i m not sure we are transpiling those yet.
But the issue is that if undefined is not involved those becomes longer.
if typeof canvas !== 'undefined' rather than a simple if this.canvas

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please elaborate
I lack the knowledge

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the diff between this syntax and obj?.[key] ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

none, but where we use obj?.[key] we were already looking for undefined ( ex: the overrideStyle object ). Here we want just to check if canvas is falsy in any way.

obj.first?.second

const temp = obj.first;
const nestedProp =
  temp === null || temp === undefined ? undefined : temp.second;

If we land on supported browsers that do not support the ?. all those equality check get added in the codebase. I just don't like to think all that stuff gets added in.

Not a big issue anyway

@asturur
Copy link
Member

asturur commented Oct 29, 2022

Seems all good to me apart:

  • the extra this argument in the renderCircle and renderSquare control
  • the renaming of the cursor style functions
  • i have to look into that getActionFromCorner

@asturur
Copy link
Member

asturur commented Oct 29, 2022

Regarding the tasks you proposed , piping the function is a well known pattern as good as another, there is no reason to change it, it allows for reusability, keeps functions with a single concerns, is newish ( added in 4.0 ) i don't see the reason to change it, is just added chaos.

Regarding sharing or no, that is really a no.
You can still make NON shared controls if you want, creating them new for each object. but if you embed that in the code in the library you can't do the contrary anymore. Why add this limitation?

I m totaly happy with a control set that is able to render itslef moving from object to object rather than many copies of it one per object.

@ShaMan123
Copy link
Contributor Author

ShaMan123 commented Oct 29, 2022

Regarding sharing or no, that is really a no.

We will remain disagreed on this topic
The why is because it is very unintuitive. The same as the discussion we had on default values. Exactly IMO. A dev wanting a change to propagate should loop over existing objects. Elsewise it is a reactive mobx kind of thing.
I view controls as part of Object, you view it as a separate layer as far as I can tell.
A dev mutating that object will never understand that he changed the entire app.

curAngle = Math.atan2(y - pivotPoint.y, x - pivotPoint.x);
let angle = radiansToDegrees(curAngle - lastAngle + theta);

if (target.snapAngle && target.snapAngle > 0) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

@ShaMan123 ShaMan123 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added isLocked util, not very useful but at least it makes it simple to find all the locking logic
Extracting to a wrapper will cause too many unnecessary code changes

@ShaMan123
Copy link
Contributor Author

ShaMan123 commented Oct 30, 2022

Regarding sharing or no, that is really a no.

We will remain disagreed on this topic The why is because it is very unintuitive. The same as the discussion we had on default values. Exactly IMO. A dev wanting a change to propagate should loop over existing objects. Elsewise it is a reactive mobx kind of thing. I view controls as part of Object, you view it as a separate layer as far as I can tell. A dev mutating that object will never understand that he changed the entire app.

@asturur I have a compromise.
If we expose a class ControlSet that is basically a set of controls.
The class will hold the original control set ref as follows:

class ControlSet {
  defaultControls: Record<string, Control>;
  controls: Record<string, Control>;

  constructor(controls: Record<string, Control> = {}) {
    this.defaultControls = controls;
  }
  getControl(key: string) {
    return this.controls[key] || this.defaultControls[key];
  }
  setControl(key: string, control: Control) {
    this.controls[key] = control;
  }
}

This is a win win solution.
For your side no one will touch this ControlSet class, but continue as previously by mutating the ref that was passed to the ControlSet (and if necessary could pass a new ControlSet to the object).
For my side the dev can use setControl and is safeguarded from unwanted sharing. No need for a gotcha/documentation/issues.
We keep what you want and make it intuitive.
I hope I convinced you.
Awaiting your response.

And I can fix the derivation of textbox controls with this approach

@ShaMan123
Copy link
Contributor Author

ShaMan123 commented Oct 30, 2022

And I can fix the derivation of textbox controls with this approach

A win-win-win you-me-textbox

class ControlSet {
  defaultControls: Record<string, Control> | ControlSet;
  controls: Record<string, Control>;

  constructor(controls: Record<string, Control> | ControlSet = {}) {
    this.defaultControls = controls;
  }
  getControl(key: string): Control {
    return (
      this.controls[key] ||
      (this.defaultControls instanceof ControlSet
        ? this.defaultControls.getControl(key)
        : this.defaultControls[key])
    );
  }
  setControl(key: string, control: Control) {
    this.controls[key] = control;
  }
}

const objectControls = new ControlSet(defaultControls);
const textboxControls = new ControlSet(objectControls);
textboxControls.setControl('mr', new Control({}));
objectControls.setControl('a', new Control({}));
textboxControls.getControl('a');

And then on each Object we do the same so when using setControl it changes only the object, while changing the objectControls.defaultControls will change all

new ControlSet(objectControls)
new ControlSet(textboxControls)

@ShaMan123
Copy link
Contributor Author

ShaMan123 commented Oct 30, 2022

It also allows use to move control logic from interactivity.
Things like control visibility etc.

class BoundControlSet {
  constructor(controlSet: ControlSet, object: FabricObject) {
    this.controls = controlSet;
    this.object = object;
  }
  getVisibility(...) {
    
  }
  setVisibility(...) {
    
  }
}

This is a better pattern IMO for making interactivity optional and typed. We encapsulate the logic in dedicated classes instead on FabricObject

Copy link
Contributor Author

@ShaMan123 ShaMan123 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking of defaultControls
I think they should be assigned in the interactivity mixin.
And that textbox should subclass the mixin.
And that we use applyMixins for it instead of subclassing

Not sure though
Thoughts?

@asturur
Copy link
Member

asturur commented Oct 30, 2022

@ShaMan123 i want to do the TS migration, i don't want to do refactors. There is a lot of work to do and the more we do because we think is necessary, the later we can go back to features and website and docs

I disagree on TS and classes the more i write them. peace.

If we want to finish the ts migration we stick to that, we take notes on what we would like to see changed, we organize them and for now we move on head down on the goal

Even the isLocked wrapper wasn't an idea i would have chased now.

@ShaMan123
Copy link
Contributor Author

I understand.
These are notes for us to discuss.
Ideas.
Do you prefer I put them elsewhere? Do you prefer not hearing them?
I don't understand

@ShaMan123
Copy link
Contributor Author

So if I understood you, my comments are throwing you off.
So if I put everything in a discussion(s) that what be better?

@asturur
Copy link
Member

asturur commented Oct 30, 2022

So if I understood you, my comments are throwing you off. So if I put everything in a discussion(s) that what be better?

How can i know that you put them in the PR because you want to discuss them somewhere else another day?
If i see them in the PR i think you want to address them now.

@asturur
Copy link
Member

asturur commented Oct 30, 2022

let's skip getActionFromCorner check up and let's merge this.
I checked and i don't have much memories apart that it was a slimdown of the previous function that was there since long.
The idea was to move Drag in the realm of controls too, so this is probably a safe change

@asturur asturur merged commit d55fabc into master Oct 30, 2022
@asturur asturur deleted the ts-controls branch October 30, 2022 20:17
frankrousseau pushed a commit to cgwire/fabric.js that referenced this pull request Jan 6, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants