Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
coopwire-hypothesis
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
孙灵跃 Leon Sun
coopwire-hypothesis
Commits
542ae6c8
Unverified
Commit
542ae6c8
authored
Feb 12, 2020
by
Robert Knight
Committed by
GitHub
Feb 12, 2020
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1766 from hypothesis/avoid-ng-lookup-for-services
Decouple `wrapReactComponent` from Angular `$injector`
parents
e279908b
dff1f428
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
51 additions
and
40 deletions
+51
-40
index.js
src/sidebar/index.js
+21
-23
service-context.js
src/sidebar/util/service-context.js
+2
-2
wrap-react-component-test.js
src/sidebar/util/test/wrap-react-component-test.js
+11
-6
wrap-react-component.js
src/sidebar/util/wrap-react-component.js
+17
-9
No files found.
src/sidebar/index.js
View file @
542ae6c8
...
@@ -251,6 +251,8 @@ function startAngularApp(config) {
...
@@ -251,6 +251,8 @@ function startAngularApp(config) {
.
register
(
'$rootScope'
,
{
value
:
$rootScope
});
.
register
(
'$rootScope'
,
{
value
:
$rootScope
});
}
}
const
wrapComponent
=
component
=>
wrapReactComponent
(
component
,
container
);
angular
angular
.
module
(
'h'
,
[
angularRoute
,
angularToastr
])
.
module
(
'h'
,
[
angularRoute
,
angularToastr
])
...
@@ -259,36 +261,33 @@ function startAngularApp(config) {
...
@@ -259,36 +261,33 @@ function startAngularApp(config) {
// UI components
// UI components
.
component
(
'annotation'
,
annotation
)
.
component
(
'annotation'
,
annotation
)
.
component
(
'annotationBody'
,
wrap
React
Component
(
AnnotationBody
))
.
component
(
'annotationBody'
,
wrapComponent
(
AnnotationBody
))
.
component
(
'annotationHeader'
,
wrap
React
Component
(
AnnotationHeader
))
.
component
(
'annotationHeader'
,
wrapComponent
(
AnnotationHeader
))
.
component
(
'annotationActionBar'
,
wrap
React
Component
(
AnnotationActionBar
))
.
component
(
'annotationActionBar'
,
wrapComponent
(
AnnotationActionBar
))
.
component
(
'annotationLicense'
,
wrap
React
Component
(
AnnotationLicense
))
.
component
(
'annotationLicense'
,
wrapComponent
(
AnnotationLicense
))
.
component
(
'annotationOmega'
,
wrap
React
Component
(
AnnotationOmega
))
.
component
(
'annotationOmega'
,
wrapComponent
(
AnnotationOmega
))
.
component
(
.
component
(
'annotationPublishControl'
,
'annotationPublishControl'
,
wrap
React
Component
(
AnnotationPublishControl
)
wrapComponent
(
AnnotationPublishControl
)
)
)
.
component
(
'annotationQuote'
,
wrap
React
Component
(
AnnotationQuote
))
.
component
(
'annotationQuote'
,
wrapComponent
(
AnnotationQuote
))
.
component
(
'annotationThread'
,
annotationThread
)
.
component
(
'annotationThread'
,
annotationThread
)
.
component
(
'annotationViewerContent'
,
annotationViewerContent
)
.
component
(
'annotationViewerContent'
,
annotationViewerContent
)
.
component
(
'helpPanel'
,
wrap
React
Component
(
HelpPanel
))
.
component
(
'helpPanel'
,
wrapComponent
(
HelpPanel
))
.
component
(
'loggedOutMessage'
,
wrap
React
Component
(
LoggedOutMessage
))
.
component
(
'loggedOutMessage'
,
wrapComponent
(
LoggedOutMessage
))
.
component
(
'moderationBanner'
,
wrap
React
Component
(
ModerationBanner
))
.
component
(
'moderationBanner'
,
wrapComponent
(
ModerationBanner
))
.
component
(
'searchStatusBar'
,
wrap
React
Component
(
SearchStatusBar
))
.
component
(
'searchStatusBar'
,
wrapComponent
(
SearchStatusBar
))
.
component
(
'focusedModeHeader'
,
wrap
React
Component
(
FocusedModeHeader
))
.
component
(
'focusedModeHeader'
,
wrapComponent
(
FocusedModeHeader
))
.
component
(
'selectionTabs'
,
wrap
React
Component
(
SelectionTabs
))
.
component
(
'selectionTabs'
,
wrapComponent
(
SelectionTabs
))
.
component
(
'sidebarContent'
,
sidebarContent
)
.
component
(
'sidebarContent'
,
sidebarContent
)
.
component
(
'sidebarContentError'
,
wrapReactComponent
(
SidebarContentError
))
.
component
(
'sidebarContentError'
,
wrapComponent
(
SidebarContentError
))
.
component
(
.
component
(
'shareAnnotationsPanel'
,
wrapComponent
(
ShareAnnotationsPanel
))
'shareAnnotationsPanel'
,
wrapReactComponent
(
ShareAnnotationsPanel
)
)
.
component
(
'streamContent'
,
streamContent
)
.
component
(
'streamContent'
,
streamContent
)
.
component
(
'svgIcon'
,
wrap
React
Component
(
SvgIcon
))
.
component
(
'svgIcon'
,
wrapComponent
(
SvgIcon
))
.
component
(
'tagEditor'
,
wrap
React
Component
(
TagEditor
))
.
component
(
'tagEditor'
,
wrapComponent
(
TagEditor
))
.
component
(
'tagList'
,
wrap
React
Component
(
TagList
))
.
component
(
'tagList'
,
wrapComponent
(
TagList
))
.
component
(
'threadList'
,
threadList
)
.
component
(
'threadList'
,
threadList
)
.
component
(
'topBar'
,
wrap
React
Component
(
TopBar
))
.
component
(
'topBar'
,
wrapComponent
(
TopBar
))
.
directive
(
'hAutofocus'
,
hAutofocusDirective
)
.
directive
(
'hAutofocus'
,
hAutofocusDirective
)
.
directive
(
'hBranding'
,
hBrandingDirective
)
.
directive
(
'hBranding'
,
hBrandingDirective
)
.
directive
(
'hOnTouch'
,
hOnTouchDirective
)
.
directive
(
'hOnTouch'
,
hOnTouchDirective
)
...
@@ -315,7 +314,6 @@ function startAngularApp(config) {
...
@@ -315,7 +314,6 @@ function startAngularApp(config) {
.
service
(
'session'
,
()
=>
container
.
get
(
'session'
))
.
service
(
'session'
,
()
=>
container
.
get
(
'session'
))
.
service
(
'streamer'
,
()
=>
container
.
get
(
'streamer'
))
.
service
(
'streamer'
,
()
=>
container
.
get
(
'streamer'
))
.
service
(
'streamFilter'
,
()
=>
container
.
get
(
'streamFilter'
))
.
service
(
'streamFilter'
,
()
=>
container
.
get
(
'streamFilter'
))
.
service
(
'tags'
,
()
=>
container
.
get
(
'tags'
))
// Redux store
// Redux store
.
service
(
'store'
,
()
=>
container
.
get
(
'store'
))
.
service
(
'store'
,
()
=>
container
.
get
(
'store'
))
...
...
src/sidebar/util/service-context.js
View file @
542ae6c8
...
@@ -64,7 +64,7 @@ export function withServices(Component) {
...
@@ -64,7 +64,7 @@ export function withServices(Component) {
function
Wrapper
(
props
)
{
function
Wrapper
(
props
)
{
// Get the current dependency injector instance that is provided by a
// Get the current dependency injector instance that is provided by a
// `ServiceContext.Provider` somewhere higher up the component tree.
// `ServiceContext.Provider` somewhere higher up the component tree.
const
$
injector
=
useContext
(
ServiceContext
);
const
injector
=
useContext
(
ServiceContext
);
// Inject services, unless they have been overridden by props passed from
// Inject services, unless they have been overridden by props passed from
// the parent component.
// the parent component.
...
@@ -80,7 +80,7 @@ export function withServices(Component) {
...
@@ -80,7 +80,7 @@ export function withServices(Component) {
}
}
if
(
!
(
service
in
props
))
{
if
(
!
(
service
in
props
))
{
services
[
service
]
=
$
injector
.
get
(
service
);
services
[
service
]
=
injector
.
get
(
service
);
}
}
}
}
return
<
Component
{...
services
}
{...
props
}
/>
;
return
<
Component
{...
services
}
{...
props
}
/>
;
...
...
src/sidebar/util/test/wrap-react-component-test.js
View file @
542ae6c8
...
@@ -3,6 +3,7 @@ import { Component, createElement } from 'preact';
...
@@ -3,6 +3,7 @@ import { Component, createElement } from 'preact';
import
{
useContext
}
from
'preact/hooks'
;
import
{
useContext
}
from
'preact/hooks'
;
import
propTypes
from
'prop-types'
;
import
propTypes
from
'prop-types'
;
import
{
Injector
}
from
'../../../shared/injector'
;
import
{
createDirective
}
from
'../../directive/test/util'
;
import
{
createDirective
}
from
'../../directive/test/util'
;
import
{
ServiceContext
}
from
'../service-context'
;
import
{
ServiceContext
}
from
'../service-context'
;
import
wrapReactComponent
from
'../wrap-react-component'
;
import
wrapReactComponent
from
'../wrap-react-component'
;
...
@@ -58,11 +59,15 @@ describe('wrapReactComponent', () => {
...
@@ -58,11 +59,15 @@ describe('wrapReactComponent', () => {
return
{
element
,
onClick
,
onDblClick
};
return
{
element
,
onClick
,
onDblClick
};
}
}
let
servicesInjector
;
beforeEach
(()
=>
{
beforeEach
(()
=>
{
const
servicesInjector
=
new
Injector
();
servicesInjector
.
register
(
'theme'
,
{
value
:
'dark'
});
angular
angular
.
module
(
'app'
,
[])
.
module
(
'app'
,
[])
.
component
(
'btn'
,
wrapReactComponent
(
Button
))
.
component
(
'btn'
,
wrapReactComponent
(
Button
,
servicesInjector
));
.
value
(
'theme'
,
'dark'
);
angular
.
mock
.
module
(
'app'
);
angular
.
mock
.
module
(
'app'
);
});
});
...
@@ -73,7 +78,7 @@ describe('wrapReactComponent', () => {
...
@@ -73,7 +78,7 @@ describe('wrapReactComponent', () => {
});
});
it
(
'derives Angular component "bindings" from React "propTypes"'
,
()
=>
{
it
(
'derives Angular component "bindings" from React "propTypes"'
,
()
=>
{
const
ngComponent
=
wrapReactComponent
(
Button
);
const
ngComponent
=
wrapReactComponent
(
Button
,
servicesInjector
);
assert
.
deepEqual
(
ngComponent
.
bindings
,
{
assert
.
deepEqual
(
ngComponent
.
bindings
,
{
label
:
'<'
,
label
:
'<'
,
isDisabled
:
'<'
,
isDisabled
:
'<'
,
...
@@ -141,7 +146,7 @@ describe('wrapReactComponent', () => {
...
@@ -141,7 +146,7 @@ describe('wrapReactComponent', () => {
angular
angular
.
module
(
'app'
,
[])
.
module
(
'app'
,
[])
.
component
(
'parent'
,
parentComponent
)
.
component
(
'parent'
,
parentComponent
)
.
component
(
'child'
,
wrapReactComponent
(
ChildComponent
));
.
component
(
'child'
,
wrapReactComponent
(
ChildComponent
,
servicesInjector
));
angular
.
mock
.
module
(
'app'
);
angular
.
mock
.
module
(
'app'
);
// Render the component with the child initially visible.
// Render the component with the child initially visible.
...
@@ -160,7 +165,7 @@ describe('wrapReactComponent', () => {
...
@@ -160,7 +165,7 @@ describe('wrapReactComponent', () => {
return
<
div
>
Hello
world
<
/div>
;
return
<
div
>
Hello
world
<
/div>
;
}
}
assert
.
throws
(
assert
.
throws
(
()
=>
wrapReactComponent
(
TestComponent
),
()
=>
wrapReactComponent
(
TestComponent
,
servicesInjector
),
'React component TestComponent does not specify its inputs using "propTypes"'
'React component TestComponent does not specify its inputs using "propTypes"'
);
);
});
});
...
@@ -231,7 +236,7 @@ describe('wrapReactComponent', () => {
...
@@ -231,7 +236,7 @@ describe('wrapReactComponent', () => {
angular
angular
.
module
(
'app'
,
[])
.
module
(
'app'
,
[])
.
component
(
'parent'
,
parentComponent
)
.
component
(
'parent'
,
parentComponent
)
.
component
(
'child'
,
wrapReactComponent
(
Child
));
.
component
(
'child'
,
wrapReactComponent
(
Child
,
servicesInjector
));
angular
.
mock
.
module
(
'app'
);
angular
.
mock
.
module
(
'app'
);
const
element
=
createDirective
(
document
,
'parent'
);
const
element
=
createDirective
(
document
,
'parent'
);
...
...
src/sidebar/util/wrap-react-component.js
View file @
542ae6c8
...
@@ -6,6 +6,10 @@ function useExpressionBinding(propName) {
...
@@ -6,6 +6,10 @@ function useExpressionBinding(propName) {
return
propName
.
match
(
/^on
[
A-Z
]
/
);
return
propName
.
match
(
/^on
[
A-Z
]
/
);
}
}
/**
* @typedef {import('../../shared/injector').Injector} Injector
*/
/**
/**
* Controller for an Angular component that wraps a React component.
* Controller for an Angular component that wraps a React component.
*
*
...
@@ -14,14 +18,16 @@ function useExpressionBinding(propName) {
...
@@ -14,14 +18,16 @@ function useExpressionBinding(propName) {
* has been created.
* has been created.
*/
*/
class
ReactController
{
class
ReactController
{
constructor
(
$element
,
$injector
,
$scope
,
type
)
{
constructor
(
$element
,
services
,
$scope
,
type
)
{
/** The DOM element where the React component should be rendered. */
/** The DOM element where the React component should be rendered. */
this
.
domElement
=
$element
[
0
];
this
.
domElement
=
$element
[
0
];
/**
/**
* The Angular service injector, used by this component and its descendants.
* The services injector, used by this component and its descendants.
*
* @type {Injector}
*/
*/
this
.
$injector
=
$injector
;
this
.
services
=
services
;
/** The React component function or class. */
/** The React component function or class. */
this
.
type
=
type
;
this
.
type
=
type
;
...
@@ -83,10 +89,10 @@ class ReactController {
...
@@ -83,10 +89,10 @@ class ReactController {
updateReactComponent
()
{
updateReactComponent
()
{
// Render component, with a `ServiceContext.Provider` wrapper which
// Render component, with a `ServiceContext.Provider` wrapper which
// provides access to
Angular
services via `withServices` or `useContext`
// provides access to services via `withServices` or `useContext`
// in child components.
// in child components.
render
(
render
(
<
ServiceContext
.
Provider
value
=
{
this
.
$injector
}
>
<
ServiceContext
.
Provider
value
=
{
this
.
services
}
>
<
this
.
type
{...
this
.
props
}
/
>
<
this
.
type
{...
this
.
props
}
/
>
<
/ServiceContext.Provider>
,
<
/ServiceContext.Provider>
,
this
.
domElement
this
.
domElement
...
@@ -103,14 +109,16 @@ class ReactController {
...
@@ -103,14 +109,16 @@ class ReactController {
* one-way ('<') bindings except for those with names matching /^on[A-Z]/ which
* one-way ('<') bindings except for those with names matching /^on[A-Z]/ which
* are assumed to be callbacks that use expression ('&') bindings.
* are assumed to be callbacks that use expression ('&') bindings.
*
*
* If the React component needs access to a
n Angular
service, it can get at
* If the React component needs access to a service, it can get at
* them using the `withServices` wrapper from service-context.js.
* them using the `withServices` wrapper from service-context.js.
*
*
* @param {Function} type - The React component class or function
* @param {Function} type - The React component class or function
* @param {Injector} services
* Dependency injection container providing services that components use.
* @return {Object} -
* @return {Object} -
* An AngularJS component spec for use with `angular.component(...)`
* An AngularJS component spec for use with `angular.component(...)`
*/
*/
export
default
function
wrapReactComponent
(
type
)
{
export
default
function
wrapReactComponent
(
type
,
services
)
{
if
(
!
type
.
propTypes
)
{
if
(
!
type
.
propTypes
)
{
throw
new
Error
(
throw
new
Error
(
`React component
${
type
.
name
}
does not specify its inputs using "propTypes"`
`React component
${
type
.
name
}
does not specify its inputs using "propTypes"`
...
@@ -122,8 +130,8 @@ export default function wrapReactComponent(type) {
...
@@ -122,8 +130,8 @@ export default function wrapReactComponent(type) {
* component being wrapped.
* component being wrapped.
*/
*/
// @ngInject
// @ngInject
function
createController
(
$element
,
$
injector
,
$
scope
)
{
function
createController
(
$element
,
$scope
)
{
return
new
ReactController
(
$element
,
$injector
,
$scope
,
type
);
return
new
ReactController
(
$element
,
services
,
$scope
,
type
);
}
}
const
bindings
=
{};
const
bindings
=
{};
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment