-
Notifications
You must be signed in to change notification settings - Fork 3.8k
feat: add extension point for overriding Grid functionality #8751
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
base: develop
Are you sure you want to change the base?
feat: add extension point for overriding Grid functionality #8751
Conversation
|
@gonfunko bump |
gonfunko
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for this change, and apologies for taking a while on the review! Broadly speaking making the grid swappable seems great, but I think there are some structural issues we might want to address while we're creating a new public API. In particular, I think the way things currently work in core is a bit silly, and the existing indirection complicates making this pluggable. However, I think the implementation can be adjusted to make the API cleaner without too much trouble.
As things stand, the flow is:
- Grid options can be specified during the call to
inject() inject()callscreateDom()(in inject.ts), which callsGrid.createDom()and stores the resulting SVG pattern on theoptionsobject- A
WorkspaceSvgis constructed, which creates a newGridobject from the grid options specified in the call toinject()and the SVG pattern created and stashed on theoptionsobject in step 2
Instead, I think we want to:
- Continue to specify grid options during the call to inject, and have those parsed out in options.ts as they currently are
- Do nothing grid-related in the
createDom()in inject.ts. - Have
WorkspaceSvgcontinue to initialize a Grid in its constructor, but (a) get the class from the registry and (b) pass no arguments to the Grid constructor - In
WorkspaceSvg.createDom():
- Call
this.grid.createDom(), which is now an instance method, passing only the parsed grid options - Assign a random ID to the element returned from that method
- Create a element, add the returned element as a child, and add the to the injection div (this currently happens in inject.ts)
This has the following benefits:
- Removes the need for Options.gridPattern, which is the only field there that isn't really a configuration option as such
- Removes the need for the
rnd,defs, and (in v12)injectionDivarguments toGrid.createDom(), which are quite low-level and require the implementation to follow a lot of contracts. Instead, implementers need only concern themselves with creating and returning an SVG pattern, and Blockly will handle its own internal bookkeeping. - Makes
Grid.createDom()an instance method, which works easier with interfaces as you noted - Eliminates the need for
IGridProvider - Allows developers to add their own configuration methods to their implementations of IGrid, which they can use to configure properties, e.g. the image URL, size, opacity etc. that you noted. IMO this is strongly preferable to tacking arbitrary properties on to the
gridinjection option; that has the typing problems you described, and broadly speaking we don't really want to add options there in general. With this approach, after injection, developers can just callworkspace.getGrid().setWhateverConfigOption(); those methods won't be part of theIGridinterface, but since they just injected their own version they can confidently cast the return type ofgetGrid()to their concrete implementation.
| * @returns The pattern ID. | ||
| * @internal | ||
| */ | ||
| getPatternId(): string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggest removing this; it's quite low level, and its only caller in core is in WorkspaceSvg, which can be eliminated easily if we follow the approach I outlined in my top-level comment.
| * @param scale The new workspace scale. | ||
| * @internal | ||
| */ | ||
| update(scale: number): void; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's make this setScale() and getScale() for consistency. The built-in Grid implementation can still call update() to adjust itself in response to changes in its configuration, but that doesn't need to be part of the interface (or require scale as an argument).
| * conflicts with other Blockly instances on the page. | ||
| * | ||
| * @returns The pattern ID. | ||
| * @internal |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Throughout, I don't think we want @internal annotations in the interface; if we're committing it to the interface, that makes it public API.
The basics
The details
Resolves
Fixes #8614
Proposed Changes
Refactors the Grid class so that it can be overridden by a plugin. The static methods for parsing grid options and creating the SVG pattern element have also been moved onto a new GridProvider class which can be passed in as a plugin in the BlocklyOptions object. The implementations of these functions are unchanged; they just got moved around.
Originally, I just wanted these to be static methods on the IGrid interface, but the typings for the registry's
getClassfunctions don't really allow for static properties and I didn't want to tear all that up in this PR.I would love some feedback on the typing of GridOptions here. My example use case for this is the MakeCode Minecraft editor which overrides the grid with a background image that scrolls with the workspace. A plugin that provided that functionality would likely need to extend the GridOptions type with extra fields like the URL of the image, width/height, opacity, etc. However, the
gridproperty of the BlocklyOptions object passed to inject is pretty strongly typed; the only way around it right now is to do a lot of casting toany.Reason for Changes
See the linked issue for reasoning!
Test Coverage
Tested manually in the playground and by creating a test plugin locally. Happy to add unit tests, but I didn't see any for other extension points at a click glance.
Documentation
Additional Information
I can also add a plugin to blockly-samples that utilizes this change should this PR be approved.