Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign upHighlight CSS and JS selectors #1598
Conversation
|
I'm feeling pretty good about this code overall, but I'm not sure what's causing the test failure- it wasn't failing locally for me. I think it's ready for another pass, but it might be better to talk about the overall approach again @outoftime |
|
@wylieconlon heyo! not going to be able to do a full review tonight (though i have been looking at the diff in spare moments and broadly it’s looking good) but on the test failure tip I believe that is caused by some ES6+ code not getting compiled to ES5 in the build process. My guess would be the new babel libraries, which (naturally) I’m sure is written in modern ES. So that would just need to get added to the list of libraries that go through the Babel loader in the Webpack config. Also probably obvious but as written this will be blocked on upgrading us to Babel 7 in general (I’m going to do it I swear!) |
| @@ -129,9 +129,27 @@ class Editor extends React.Component { | |||
|
|
|||
| _startNewSession(source) { | |||
| const session = createAceSessionWithoutWorker(this.props.language, source); | |||
| const cursor = session.selection.lead; | |||
outoftime
Nov 4, 2018
Contributor
Seems like lead as a property may not be part of the public API—the docs only mention getSelectionLead()
Seems like lead as a property may not be part of the public API—the docs only mention getSelectionLead()
| cursor, | ||
| this.props.language, | ||
| ); | ||
| }); |
outoftime
Nov 4, 2018
Contributor
In general I think facts about UI state should either be completely invisible to application (redux) state, or completely controlled by/synchronized with Redux. So in this case, once application state is concerned with the cursor position, it should also control cursor position. Practically I think this just means we should be taking cursor position and focus as props in this component, watching for changes, and updating the editor components if they’re out of sync.
This also means we can replace the never-terribly-appealing REQUEST_FOCUSED_LINE mechanism with a simple action to move the cursor to a particular location at the Redux level.
In general I think facts about UI state should either be completely invisible to application (redux) state, or completely controlled by/synchronized with Redux. So in this case, once application state is concerned with the cursor position, it should also control cursor position. Practically I think this just means we should be taking cursor position and focus as props in this component, watching for changes, and updating the editor components if they’re out of sync.
This also means we can replace the never-terribly-appealing REQUEST_FOCUSED_LINE mechanism with a simple action to move the cursor to a particular location at the Redux level.
| @@ -47,6 +47,7 @@ class PreviewFrame extends React.Component { | |||
| const {consoleEntries, isActive} = this.props; | |||
|
|
|||
| if (this._channel && isActive) { | |||
| this._postFocusedSelectorToFrame(this.props.focusedSelector); | |||
outoftime
Nov 4, 2018
Contributor
Should we only do this if the focused selector has in fact changed?
Should we only do this if the focused selector has in fact changed?
|
|
||
| onEditorBlurred() { | ||
| dispatch( | ||
| editorBlurred(), |
outoftime
Nov 4, 2018
Contributor
Should this still contain a language argument? As it is I think we rely on ACE dispatching the blur event before the focus event in the case where the user clicks from one editor to another.
Should this still contain a language argument? As it is I think we rely on ACE dispatching the blur event before the focus event in the case where the user clicks from one editor to another.
| if (element.offsetParent === null) { | ||
| cover.style.position = 'fixed'; | ||
| } else if (element !== document.body || | ||
| element !== document.documentElement) { |
outoftime
Nov 4, 2018
Contributor
It seems like every element is either not <html> or not <body>?
It seems like every element is either not <html> or not <body>?
| cover.style.top = `${offset.top}px`; | ||
| cover.style.width = `${element.offsetWidth}px`; | ||
| cover.style.height = `${element.offsetHeight}px`; | ||
| cover.classList.add('fade'); |
outoftime
Nov 4, 2018
Contributor
Why do we need a second class here? Can’t we use the __popcode-highlighter class for all the rules we want to apply?
Why do we need a second class here? Can’t we use the __popcode-highlighter class for all the rules we want to apply?
|
|
||
| const emptyMap = new Immutable.Map(); | ||
|
|
||
| export default function reduceProjects(stateIn, action) { |
outoftime
Nov 4, 2018
Contributor
reduceSelectorLocations
reduceSelectorLocations
outoftime
Nov 4, 2018
Contributor
Also, on my list is to refactor all existing reducers to use handleActions, but for now I’d at least like to use it for new reducers (e.g. resizableFlex)
Also, on my list is to refactor all existing reducers to use handleActions, but for now I’d at least like to use it for new reducers (e.g. resizableFlex)
outoftime
Nov 4, 2018
Contributor
Also generally we want to have unit tests for all reducers and sagas
Also generally we want to have unit tests for all reducers and sagas
|
|
||
| export function* parseCurrentProjectSource() { | ||
| const getJsSelectorLocations = yield call(importGetJsSelectorLocations); | ||
| const getCssSelectorLocations = yield call(importGetCssSelectorLocations); |
outoftime
Nov 4, 2018
Contributor
Can use an all effect to do these in parallel
Can use an all effect to do these in parallel
| export function* parseCurrentProjectSource() { | ||
| const getJsSelectorLocations = yield call(importGetJsSelectorLocations); | ||
| const getCssSelectorLocations = yield call(importGetCssSelectorLocations); | ||
| const currentProject = yield select(getCurrentProject); |
outoftime
Nov 4, 2018
Contributor
Mild preference for destructuring here
Mild preference for destructuring here
| const cssSelectorLocations = yield call( | ||
| getCssSelectorLocations, | ||
| currentProject.sources.css, | ||
| ); |
outoftime
Nov 4, 2018
Contributor
An all again would be nice (obviously these are not actually async but they certainly could conceivably be in the future, using a web worker for instance, so best to write the code to do the best thing in all cases).
An all again would be nice (obviously these are not actually async but they certainly could conceivably be in the future, using a web worker for instance, so best to write the code to do the best thing in all cases).
| @@ -131,6 +171,10 @@ export default function* projects() { | |||
| 'UPDATE_PROJECT_SOURCE', | |||
| 'UPDATE_PROJECT_INSTRUCTIONS', | |||
| ], updateProjectSource), | |||
| throttle(100, [ | |||
| 'UPDATE_PROJECT_SOURCE', | |||
| 'PROJECT_RESTORED_FROM_LAST_SESSION', | |||
outoftime
Nov 4, 2018
Contributor
We’ll want to do this for CHANGE_CURRENT_PROJECT as well, maybe others? And CREATE_PROJECT should clear the selector list.
We’ll want to do this for CHANGE_CURRENT_PROJECT as well, maybe others? And CREATE_PROJECT should clear the selector list.
| const selectors = yield select(getSelectorLocationsForLanguage, language); | ||
| const selector = yield call(selectorAtCursor, selectors, cursor); | ||
| yield put(currentFocusedSelectorChanged(selector)); | ||
| } |
outoftime
Nov 4, 2018
Contributor
Technically this doesn’t need to be in a saga, does it? But in the spirit of “selectorAtCursor() could very conceivably be an async function” I’m down to skate to where the puck is headed.
Technically this doesn’t need to be in a saga, does it? But in the spirit of “selectorAtCursor() could very conceivably be an async function” I’m down to skate to where the puck is headed.
| case 'UPDATE_SELECTOR_LOCATIONS': | ||
| return state.set( | ||
| 'selectors', | ||
| new SelectorLocations(action.payload.selectors), |
outoftime
Nov 4, 2018
•
Contributor
Although there are some specific exceptions, we usually want to store all non-scalar data in Redux using Immutable data structures. In this case I think we can actually just store the locations using the existing EditorLocation record.
Although there are some specific exceptions, we usually want to store all non-scalar data in Redux using Immutable data structures. In this case I think we can actually just store the locations using the existing EditorLocation record.
| return state.getIn([ | ||
| 'selectorLocations', | ||
| 'selectors', | ||
| ])[language] || null; |
outoftime
Nov 4, 2018
Contributor
Why not getIn(['selectorLocations', 'selectors', language])?
Why not getIn(['selectorLocations', 'selectors', language])?
| switch (action.type) { | ||
| case 'UPDATE_SELECTOR_LOCATIONS': | ||
| return state.set( | ||
| 'selectors', |
outoftime
Nov 4, 2018
Contributor
I don’t think there’s any need for an additional level of nesting here—isn’t the SelectorLocations record itself sufficient to represent this slice of the state?
I don’t think there’s any need for an additional level of nesting here—isn’t the SelectorLocations record itself sufficient to represent this slice of the state?
| @@ -131,6 +171,10 @@ export default function* projects() { | |||
| 'UPDATE_PROJECT_SOURCE', | |||
| 'UPDATE_PROJECT_INSTRUCTIONS', | |||
| ], updateProjectSource), | |||
| throttle(100, [ | |||
| 'UPDATE_PROJECT_SOURCE', | |||
outoftime
Nov 4, 2018
Contributor
For UPDATE_PROJECT_SOURCE, wouldn’t it be more efficient to call a different saga that only re-parses and calculates the selector locations for that language?
For UPDATE_PROJECT_SOURCE, wouldn’t it be more efficient to call a different saga that only re-parses and calculates the selector locations for that language?
| }); | ||
|
|
||
| return selectors; | ||
| } catch (e) { |
outoftime
Nov 4, 2018
Contributor
Should we be looking for a specific error that indicates invalid CSS syntax? I wouldn’t want to inadvertently swallow an error thrown by a bug in our code.
Should we be looking for a specific error that indicates invalid CSS syntax? I wouldn’t want to inadvertently swallow an error thrown by a bug in our code.
| traverse(ast, visitor, null); | ||
|
|
||
| return foundMatches; | ||
| } catch (e) { |
outoftime
Nov 4, 2018
Contributor
Same question about blanket catch here
Same question about blanket catch here
| if ( | ||
| ( | ||
| callee === '$' || | ||
| callee.indexOf('querySelector') !== -1 |
outoftime
Nov 4, 2018
Contributor
I would use callee.startsWith (this is a fairly new method but should be polyfilled)
I would use callee.startsWith (this is a fairly new method but should be polyfilled)
| @@ -0,0 +1,21 @@ | |||
| .__popcode-highlighter { | |||
| z-index: 2000000; | |||
outoftime
Nov 4, 2018
Contributor
I’m not an enormous fan of arbitrarily large z-indexes to accomplish “definitely above everything else”—when we’re constructing the highlighter, can we just find the topmost ancestor element that has a z-index, and set the highighter’s z-index to one plus that?
I’m not an enormous fan of arbitrarily large z-indexes to accomplish “definitely above everything else”—when we’re constructing the highlighter, can we just find the topmost ancestor element that has a z-index, and set the highighter’s z-index to one plus that?
|
|
||
| .__popcode-highlighter.fade { | ||
| background-color: #ffffff00; | ||
| } |
outoftime
Nov 4, 2018
Contributor
I love this effect! Seems like it would be a little more intuitive to describe it using an animation rather than a transition, though? I found it confusing when I initially noticed that the highlighter was being given two class names at the same time…
I love this effect! Seems like it would be a little more intuitive to describe it using an animation rather than a transition, though? I found it confusing when I initially noticed that the highlighter was being given two class names at the same time…
|
|
||
| test('finds all selectors in css', (assert) => { | ||
| const selectors = getCssSelectorLocations(source); | ||
| assert.deepEqual(selectors.map(s => s.selector), [ |
outoftime
Nov 4, 2018
Contributor
You can do the map a little more cutely with lodash, e.g. map(selectors, 'selector')
You can do the map a little more cutely with lodash, e.g. map(selectors, 'selector')
|
Great stuff! Feels like very strong progress and the structure is mostly there—only major thing would be to make the cursor/focus state fully controlled by Redux rather than just a one-way data flow. Can’t wait to release this, it’s going to be huge!! |
|
Oops I meant to click “request changes” ; D |
This continues work from @pwjablonski on #1025