hippo-fw 0.9.1 → 0.9.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/Gemfile +16 -11
- data/Rakefile +0 -7
- data/bin/hippo +5 -1
- data/client/hippo/__mocks__/config.js +2 -3
- data/client/hippo/boot.jsx +0 -2
- data/client/hippo/components/asset.jsx +1 -1
- data/client/hippo/components/form.jsx +8 -4
- data/client/hippo/components/form/fields.jsx +28 -14
- data/client/hippo/components/form/model.js +65 -20
- data/client/hippo/components/form/wrapper.jsx +11 -5
- data/client/hippo/components/icon.jsx +1 -1
- data/client/hippo/components/master-detail.jsx +66 -0
- data/client/hippo/components/master-detail.scss +50 -0
- data/client/hippo/components/record-finder.jsx +5 -5
- data/client/hippo/components/tool-tip.jsx +20 -0
- data/client/hippo/config.js +5 -3
- data/client/hippo/extensions/base.js +4 -0
- data/client/hippo/lib/smooth-scroll.js +17 -16
- data/client/hippo/models/asset.js +8 -10
- data/client/hippo/models/collection.js +1 -4
- data/client/hippo/models/query/array-result.js +11 -9
- data/client/hippo/models/sync.js +3 -3
- data/client/hippo/models/tenant.js +29 -0
- data/client/hippo/screens/system-settings.jsx +5 -4
- data/client/hippo/screens/system-settings/mailer-config.jsx +11 -17
- data/client/hippo/screens/system-settings/tenant.jsx +90 -0
- data/client/hippo/screens/user-management/edit-form.jsx +15 -25
- data/client/hippo/testing/index.js +1 -0
- data/client/hippo/workspace/styles.scss +0 -23
- data/command-reference-files/initial/.babelrc +10 -8
- data/command-reference-files/initial/Gemfile +1 -1
- data/{views/index.html → command-reference-files/initial/views/index.erb} +1 -0
- data/config/routes.rb +48 -17
- data/config/webpack.config.js +7 -12
- data/db/migrate/01_create_tenants.rb +13 -0
- data/db/migrate/{01_create_system_settings.rb → 02_create_system_settings.rb} +2 -1
- data/db/migrate/{02_create_assets.rb → 03_create_assets.rb} +2 -4
- data/db/migrate/{20140615031600_create_users.rb → 04_create_users.rb} +4 -2
- data/db/seed.rb +10 -1
- data/hippo-fw.gemspec +53 -51
- data/lib/hippo.rb +7 -1
- data/lib/hippo/access.rb +0 -1
- data/lib/hippo/access/roles/basic_user.rb +2 -0
- data/lib/hippo/api.rb +4 -3
- data/lib/hippo/{access → api}/authentication_provider.rb +3 -1
- data/lib/hippo/api/controller_base.rb +2 -2
- data/lib/hippo/api/handlers/asset.rb +28 -2
- data/lib/hippo/api/handlers/tenant.rb +26 -0
- data/lib/hippo/api/helper_methods.rb +5 -13
- data/lib/hippo/api/request_wrapper.rb +8 -1
- data/lib/hippo/api/root.rb +50 -51
- data/lib/hippo/api/route_set.rb +101 -0
- data/lib/hippo/api/routing.rb +9 -98
- data/lib/hippo/api/tenant_domain_router.rb +21 -0
- data/lib/hippo/asset.rb +1 -0
- data/lib/hippo/command.rb +1 -24
- data/lib/hippo/command/app.rb +4 -3
- data/lib/hippo/command/console.rb +7 -0
- data/lib/hippo/command/generate.rb +0 -5
- data/lib/hippo/command/guard.rb +1 -0
- data/lib/hippo/command/jest.rb +2 -2
- data/lib/hippo/command/server.rb +1 -3
- data/lib/hippo/command/webpack.rb +6 -26
- data/lib/hippo/configuration.rb +21 -13
- data/lib/hippo/db.rb +2 -0
- data/lib/hippo/db/migrations.rb +9 -2
- data/lib/hippo/extension.rb +49 -14
- data/lib/hippo/extension/definition.rb +0 -4
- data/lib/hippo/guard_tasks.rb +3 -11
- data/lib/hippo/mailer.rb +28 -16
- data/lib/hippo/model.rb +10 -0
- data/lib/hippo/numbers.rb +1 -1
- data/lib/hippo/rake_tasks.rb +7 -1
- data/lib/hippo/spec_helper.rb +33 -11
- data/lib/hippo/system_settings.rb +1 -0
- data/lib/hippo/templates/base.rb +1 -1
- data/lib/hippo/templates/mail.rb +26 -0
- data/lib/hippo/templates/tenant_change.rb +23 -0
- data/lib/hippo/tenant.rb +53 -0
- data/lib/hippo/user.rb +12 -6
- data/lib/hippo/version.rb +1 -1
- data/lib/hippo/webpack.rb +57 -0
- data/lib/hippo/{command → webpack}/client_config.rb +7 -21
- data/package.json +3 -3
- data/spec/client/components/__snapshots__/master-detail.spec.jsx.snap +22 -0
- data/spec/client/components/form.spec.jsx +14 -14
- data/spec/client/components/master-detail.spec.jsx +24 -0
- data/spec/client/components/record-finder.spec.jsx +5 -2
- data/spec/client/models/asset.spec.js +2 -13
- data/spec/client/models/base.spec.js +1 -11
- data/spec/client/models/query.spec.js +2 -4
- data/spec/client/models/sync.spec.js +7 -0
- data/spec/client/screens/__snapshots__/system-settings.spec.jsx.snap +79 -0
- data/spec/client/screens/system-settings-tenants.spec.jsx +18 -0
- data/spec/client/workspace/__snapshots__/menu.spec.jsx.snap +29 -313
- data/spec/client/workspace/menu.spec.jsx +1 -9
- data/spec/factories/tenant.rb +13 -0
- data/spec/fixtures/mail/test_email.liquid +1 -0
- data/spec/fixtures/{test_printer.tex → test_printer.tex.erb} +0 -0
- data/spec/server/api/controller_base_spec.rb +1 -1
- data/spec/server/api/tenant_change_spec.rb +24 -0
- data/spec/server/api/tenant_isolation_spec.rb +37 -0
- data/spec/server/asset_spec.rb +6 -6
- data/spec/server/command_spec.rb +0 -5
- data/spec/server/mailer_spec.rb +25 -23
- data/spec/server/numbers_spec.rb +12 -13
- data/spec/server/print/form_spec.rb +2 -1
- data/spec/server/strings_spec.rb +13 -13
- data/templates/.babelrc +10 -8
- data/templates/js/screen-definitions.js +8 -10
- data/templates/mail/tenant_change.liquid +13 -0
- data/{command-reference-files/initial/views/index.html → views/index.erb} +5 -2
- data/yarn.lock +22 -169
- metadata +56 -30
- data/client/hippo/components/form/field-prop-type.js +0 -16
- data/lib/hippo/api/default_routes.rb +0 -38
- data/lib/hippo/command/generate_component.rb +0 -28
- data/lib/hippo/command/generate_component.usage +0 -11
- data/lib/hippo/command/webpack_view.rb +0 -32
- data/lib/hippo/multi_server_boot.rb +0 -26
- data/lib/hippo/reloadable_view.rb +0 -13
- data/templates/client/components/.gitkeep +0 -0
- data/templates/client/components/BaseComponent.coffee +0 -9
- data/templates/client/components/Component.cjsx +0 -4
- data/templates/client/components/template.html +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 646a4300a544e32e68520975fb4b9bad15e7ddd4
|
4
|
+
data.tar.gz: 98f55a67a95e64261cff0560af01143f2afaddeb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cfd18318f831660561a8e30f848c2b8f52540a2dc982b780ef7a4f331ac163b9c9073c2348dacd3fc22d62accd7367fd92b57563089592a8bd1562857df2eb25
|
7
|
+
data.tar.gz: 7862bfe0cc3c3e977c9958be2a8600522d9dbbf597d62968250d538ab0fc23393fab759c89a8789d9fc0c0722308d2ba4bc2daeb75bee5e8b5b2f41eada9d15a
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.4.
|
1
|
+
2.4.1
|
data/Gemfile
CHANGED
@@ -1,17 +1,22 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
|
3
|
-
|
4
|
-
git: 'https://github.com/nathanstitt/yard-activerecord',
|
5
|
-
branch: 'develop'
|
3
|
+
gemspec
|
6
4
|
|
7
|
-
gem "
|
5
|
+
gem "activerecord-multi-tenant", git: 'https://github.com/citusdata/activerecord-multi-tenant.git', branch: 'release-0.5.1'
|
8
6
|
|
9
|
-
gem '
|
10
|
-
gem 'pry-byebug'
|
7
|
+
# gem "activerecord-multi-tenant", git: "https://github.com/nathanstitt/activerecord-multi-tenant", branch: 'query_rewriter'
|
11
8
|
|
12
|
-
gem "
|
13
|
-
gem "webpack_driver", git: "https://github.com/nathanstitt/webpack_driver", branch: 'master'
|
14
|
-
gem "guard-jest", git: "https://github.com/nathanstitt/guard-jest", branch: 'master'
|
15
|
-
gem "bump"
|
9
|
+
# gem "webpack_driver", git: "https://github.com/nathanstitt/webpack_driver", branch: 'master'
|
16
10
|
|
17
|
-
|
11
|
+
group :development, :test do
|
12
|
+
gem "yard-activerecord",
|
13
|
+
git: 'https://github.com/nathanstitt/yard-activerecord',
|
14
|
+
branch: 'develop'
|
15
|
+
|
16
|
+
gem "temping", '~> 3.9.0'
|
17
|
+
|
18
|
+
gem 'puma'
|
19
|
+
gem 'pry-byebug'
|
20
|
+
# gem "guard-jest", git: "https://github.com/nathanstitt/guard-jest", branch: 'master'
|
21
|
+
gem "bump"
|
22
|
+
end
|
data/Rakefile
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'bundler/setup'
|
2
2
|
require "bundler/gem_tasks"
|
3
|
-
require 'rake/testtask'
|
4
3
|
require 'yard'
|
5
4
|
require 'yard-activerecord'
|
6
5
|
require_relative 'yard_ext/all'
|
@@ -11,12 +10,6 @@ require "bump/tasks"
|
|
11
10
|
|
12
11
|
Dir.glob('tasks/*.rake').each { |r| load r}
|
13
12
|
|
14
|
-
Rake::TestTask.new do |t|
|
15
|
-
t.libs << 'test'
|
16
|
-
t.pattern = "test/*_test.rb"
|
17
|
-
end
|
18
|
-
|
19
|
-
|
20
13
|
YARD::Rake::YardocTask.new do |t|
|
21
14
|
t.files = ['lib/skr/concerns/*.rb','lib/**/*.rb','db/schema.rb']
|
22
15
|
t.options = ["--title=Stockor Core Documentation",
|
data/bin/hippo
CHANGED
@@ -1,9 +1,8 @@
|
|
1
|
+
/* global jest */
|
1
2
|
window.localStorage = {
|
2
|
-
|
3
3
|
getItem() {
|
4
4
|
return '{}';
|
5
5
|
},
|
6
|
-
|
7
6
|
};
|
8
7
|
|
9
8
|
const config = jest.genMockFromModule('hippo/config');
|
@@ -11,7 +10,7 @@ const config = jest.genMockFromModule('hippo/config');
|
|
11
10
|
config.bootstrapUserData = jest.fn();
|
12
11
|
config.reset = jest.fn();
|
13
12
|
Object.defineProperty(config, 'api_path', {
|
14
|
-
value: '/api'
|
13
|
+
value: '/api',
|
15
14
|
});
|
16
15
|
|
17
16
|
export default config;
|
data/client/hippo/boot.jsx
CHANGED
@@ -45,7 +45,7 @@ export default class Asset extends React.PureComponent {
|
|
45
45
|
}
|
46
46
|
|
47
47
|
preview() {
|
48
|
-
if (this.asset) {
|
48
|
+
if (this.asset && this.asset.exists) {
|
49
49
|
return this.asset.isImage ?
|
50
50
|
<img src={this.asset.previewUrl} alt="" /> :
|
51
51
|
<DocumentIcon size="xlarge" type="status" />;
|
@@ -9,8 +9,7 @@ import { isBlank } from '../lib/util';
|
|
9
9
|
|
10
10
|
export Form from './form/wrapper';
|
11
11
|
export Field from './form/fields';
|
12
|
-
export
|
13
|
-
export FormFieldPropType from './form/field-prop-type';
|
12
|
+
export { FormField, FormState } from './form/model';
|
14
13
|
|
15
14
|
function buildTest(options, defaultOptions) {
|
16
15
|
return merge({}, defaultOptions, options);
|
@@ -44,8 +43,13 @@ export function validEmail(options) {
|
|
44
43
|
return buildTest(options, { message: 'must be a valid email', test: email => isEmail(email || '') });
|
45
44
|
}
|
46
45
|
|
47
|
-
export function validURL(options) {
|
48
|
-
return buildTest(options, {
|
46
|
+
export function validURL(options = {}) {
|
47
|
+
return buildTest(options, {
|
48
|
+
message: 'must be a valid address',
|
49
|
+
test(url) {
|
50
|
+
return (options.allowBlank && !url) || isURL(url || '');
|
51
|
+
},
|
52
|
+
});
|
49
53
|
}
|
50
54
|
|
51
55
|
export function booleanValue(options = {}) {
|
@@ -5,12 +5,13 @@ import { inject, observer } from 'mobx-react';
|
|
5
5
|
import classnames from 'classnames';
|
6
6
|
import { Col, getColumnProps } from 'react-flexbox-grid';
|
7
7
|
|
8
|
+
import invariant from 'invariant';
|
8
9
|
import Field from 'grommet/components/FormField';
|
9
10
|
import NumberInput from 'grommet/components/NumberInput';
|
10
11
|
|
11
12
|
import { titleize } from '../../lib/util';
|
12
|
-
import FormFieldPropType from './field-prop-type';
|
13
13
|
|
14
|
+
import { FormField as FormFieldModel } from './model';
|
14
15
|
import DateWrapper from './fields/date-wrapper';
|
15
16
|
import SelectWrapper from './fields/select-wrapper';
|
16
17
|
import TextWrapper from './fields/text-wrapper';
|
@@ -27,8 +28,17 @@ const TypesMapping = {
|
|
27
28
|
|
28
29
|
};
|
29
30
|
|
30
|
-
|
31
|
+
|
32
|
+
@inject('formState')
|
33
|
+
@observer
|
31
34
|
export default class FormField extends React.PureComponent {
|
35
|
+
static propTypes = Object.assign({
|
36
|
+
label: PropTypes.string,
|
37
|
+
name: PropTypes.string.isRequired,
|
38
|
+
className: PropTypes.string,
|
39
|
+
type: PropTypes.string,
|
40
|
+
}, Col.PropTypes)
|
41
|
+
|
32
42
|
static defaultProps = {
|
33
43
|
label: '',
|
34
44
|
className: '',
|
@@ -39,33 +49,37 @@ export default class FormField extends React.PureComponent {
|
|
39
49
|
if (this.inputRef) { this.inputRef.focus(); }
|
40
50
|
}
|
41
51
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
52
|
+
componentWillMount() {
|
53
|
+
this.field = this.props.formState.setField(this.props.name, this.props);
|
54
|
+
}
|
55
|
+
|
56
|
+
componentWillReceiveProps(nextProps) {
|
57
|
+
invariant(nextProps.name === this.props.name,
|
58
|
+
`cannot update 'name' prop from ${this.props.name} to ${nextProps.name}`);
|
59
|
+
this.field.update(this.props);
|
60
|
+
}
|
49
61
|
|
50
62
|
render() {
|
51
63
|
const {
|
52
|
-
name, className, autoFocus, type, children, label,
|
64
|
+
name, className, autoFocus, type, children, label,
|
65
|
+
validate: _, formState: __, help: ___, ...otherProps
|
53
66
|
} = getColumnProps(this.props);
|
54
67
|
const InputTag = TypesMapping[type] || TypesMapping.text;
|
55
|
-
|
68
|
+
|
56
69
|
return (
|
57
70
|
<div className={classnames('form-field', className)}>
|
58
71
|
<Field
|
59
72
|
label={label || titleize(name)}
|
60
|
-
error={field.invalidMessage}
|
73
|
+
error={this.field.invalidMessage}
|
74
|
+
help={this.field.help}
|
61
75
|
>
|
62
76
|
<InputTag
|
63
77
|
name={name}
|
64
78
|
autoFocus={autoFocus}
|
65
79
|
ref={f => (this.inputRef = f)}
|
66
|
-
value={field.value || ''}
|
80
|
+
value={this.field.value || ''}
|
67
81
|
type={InputTag === TypesMapping.text ? this.props.type : undefined}
|
68
|
-
{...field.events}
|
82
|
+
{...this.field.events}
|
69
83
|
{...otherProps}
|
70
84
|
/>
|
71
85
|
{children}
|
@@ -1,30 +1,41 @@
|
|
1
|
-
/* eslint no-param-reassign: ["error", {
|
2
|
-
|
1
|
+
/* eslint no-param-reassign: ["error", {
|
2
|
+
"props": true, "ignorePropertyModificationsFor": ["field", "model"]
|
3
|
+
}] */
|
4
|
+
import { observable, computed, when, action } from 'mobx';
|
3
5
|
import {
|
4
|
-
|
6
|
+
pick, isFunction, mapValues, every, get, filter, isNil, each,
|
5
7
|
} from 'lodash';
|
6
8
|
|
7
|
-
class
|
9
|
+
export class FormField {
|
8
10
|
name: '';
|
9
11
|
@observable isTouched = false
|
12
|
+
@observable isChanged = false;
|
10
13
|
@observable value = '';
|
11
14
|
@observable message = '';
|
15
|
+
@observable help;
|
16
|
+
@observable validate;
|
17
|
+
@observable default;
|
12
18
|
|
13
19
|
constructor(name, attrs) {
|
14
20
|
this.name = name;
|
15
|
-
|
16
|
-
if (!isObject(extendWith)) {
|
17
|
-
extendWith = { test: extendWith };
|
18
|
-
}
|
19
|
-
extend(this, extendWith);
|
21
|
+
this.update(attrs);
|
20
22
|
if (this.default) {
|
21
23
|
this.value = this.default;
|
22
24
|
}
|
23
25
|
}
|
24
26
|
|
27
|
+
update(attrs) {
|
28
|
+
each(pick(attrs, [
|
29
|
+
'name', 'default', 'help', 'validate',
|
30
|
+
]), (v, k) => {
|
31
|
+
this[k] = isFunction(v) ? v.call(this) : v;
|
32
|
+
});
|
33
|
+
}
|
34
|
+
|
25
35
|
@action.bound
|
26
|
-
onChange(
|
27
|
-
this.
|
36
|
+
onChange({ target: { value: updatedValue } }) {
|
37
|
+
this.isChanged = (this.value !== updatedValue);
|
38
|
+
this.value = updatedValue;
|
28
39
|
}
|
29
40
|
|
30
41
|
@action.bound
|
@@ -33,11 +44,12 @@ class Field {
|
|
33
44
|
}
|
34
45
|
|
35
46
|
@computed get isValid() {
|
36
|
-
|
47
|
+
if (!this.validate) { return true; }
|
48
|
+
return !!this.validate.test(this.value);
|
37
49
|
}
|
38
50
|
|
39
51
|
@computed get invalidMessage() {
|
40
|
-
return (!this.isValid && this.isTouched) ? this.message : null;
|
52
|
+
return (!this.isValid && this.isTouched) ? this.validate.message : null;
|
41
53
|
}
|
42
54
|
|
43
55
|
get events() {
|
@@ -47,33 +59,66 @@ class Field {
|
|
47
59
|
|
48
60
|
};
|
49
61
|
}
|
62
|
+
|
63
|
+
reset() {
|
64
|
+
this.value = '';
|
65
|
+
this.isTouched = false;
|
66
|
+
this.isChanged = false;
|
67
|
+
}
|
50
68
|
}
|
51
69
|
|
52
70
|
|
53
|
-
export
|
71
|
+
export class FormState {
|
54
72
|
|
55
73
|
fields = observable.map();
|
56
74
|
|
57
|
-
|
75
|
+
@action
|
76
|
+
setFields(fields) {
|
58
77
|
this.fields.replace(
|
59
|
-
mapValues(fields, (field, name) => new
|
78
|
+
mapValues(fields, (field, name) => new FormField(name, field)),
|
60
79
|
);
|
61
80
|
}
|
62
81
|
|
82
|
+
@action
|
83
|
+
setField(name, attrs) {
|
84
|
+
let field = this.fields.get(name);
|
85
|
+
if (field) {
|
86
|
+
field.update(attrs);
|
87
|
+
} else {
|
88
|
+
field = new FormField(name, attrs);
|
89
|
+
this.fields.set(name, field);
|
90
|
+
}
|
91
|
+
return field;
|
92
|
+
}
|
93
|
+
|
94
|
+
@computed get invalidFields() {
|
95
|
+
return filter(this.fields.values(), { isValid: false });
|
96
|
+
}
|
97
|
+
|
63
98
|
@computed get isValid() {
|
64
|
-
return
|
99
|
+
return 0 === this.invalidFields.length;
|
65
100
|
}
|
66
101
|
|
67
102
|
@computed get isTouched() {
|
68
103
|
return !every(this.fields.values(), { isTouched: false });
|
69
104
|
}
|
70
105
|
|
71
|
-
get(
|
72
|
-
|
106
|
+
get(path, defaultValue) {
|
107
|
+
const [name, ...rest] = path.split('.');
|
108
|
+
const field = this.fields.get(name);
|
109
|
+
if (!field) { return defaultValue; }
|
110
|
+
if (rest.length) {
|
111
|
+
return get(field, rest.join('.'), defaultValue);
|
112
|
+
}
|
113
|
+
return field;
|
114
|
+
}
|
115
|
+
|
116
|
+
reset() {
|
117
|
+
this.fields.forEach(field => field.reset());
|
73
118
|
}
|
74
119
|
|
75
120
|
set(values) {
|
76
|
-
this.fields.forEach((field, name) => (field.value = values[name]));
|
121
|
+
this.fields.forEach((field, name) => (field.value = isNil(values[name]) ? '' : values[name]));
|
77
122
|
}
|
78
123
|
|
79
124
|
setFromModel(model) {
|
@@ -2,30 +2,36 @@ import React from 'react';
|
|
2
2
|
|
3
3
|
import { Provider, observer } from 'mobx-react';
|
4
4
|
|
5
|
-
import
|
5
|
+
import { FormState } from './model';
|
6
6
|
|
7
7
|
@observer
|
8
8
|
export default class FormWrapper extends React.PureComponent {
|
9
9
|
|
10
10
|
static propTypes = {
|
11
11
|
children: React.PropTypes.node.isRequired,
|
12
|
-
|
12
|
+
state: React.PropTypes.instanceOf(FormState),
|
13
13
|
tag: React.PropTypes.string,
|
14
14
|
className: React.PropTypes.string,
|
15
15
|
}
|
16
16
|
|
17
|
+
static get defaultProps() {
|
18
|
+
return {
|
19
|
+
state: new FormState(),
|
20
|
+
};
|
21
|
+
}
|
22
|
+
|
17
23
|
renderTagless() {
|
18
24
|
return (
|
19
|
-
<Provider
|
25
|
+
<Provider formState={this.props.state}>
|
20
26
|
{this.props.children}
|
21
27
|
</Provider>
|
22
28
|
);
|
23
29
|
}
|
24
30
|
|
25
31
|
renderTagged() {
|
26
|
-
const { tag: Tag,
|
32
|
+
const { tag: Tag, state, children, ...otherProps } = this.props;
|
27
33
|
return (
|
28
|
-
<Provider
|
34
|
+
<Provider formState={state}>
|
29
35
|
<Tag {...otherProps}>
|
30
36
|
{children}
|
31
37
|
</Tag>
|
@@ -7,7 +7,7 @@ import classnames from 'classnames';
|
|
7
7
|
|
8
8
|
const DEFAULT_TOOLTIP_PROPS = { placement: 'top', trigger: 'click' };
|
9
9
|
|
10
|
-
export default class Icon extends React.
|
10
|
+
export default class Icon extends React.PureComponent {
|
11
11
|
|
12
12
|
static propTypes = {
|
13
13
|
type: PropTypes.string.isRequired,
|
@@ -0,0 +1,66 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import cn from 'classnames';
|
4
|
+
import { observable, action, computed } from 'mobx';
|
5
|
+
import { observer } from 'mobx-react';
|
6
|
+
import { delay } from 'lodash';
|
7
|
+
|
8
|
+
import './master-detail.scss';
|
9
|
+
|
10
|
+
const DELAY_TIME = 500;
|
11
|
+
|
12
|
+
@observer
|
13
|
+
export default class MasterDetail extends React.PureComponent {
|
14
|
+
|
15
|
+
|
16
|
+
static propTypes = {
|
17
|
+
master: PropTypes.element.isRequired,
|
18
|
+
detail: PropTypes.element,
|
19
|
+
}
|
20
|
+
|
21
|
+
@observable detailVisible;
|
22
|
+
|
23
|
+
componentWillUnmount() {
|
24
|
+
if (this.pendingDefer) { this.clearTimeout(this.pendingDefer); }
|
25
|
+
}
|
26
|
+
|
27
|
+
@action.bound
|
28
|
+
setVisible(val) {
|
29
|
+
this.pendingDefer = delay(() => {
|
30
|
+
this.pendingDefer = null;
|
31
|
+
this.detailVisible = val;
|
32
|
+
}, DELAY_TIME);
|
33
|
+
}
|
34
|
+
|
35
|
+
setVisible
|
36
|
+
componentWillReceiveProps(nextProps) {
|
37
|
+
if (this.props.detail && !nextProps.detail) {
|
38
|
+
this.setVisible(false);
|
39
|
+
} else if (!this.props.detail && nextProps.detail) {
|
40
|
+
this.setVisible(true);
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
@computed get className() {
|
45
|
+
return cn('master-detail-wrapper', {
|
46
|
+
'detail-visible': this.detailVisible,
|
47
|
+
'has-detail': this.props.detail,
|
48
|
+
'detail-removed': this.detailVisible && !this.props.detail,
|
49
|
+
});
|
50
|
+
}
|
51
|
+
|
52
|
+
render() {
|
53
|
+
return (
|
54
|
+
<div
|
55
|
+
className={this.className}
|
56
|
+
>
|
57
|
+
<div className="master">
|
58
|
+
{this.props.master}
|
59
|
+
</div>
|
60
|
+
<div className="detail">
|
61
|
+
{this.props.detail}
|
62
|
+
</div>
|
63
|
+
</div>
|
64
|
+
);
|
65
|
+
}
|
66
|
+
}
|