-
Notifications
You must be signed in to change notification settings - Fork 292
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
[REFACTORING] Improve Hedy Editor interface #4435
Conversation
I changed the way in which we create the editors, and now all of the internal logic that has to do with Ace is handled in the constructor. I kept the factory, but now there are only 2 methods there: |
static/js/editor.ts
Outdated
setEditorMode: (isReadMode: boolean) => void; | ||
getBreakpoints(): Breakpoints; | ||
// TODO: improve this type definition | ||
on(key: any, handler: any): 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.
Guys I don't know how I can put the types that I used in the definition of this function here, can someone help?
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 start with the declaration of HedyAceEditor.on()
:
public on(key: Parameters<typeof this.editorEvent.on>[0], handler: Parameters<typeof this.editorEvent.on>[1]) {
Let's look at this fragment:
Parameters<typeof this.editorEvent.on>[0]
This says:
- Take the type of
this.editorEvent.on
(typeof
turns a value into a type). - Assuming that's a function type, get the types of the parameter types of that function type
- Then take the first element of that array
The reason you can't copy/paste this is because it contains typeof this.editorEvent.on
; since this is an interface definition and not a class, we can't say "the field editorEvent
on myself", because you don't even know that you have such a field.
But, we we know what type this.editorEvent
would have if it existed, so we can substitute that!
Because on HedyAceEditor
this is its declaration:
private editorEvent = new EventEmitter<EditorEvent>({change: true});
So the type of this.editorEvent
is EventEmitter<EditorEvent>
. Indexing into a type looks slightly different from indexing into an object (for reasons that I also don't quite understand), so we need to replace typeof this.editorEvent.on
=> EventEmitter<EditorEvent>['on']
.
Putting it all together, the first element would look like:
Parameters<EventEmitter<EditorEvent>['on']>[0]
Now that's obviously an ugly mouthful, so let's introduce a type alias to make that palatable:
type EditorEventEmitter = EventEmitter<EditorEvent>;
type OnEditorEventParameters = Parameters<EditorEventEmitter['on']>;
on(key: OnEditorEventParameters[0], handler: OnEditorEventParameters[1]): 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.
Thank you so muuuuch!!!! I really tried to look this up, but none of my attempts quite worked. Thank you for the detailed explanation!
This PR is ready for review! |
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.
Nice! As always, I have some tips and questions to make you think, but I'll leave it completely up to you what to do with those!
Looking good though!
static/js/app.ts
Outdated
theGlobalEditor = editorCreator.initializeWritableEditor($editor, EditorType.MAIN, dir); | ||
attachMainEditorEvents(); | ||
error.setEditor(theGlobalEditor); | ||
window.Range = ace.require('ace/range').Range // get reference to ace/range |
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.
Oof 😥. I see why this might be done, but sticking it window.Range
feels a little dirty to me.
Can we not make a global:
let Range: /* needs a type here, not sure what that needs to be, maybe Ace.Range? */ Ace.Range;
// and then
Range = ace.require('ace/range').Range;
?
At least it would be keeping out of the global browser object.
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.
The weird part is that this is not being referenced anywhere, so I thought this might be a property of the window
object. If that's not the case maybe we can remove this.
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.
window
is the object that represents the global scope.
So if you do:
window.banana = 'banana';
At any point later you can do
console.log(banana);
And it will work
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.
I understand. I removed the reference to windows.Range since it wasn't really used anywhere
static/js/app.ts
Outdated
@@ -239,6 +244,79 @@ export function initializeCodePage(options: InitializeCodePageOptions) { | |||
$('#program_name').on('blur', () => saveIfNecessary()); | |||
} | |||
|
|||
function attachMainEditorEvents() { |
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.
You might pass the editor in as an argument, and get rid of at least one use of the global variable?
} | ||
}); | ||
|
||
// Removed until we can fix the skip lines feature |
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.
Ohh, this is the "debugger", isn't it?
What's the issue with it?
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.
No, this is the skip line feature. It had a weird problem where if 2 different programs in different sessions where being executed at the same time and both had errors, they had their errors mixed up. We already have a fix for this in #4414, but we decided to wait a few weeks and keep testing before shipping it.
COMMON_MISTAKES, | ||
CHEATSHEET, | ||
PARSONS, | ||
EXAMPLE |
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.
Is it important to make a distinction between these? Are they not all "just some syntax highlighted block" ?
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.
All of them require different options! And I didn't want to expose a setOptions
method, so I decided to set that up in the constructors. Since there are different kinds of these blocks I created this enum to configure all of them in the constructor. You can see that in the constructor.
static/js/message-translations.ts
Outdated
"adventures_restored": "Die Standardabenteuer wurden wiederhergestellt.", | ||
"adventures_restored": "The default adventures have been restored!", |
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.
Oops?
static/js/ace-editor.ts
Outdated
} else { // It's an example editor | ||
// Fits to content size | ||
this._editor.setOptions({ maxLines: Infinity }); | ||
if(editorType === EditorType.CHEATSHEET) { |
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.
Oof, do these really all need to be different?
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.
I think so, because some of them serve different purpose and have different looks, I believe
} | ||
} | ||
} else { | ||
if (editorType === EditorType.MAIN) { |
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.
I just saw this same if
a couple of lines above. Can they be collapsed? Is one of them never executed?
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.
I just deleted one of those if!
static/js/ace-editor.ts
Outdated
export class HedyAceEditor implements HedyEditor { | ||
private _editor: AceAjax.Editor; | ||
private _markers?: Markers | ||
isReadOnly: boolean; |
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.
You probably want to make this
isReadOnly: boolean; | |
private isReadOnly: boolean; |
Because right now in other parts of the code you're accidentally directly accessing this public field, instead of going through the getter/setter as I think you probably intended.
static/js/ace-editor.ts
Outdated
/** | ||
* @returns if the editor is set to read-only mode | ||
*/ | ||
public get getIsReadOnly(): boolean { |
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.
Probably
public get isReadOnly(): boolean {
return this._isReadOnly;
}
?
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.
Or honestly, can you read directly from the editor you wrapped?
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.
You are so right. I'm using the editor to handle that now.
// TODO: FIX THIS | ||
initializeDebugger({ | ||
editor: this._editor, | ||
markers: this.markers, | ||
level: theLevel, | ||
language: theLanguage, |
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.
Yeah, it's interesting to think about whether the debugger should be inside the editor, or outside it.
You could say that the debugger is external to the editor, just using some features of the editor like "putting markers in the gutter" and "striking through certain lines".
But no need to change that now. You can always do that later, once you start adding a new editor implementation and you notice that there's a lot of repeated code between them 😉
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.
Yes, exactly, this is a weird one. Because yeah, I can say that the debugger is part of an editor so it should be part of this interface. On the other hand, it'll probably have to deal with HTML and DOM objects, and we're trying to avoid to include those things in this interface.
However, I think that will become clearer once I start working on including code mirror and replacing the current debugger.
I found that we can't remove the trimWhiteSpaceFunction since if we trim the contents of the editor every time you get the contents, you can't add spaces to the end of the line when editing. |
OK, let's test this extensively on alpha before deploying :) @jpelay can you let me know when you are sure we can deploy? Maybe also test the new dashboard a bit (less core, so less important) |
Thank you for contributing! Your pull request is now going on the merge train (choo choo! Do not click update from main anymore, and be sure to allow changes to be pushed to your fork). |
Yes, I will! And since the tests that we made for the editor that we remove earlier today work locally, I'll use them to test these changes in my computer. I'll let you know when I think everything's been tested extensively! |
Thank you for contributing! Your pull request is now going on the merge train (choo choo! Do not click update from main anymore, and be sure to allow changes to be pushed to your fork). |
Description
This PR tackles the different comments left by @rix0rrr in #4341. The changes requested are the following:
initializeModalEditor
if the difference between a main editor if the same as "main" editor and notaskPromptOpen
getValue
method to something more descriptive and maybe turn it into a read-only propertyisReadOnly
from method to read-only property passed in the constructor (not passed in the constructor yetdone!, and can't put it to read only mode due to this and this )trimTrailingSpace
method? Maybe do it automatically ingetValue
configureMainEditor
as part of the generic interfacesetEditorMode
Fixes #4382
How to test
Everything should work as expected