pageflow-react 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.jshintrc +15 -0
  4. data/CHANGELOG.md +7 -0
  5. data/Gemfile +10 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +40 -0
  8. data/Rakefile +4 -0
  9. data/app/assets/javascripts/pageflow/react.js +8975 -0
  10. data/app/assets/javascripts/pageflow/react/components.js +4 -0
  11. data/app/views/pageflow/react/_widget.html.erb +1 -0
  12. data/app/views/pageflow/react/page.html.erb +7 -0
  13. data/js/.eslintrc +33 -0
  14. data/js/.gitignore +1 -0
  15. data/js/karma.conf.js +61 -0
  16. data/js/package.json +43 -0
  17. data/js/spec/.eslintrc +8 -0
  18. data/js/spec/components/background_image_spec.js +47 -0
  19. data/js/spec/components/page_thumbnail_spec.js +213 -0
  20. data/js/spec/create_container_spec.js +82 -0
  21. data/js/spec/resolve_spec.js +3 -0
  22. data/js/spec/resolvers/backbone_model_resolver_spec.js +256 -0
  23. data/js/spec/resolvers/create_recursive_resolver_spec.js +120 -0
  24. data/js/spec/resolvers/editor_file_ids_resolver_spec.js +49 -0
  25. data/js/spec/resolvers/i18n_resolver_spec.js +20 -0
  26. data/js/spec/resolvers/object_resolver_spec.js +165 -0
  27. data/js/spec/resolvers/page_type_resolver_spec.js +23 -0
  28. data/js/spec/resolvers/seed_resolver_spec.js +128 -0
  29. data/js/spec/stub_spec.js +16 -0
  30. data/js/spec/support/render_component.js +7 -0
  31. data/js/src/components/background_image.jsx +60 -0
  32. data/js/src/components/lazy_background_image.jsx +23 -0
  33. data/js/src/components/lazy_loaded_page_thumbnail.jsx +19 -0
  34. data/js/src/components/page_background.jsx +11 -0
  35. data/js/src/components/page_background_image.jsx +13 -0
  36. data/js/src/components/page_content.jsx +51 -0
  37. data/js/src/components/page_header.jsx +15 -0
  38. data/js/src/components/page_link.jsx +35 -0
  39. data/js/src/components/page_shadow.jsx +19 -0
  40. data/js/src/components/page_text.jsx +17 -0
  41. data/js/src/components/page_thumbnail.jsx +86 -0
  42. data/js/src/components/page_wrapper.jsx +11 -0
  43. data/js/src/components/scroller.js +43 -0
  44. data/js/src/create_container.jsx +53 -0
  45. data/js/src/create_page.jsx +38 -0
  46. data/js/src/create_page_component.jsx +45 -0
  47. data/js/src/create_page_type.js +57 -0
  48. data/js/src/create_resolver_root.jsx +21 -0
  49. data/js/src/create_widget.jsx +3 -0
  50. data/js/src/create_widget_type.js +12 -0
  51. data/js/src/index.js +69 -0
  52. data/js/src/mutate.js +17 -0
  53. data/js/src/mutations/mutation.js +5 -0
  54. data/js/src/mutations/update_page_link_mutation.js +30 -0
  55. data/js/src/mutations/update_page_mutation.js +19 -0
  56. data/js/src/resolve.js +45 -0
  57. data/js/src/resolvers/backbone_model_resolver.js +118 -0
  58. data/js/src/resolvers/create_recursive_resolver.js +20 -0
  59. data/js/src/resolvers/current_parent_page_resolver.js +38 -0
  60. data/js/src/resolvers/editor_chapter_resolver.js +10 -0
  61. data/js/src/resolvers/editor_file_ids_resolver.js +30 -0
  62. data/js/src/resolvers/editor_page_resolver.js +11 -0
  63. data/js/src/resolvers/i18n_resolver.js +11 -0
  64. data/js/src/resolvers/object_resolver.js +58 -0
  65. data/js/src/resolvers/page_type_resolver.js +12 -0
  66. data/js/src/resolvers/resolver.js +16 -0
  67. data/js/src/resolvers/seed_chapter_resolver.js +10 -0
  68. data/js/src/resolvers/seed_file_ids_resolver.js +11 -0
  69. data/js/src/resolvers/seed_page_resolver.js +11 -0
  70. data/js/src/resolvers/seed_resolver.js +75 -0
  71. data/js/src/utils/camelize.js +29 -0
  72. data/js/webpack.config.js +31 -0
  73. data/lib/pageflow-react.rb +13 -0
  74. data/lib/pageflow/react/engine.rb +15 -0
  75. data/lib/pageflow/react/page_type.rb +16 -0
  76. data/lib/pageflow/react/version.rb +5 -0
  77. data/lib/pageflow/react/widget_type.rb +21 -0
  78. data/pageflow-react.gemspec +29 -0
  79. metadata +205 -0
