-
Notifications
You must be signed in to change notification settings - Fork 140
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
Highlight CSS and JS selectors #1598
base: master
Are you sure you want to change the base?
Changes from all commits
edfbdb8
c7dcbaf
48e73c0
f08514d
5014e62
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import {createAction} from 'redux-actions'; | ||
|
||
export const updateSelectorLocations = createAction( | ||
'UPDATE_SELECTOR_LOCATIONS', | ||
selectors => ({selectors}), | ||
(_selectors, timestamp = Date.now()) => ({timestamp}), | ||
); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -129,9 +129,27 @@ class Editor extends React.Component { | |
|
||
_startNewSession(source) { | ||
const session = createAceSessionWithoutWorker(this.props.language, source); | ||
const cursor = session.selection.lead; | ||
session.on('change', () => { | ||
this.props.onInput(this._editor.getValue()); | ||
}); | ||
session.selection.on('changeCursor', () => { | ||
this.props.onCursorChange( | ||
this._editor.getValue(), | ||
cursor, | ||
this.props.language, | ||
); | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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 |
||
this._editor.on('blur', () => { | ||
this.props.onEditorBlurred(); | ||
}); | ||
this._editor.on('focus', () => { | ||
this.props.onEditorFocused( | ||
this._editor.getValue(), | ||
cursor, | ||
this.props.language, | ||
); | ||
}); | ||
session.setAnnotations(this.props.errors); | ||
this._editor.setSession(session); | ||
this._editor.moveCursorTo(0, 0); | ||
|
@@ -147,6 +165,9 @@ Editor.propTypes = { | |
requestedFocusedLine: PropTypes.instanceOf(EditorLocation), | ||
source: PropTypes.string.isRequired, | ||
textSizeIsLarge: PropTypes.bool.isRequired, | ||
onCursorChange: PropTypes.func.isRequired, | ||
onEditorBlurred: PropTypes.func.isRequired, | ||
onEditorFocused: PropTypes.func.isRequired, | ||
onInput: PropTypes.func.isRequired, | ||
onRequestedLineFocused: PropTypes.func.isRequired, | ||
}; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -47,6 +47,7 @@ class PreviewFrame extends React.Component { | |
const {consoleEntries, isActive} = this.props; | ||
|
||
if (this._channel && isActive) { | ||
this._postFocusedSelectorToFrame(this.props.focusedSelector); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we only do this if the focused selector has in fact changed? |
||
for (const [key, {expression}] of consoleEntries) { | ||
if (!prevConsoleEntries.has(key) && expression) { | ||
this._evaluateConsoleExpression(key, expression); | ||
|
@@ -135,6 +136,13 @@ class PreviewFrame extends React.Component { | |
this.props.onConsoleLog(printedValue, compiledProjectKey); | ||
} | ||
|
||
_postFocusedSelectorToFrame(selector) { | ||
this._channel.notify({ | ||
method: 'highlightElement', | ||
params: selector, | ||
}); | ||
} | ||
|
||
_attachToFrame(frame) { | ||
if (!frame) { | ||
if (this._channel) { | ||
|
@@ -168,11 +176,16 @@ class PreviewFrame extends React.Component { | |
PreviewFrame.propTypes = { | ||
compiledProject: PropTypes.instanceOf(CompiledProjectRecord).isRequired, | ||
consoleEntries: ImmutablePropTypes.iterable.isRequired, | ||
focusedSelector: PropTypes.string, | ||
isActive: PropTypes.bool.isRequired, | ||
onConsoleError: PropTypes.func.isRequired, | ||
onConsoleLog: PropTypes.func.isRequired, | ||
onConsoleValue: PropTypes.func.isRequired, | ||
onRuntimeError: PropTypes.func.isRequired, | ||
}; | ||
|
||
PreviewFrame.defaultProps = { | ||
focusedSelector: null, | ||
}; | ||
|
||
export default PreviewFrame; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,10 +9,13 @@ import { | |
isTextSizeLarge, | ||
} from '../selectors'; | ||
import { | ||
currentCursorChanged, | ||
editorFocusedRequestedLine, | ||
hideComponent, | ||
updateProjectSource, | ||
unhideComponent, | ||
editorBlurred, | ||
editorFocused, | ||
} from '../actions'; | ||
|
||
function mapStateToProps(state) { | ||
|
@@ -41,6 +44,33 @@ function mapDispatchToProps(dispatch) { | |
onRequestedLineFocused() { | ||
dispatch(editorFocusedRequestedLine()); | ||
}, | ||
|
||
onEditorCursorChange(source, cursor, language) { | ||
dispatch( | ||
currentCursorChanged( | ||
source, | ||
cursor, | ||
language, | ||
), | ||
); | ||
}, | ||
|
||
onEditorBlurred() { | ||
dispatch( | ||
editorBlurred(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this still contain a |
||
); | ||
}, | ||
|
||
onEditorFocused(source, cursor, language) { | ||
dispatch( | ||
editorFocused( | ||
source, | ||
cursor, | ||
language, | ||
), | ||
); | ||
}, | ||
|
||
}; | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import throttle from 'lodash-es/throttle'; | ||
|
||
import channel from './channel'; | ||
|
||
const RESIZE_THROTTLE = 250; | ||
let highlightSelector = null; | ||
|
||
const handleWindowResize = throttle(() => { | ||
updateCovers(highlightSelector); | ||
}, RESIZE_THROTTLE); | ||
|
||
window.addEventListener('resize', handleWindowResize); | ||
|
||
function getOffsetFromBody(element) { | ||
if (element === document.body) { | ||
return {top: 0, left: 0}; | ||
} | ||
const {top, left} = getOffsetFromBody(element.offsetParent); | ||
return {top: top + element.offsetTop, left: left + element.offsetLeft}; | ||
} | ||
|
||
function removeCovers() { | ||
const highlighterElements = | ||
document.querySelectorAll('.__popcode-highlighter'); | ||
for (const highlighterElement of highlighterElements) { | ||
highlighterElement.remove(); | ||
} | ||
} | ||
|
||
function addCovers(selector) { | ||
const elements = document.querySelectorAll(selector); | ||
for (const element of elements) { | ||
const cover = document.createElement('div'); | ||
const rect = element.getBoundingClientRect(); | ||
let offset = {top: rect.top, left: rect.left}; | ||
if (element.offsetParent === null) { | ||
cover.style.position = 'fixed'; | ||
} else if (element !== document.body || | ||
element !== document.documentElement) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems like every element is either not |
||
offset = getOffsetFromBody(element); | ||
} | ||
document.body.appendChild(cover); | ||
cover.classList = '__popcode-highlighter'; | ||
cover.style.left = `${offset.left}px`; | ||
cover.style.top = `${offset.top}px`; | ||
cover.style.width = `${element.offsetWidth}px`; | ||
cover.style.height = `${element.offsetHeight}px`; | ||
cover.classList.add('fade'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need a second class here? Can’t we use the |
||
} | ||
} | ||
|
||
function updateCovers(selector) { | ||
removeCovers(); | ||
if (selector !== null) { | ||
highlightSelector = selector; | ||
addCovers(selector); | ||
} | ||
} | ||
|
||
export default function handleElementHighlights() { | ||
channel.bind( | ||
'highlightElement', | ||
(_trans, selector) => updateCovers(selector), | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import {Record, List} from 'immutable'; | ||
|
||
export default Record({ | ||
javascript: new List(), | ||
css: new List(), | ||
}, 'SelectorLocations'); |
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.
Seems like
lead
as a property may not be part of the public API—the docs only mentiongetSelectionLead()