@@ -0,0 +1,165 @@
1
+ import ObjectResolver from 'resolvers/object_resolver';
2
+ import Resolver from 'resolvers/resolver';
3
+
4
+ import sinon from 'sinon';
5
+
6
+ describe('ObjectResolver', () => {
7
+ class FakeResolver extends Resolver {
8
+ constructor(callback) {
9
+ super(callback);
10
+ this.dispose = sinon.spy();
11
+ }
12
+
13
+ get(props, seed) {
14
+ return `resolved value ${props.other} with ${seed}`;
15
+ }
16
+
17
+ triggerCallback() {
18
+ this._handleChange();
19
+ }
20
+ }
21
+
22
+ context('with flat fragment', () => {
23
+ it('it copies scalar values', () => {
24
+ var objectResolver = new ObjectResolver({});
25
+
26
+ var result = objectResolver.get({key: 123});
27
+
28
+ expect(result.key).to.eq(123)
29
+ });
30
+
31
+ it('resolves to null if null is passed as props', () => {
32
+ var objectResolver = new ObjectResolver({
33
+ key: (callback) => {
34
+ return new FakeResolver(callback);
35
+ }
36
+ });
37
+
38
+ var result = objectResolver.get(null);
39
+
40
+ expect(result).to.eq(null)
41
+ });
42
+
43
+ it('uses resolver in fragment to resolve value for key', () => {
44
+ var objectResolver = new ObjectResolver({
45
+ key: (callback) => {
46
+ return new FakeResolver(callback);
47
+ }
48
+ });
49
+
50
+ var result = objectResolver.get({other: 123}, 'SEED');
51
+
52
+ expect(result.key).to.eq('resolved value 123 with SEED');
53
+ });
54
+
55
+ it('invokes callback when some resolver signals a change', () => {
56
+ var callback = sinon.spy();
57
+ var resolver;
58
+ var objectResolver = new ObjectResolver({
59
+ key: (callback) => {
60
+ resolver = new FakeResolver(callback);
61
+ return resolver;
62
+ }
63
+ }, callback);
64
+
65
+ objectResolver.get({});
66
+ resolver.triggerCallback();
67
+
68
+ expect(callback).to.have.been.called;
69
+ });
70
+ });
71
+
72
+ context('with nested fragment', () => {
73
+ it('recreates structure with copied scalars ', () => {
74
+ var objectResolver = new ObjectResolver();
75
+
76
+ var result = objectResolver.get({
77
+ some: {
78
+ nested: {
79
+ key: 'hello'
80
+ }
81
+ }
82
+ });
83
+
84
+ expect(result.some.nested.key).to.eq('hello');
85
+ });
86
+
87
+ it('uses resolver in fragment to resolve nested key', () => {
88
+ var objectResolver = new ObjectResolver({
89
+ nested: {
90
+ key: (callback) => {
91
+ return new FakeResolver(callback);
92
+ }
93
+ }
94
+ });
95
+
96
+ var result = objectResolver.get({nested: {other: 123}}, 'SEED');
97
+
98
+ expect(result.nested.key).to.eq('resolved value 123 with SEED');
99
+ });
100
+
101
+ it('keeps null event if resolver in nested fragment is present', () => {
102
+ var objectResolver = new ObjectResolver({
103
+ nested: {
104
+ key: (callback) => {
105
+ return new FakeResolver(callback);
106
+ }
107
+ }
108
+ });
109
+
110
+ var result = objectResolver.get({nested: null}, 'SEED');
111
+
112
+ expect(result.nested).to.eq(null);
113
+ });
114
+
115
+ it('invokes callback when resolver of nested key signals a change', () => {
116
+ var callback = sinon.spy();
117
+ var resolver;
118
+ var objectResolver = new ObjectResolver({
119
+ nested: {
120
+ key: (callback) => {
121
+ resolver = new FakeResolver(callback);
122
+ return resolver;
123
+ }
124
+ }
125
+ }, callback);
126
+
127
+ objectResolver.get({nested: {}});
128
+ resolver.triggerCallback();
129
+
130
+ expect(callback).to.have.been.called;
131
+ });
132
+ });
133
+
134
+ it('passes dispose to resolver created from fragment', () => {
135
+ var resolver;
136
+ var objectResolver = new ObjectResolver({
137
+ key: (callback) => {
138
+ resolver = new FakeResolver(callback);
139
+ return resolver;
140
+ }
141
+ });
142
+
143
+ objectResolver.get({});
144
+ objectResolver.dispose();
145
+
146
+ expect(resolver.dispose).to.have.been.called;
147
+ });
148
+
149
+ it('passes dispose to nested resolver', () => {
150
+ var resolver;
151
+ var objectResolver = new ObjectResolver({
152
+ nested: {
153
+ key: (callback) => {
154
+ resolver = new FakeResolver(callback);
155
+ return resolver;
156
+ }
157
+ }
158
+ });
159
+
160
+ objectResolver.get({nested: {}});
161
+ objectResolver.dispose();
162
+
163
+ expect(resolver.dispose).to.have.been.called;
164
+ });
165
+ });
@@ -0,0 +1,23 @@
1
+ import PageTypeResolver from 'resolvers/page_type_resolver';
2
+
3
+ describe('PageTypeResolver', () => {
4
+ it('uses page type seed data', () => {
5
+ var seed = {page_types: {
6
+ audio_page: {some: 'data'}
7
+ }};
8
+ var pageTypeResolver = new PageTypeResolver();
9
+
10
+ var result = pageTypeResolver.get({type: 'audio_page'}, seed);
11
+
12
+ expect(result.some).to.eq('data');
13
+ });
14
+
15
+ it('sets name property to page type name', () => {
16
+ var seed = {page_types: {}};
17
+ var pageTypeResolver = new PageTypeResolver();
18
+
19
+ var result = pageTypeResolver.get({type: 'audio_page'}, seed);
20
+
21
+ expect(result.name).to.eq('audio_page');
22
+ });
23
+ });
@@ -0,0 +1,128 @@
1
+ import SeedResolver from 'resolvers/seed_resolver';
2
+
3
+ describe('SeedResolver', () => {
4
+ it('gets props from seed data referenced by id', () => {
5
+ var seed = {pages: [{id: 1, title: 'Some title'}]};
6
+ var modelResolver = new SeedResolver({
7
+ seedProperty: 'pages',
8
+ attributesForProps: ['title'],
9
+ property: 'modelId'
10
+ });
11
+
12
+ var result = modelResolver.get({modelId: 1}, seed);
13
+
14
+ expect(result).to.deep.eq({title: 'Some title'})
15
+ });
16
+
17
+ it('can map attribute names', () => {
18
+ var seed = {pages: [{id: 1, template: 'video'}]};
19
+ var modelResolver = new SeedResolver({
20
+ seedProperty: 'pages',
21
+ attributesForProps: [['type', 'template']],
22
+ property: 'modelId'
23
+ });
24
+
25
+ var result = modelResolver.get({modelId: 1}, seed);
26
+
27
+ expect(result).to.deep.eq({type: 'video'})
28
+ });
29
+
30
+ it('camelizes attribute names', () => {
31
+ var seed = {pages: [{id: 1, background_image_id: 1}]};
32
+ var modelResolver = new SeedResolver({
33
+ seedProperty: 'pages',
34
+ attributesForProps: ['background_image_id'],
35
+ property: 'modelId'
36
+ });
37
+
38
+ var result = modelResolver.get({modelId: 1}, seed);
39
+
40
+ expect(result).to.deep.eq({backgroundImageId: 1})
41
+ });
42
+
43
+ it('gets null if property is missing', () => {
44
+ var modelResolver = new SeedResolver({
45
+ seedProperty: 'pages',
46
+ attributesForProps: ['title'],
47
+ property: 'modelId'
48
+ });
49
+
50
+ var result = modelResolver.get({}, {});
51
+
52
+ expect(result).to.eq(null)
53
+ });
54
+
55
+ it('gets null if seed is missing', () => {
56
+ var modelResolver = new SeedResolver({
57
+ seedProperty: 'pages',
58
+ attributesForProps: ['title'],
59
+ property: 'modelId'
60
+ });
61
+
62
+ var result = modelResolver.get({modelId: 1}, {});
63
+
64
+ expect(result).to.eq(null)
65
+ });
66
+
67
+ it('can use custom id attribute', () => {
68
+ var seed = {pages: [{perma_id: 1, title: 'Some title'}]};
69
+ var modelResolver = new SeedResolver({
70
+ seedProperty: 'pages',
71
+ attributesForProps: ['title'],
72
+ idAttribute: 'perma_id',
73
+ property: 'modelPermaId'
74
+ });
75
+
76
+ var result = modelResolver.get({modelPermaId: 1}, seed);
77
+
78
+ expect(result).to.deep.eq({title: 'Some title'})
79
+ });
80
+
81
+ context('with includeConfiguration option', () => {
82
+ it('includes attributes from a nested configuration object', () => {
83
+ var seed = {
84
+ pages: [
85
+ {id: 1, title: 'Some title', configuration: {setting: true}}
86
+ ]
87
+ };
88
+ var modelResolver = new SeedResolver({
89
+ seedProperty: 'pages',
90
+ attributesForProps: ['title'],
91
+ includeConfiguration: true,
92
+ property: 'modelId'
93
+ });
94
+
95
+ var result = modelResolver.get({modelId: 1}, seed);
96
+
97
+ expect(result).to.deep.eq({title: 'Some title', setting: true})
98
+ });
99
+
100
+ it('deeply camelizes configuration attribute names', () => {
101
+ var seed = {
102
+ pages: [
103
+ {
104
+ id: 1,
105
+ title: 'Some title',
106
+ configuration: {
107
+ page_links: [
108
+ {
109
+ image_id: 1
110
+ }
111
+ ]
112
+ }
113
+ }
114
+ ]
115
+ };
116
+ var modelResolver = new SeedResolver({
117
+ seedProperty: 'pages',
118
+ attributesForProps: ['title'],
119
+ includeConfiguration: true,
120
+ property: 'modelId'
121
+ });
122
+
123
+ var result = modelResolver.get({modelId: 1}, seed);
124
+
125
+ expect(result).to.deep.eq({title: 'Some title', pageLinks: [{imageId: 1}]})
126
+ });
127
+ });
128
+ });
@@ -0,0 +1,16 @@
1
+ //import PageHeader from 'components/page-header.jsx';
2
+ //import React from 'react/addons';
3
+ //
4
+ //const {renderIntoDocument} = React.addons.TestUtils
5
+ //
6
+ //describe('stub', () => {
7
+ // it('passes', () => {
8
+ // expect(PageHeader).to.be.a('function');
9
+ // });
10
+ //
11
+ // it('renders', () => {
12
+ // var result = React.renderToStaticMarkup(<PageHeader text="Hello" />);
13
+ //
14
+ // expect(result).to.eq('<h1>Hello</h1>'); y
15
+ // });
16
+ //});
@@ -0,0 +1,7 @@
1
+ import TestUtils from 'react-addons-test-utils';
2
+
3
+ export default function(component, props, ...children) {
4
+ const shallowRenderer = TestUtils.createRenderer();
5
+ shallowRenderer.render(React.createElement(component, props, children.length > 1 ? children : children[0]));
6
+ return shallowRenderer.getRenderOutput();
7
+ };
@@ -0,0 +1,60 @@
1
+ import React from 'react';
2
+ import classNames from 'classnames';
3
+
4
+ /**
5
+ * Display an element with a background image.
6
+ */
7
+ export default class BackgroundImage extends React.Component {
8
+ static propTypes = {
9
+ /** The id of the image file to display */
10
+ imageFileId: React.PropTypes.number,
11
+
12
+ /** Background position */
13
+ position: React.PropTypes.arrayOf(React.PropTypes.number),
14
+
15
+ /** Additional CSS classes. */
16
+ className: React.PropTypes.string,
17
+
18
+ /** Used to lazy load images. */
19
+ loaded: React.PropTypes.bool
20
+ }
21
+
22
+ static defaultProps = {
23
+ position: [50, 50]
24
+ }
25
+
26
+ render() {
27
+ return (
28
+ <div className={this.cssClass()} style={this.style()}>
29
+ </div>
30
+ );
31
+ }
32
+
33
+ cssClass() {
34
+ return classNames(
35
+ this.props.className,
36
+ this.props.loaded ? 'load_image' : null,
37
+ this.imageCssClass()
38
+ );
39
+ }
40
+
41
+ imageCssClass() {
42
+ return ['image', this.props.imageFileId || 'none'].join('_');
43
+ }
44
+
45
+ style() {
46
+ return {
47
+ backgroundPosition: `${this.positionCoordinate(0)}% ${this.positionCoordinate(1)}%`
48
+ };
49
+ }
50
+
51
+ positionCoordinate(index) {
52
+ var coordinate = this.props.position[index];
53
+
54
+ if (typeof coordinate === 'undefined') {
55
+ return 50;
56
+ }
57
+
58
+ return coordinate;
59
+ }
60
+ };
@@ -0,0 +1,23 @@
1
+ import React from 'react';
2
+
3
+ import createPageComponent from '../create_page_component.jsx';
4
+
5
+ import BackgroundImage from './background_image.jsx';
6
+
7
+ class LazyBackgroundImage extends React.Component {
8
+ static contextTypes = {
9
+ pageIsPreloaded: React.PropTypes.bool
10
+ }
11
+
12
+ constructor(props) {
13
+ super(props);
14
+ }
15
+
16
+ render() {
17
+ return (
18
+ <BackgroundImage {...this.props} loaded={this.context.pageIsPreloaded} />
19
+ );
20
+ }
21
+ };
22
+
23
+ export default createPageComponent(LazyBackgroundImage);
@@ -0,0 +1,19 @@
1
+ import React from 'react';
2
+
3
+ import createPageComponent from '../create_page_component.jsx';
4
+
5
+ import PageThumbnail from './page_thumbnail.jsx';
6
+
7
+ class LazyLoadedPageThumbnail extends React.Component {
8
+ static contextTypes = {
9
+ pageIsPreloaded: React.PropTypes.bool
10
+ }
11
+
12
+ render() {
13
+ return (
14
+ <PageThumbnail {...this.props} lazy={true} loaded={this.context.pageIsPreloaded} />
15
+ );
16
+ }
17
+ };
18
+
19
+ export default createPageComponent(LazyLoadedPageThumbnail);