aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/services
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/services')
-rw-r--r--src/components/services/content/ServiceWebview.js73
-rw-r--r--src/components/services/content/Services.js81
-rw-r--r--src/components/services/tabs/TabBarSortableList.js44
-rw-r--r--src/components/services/tabs/TabItem.js136
-rw-r--r--src/components/services/tabs/Tabbar.js77
5 files changed, 411 insertions, 0 deletions
diff --git a/src/components/services/content/ServiceWebview.js b/src/components/services/content/ServiceWebview.js
new file mode 100644
index 000000000..043ff42ea
--- /dev/null
+++ b/src/components/services/content/ServiceWebview.js
@@ -0,0 +1,73 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { autorun } from 'mobx';
4import { observer } from 'mobx-react';
5import Webview from 'react-electron-web-view';
6import classnames from 'classnames';
7
8import ServiceModel from '../../../models/Service';
9
10@observer
11export default class ServiceWebview extends Component {
12 static propTypes = {
13 service: PropTypes.instanceOf(ServiceModel).isRequired,
14 setWebviewReference: PropTypes.func.isRequired,
15 };
16
17 static defaultProps = {
18 isActive: false,
19 };
20
21 state = {
22 forceRepaint: false,
23 };
24
25 componentDidMount() {
26 autorun(() => {
27 if (this.props.service.isActive) {
28 this.setState({ forceRepaint: true });
29 setTimeout(() => {
30 this.setState({ forceRepaint: false });
31 }, 100);
32 }
33 });
34 }
35
36 webview = null;
37
38 render() {
39 const {
40 service,
41 setWebviewReference,
42 } = this.props;
43
44 const webviewClasses = classnames({
45 services__webview: true,
46 'is-active': service.isActive,
47 'services__webview--force-repaint': this.state.forceRepaint,
48 });
49
50 return (
51 <div className={webviewClasses}>
52 <Webview
53 ref={(element) => { this.webview = element; }}
54
55 autosize
56 src={service.url}
57 preload="./webview/plugin.js"
58 partition={`persist:service-${service.id}`}
59
60 onDidAttach={() => setWebviewReference({
61 serviceId: service.id,
62 webview: this.webview.view,
63 })}
64
65 useragent={service.userAgent}
66
67 disablewebsecurity
68 allowpopups
69 />
70 </div>
71 );
72 }
73}
diff --git a/src/components/services/content/Services.js b/src/components/services/content/Services.js
new file mode 100644
index 000000000..03c68b06f
--- /dev/null
+++ b/src/components/services/content/Services.js
@@ -0,0 +1,81 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4import { Link } from 'react-router';
5import { defineMessages, intlShape } from 'react-intl';
6
7import Webview from './ServiceWebview';
8import Appear from '../../ui/effects/Appear';
9
10const messages = defineMessages({
11 welcome: {
12 id: 'services.welcome',
13 defaultMessage: '!!!Welcome to Franz',
14 },
15 getStarted: {
16 id: 'services.getStarted',
17 defaultMessage: '!!!Get started',
18 },
19});
20
21@observer
22export default class Services extends Component {
23 static propTypes = {
24 services: MobxPropTypes.arrayOrObservableArray.isRequired,
25 setWebviewReference: PropTypes.func.isRequired,
26 handleIPCMessage: PropTypes.func.isRequired,
27 openWindow: PropTypes.func.isRequired,
28 };
29
30 static defaultProps = {
31 services: [],
32 activeService: '',
33 };
34
35 static contextTypes = {
36 intl: intlShape,
37 };
38
39 render() {
40 const {
41 services,
42 handleIPCMessage,
43 setWebviewReference,
44 openWindow,
45 } = this.props;
46 const { intl } = this.context;
47
48 return (
49 <div className="services">
50 {services.length === 0 && (
51 <Appear
52 timeout={1500}
53 transitionName="slideUp"
54 >
55 <div className="services__no-service">
56 <img src="./assets/images/logo.svg" alt="" />
57 <h1>{intl.formatMessage(messages.welcome)}</h1>
58 <Appear
59 timeout={300}
60 transitionName="slideUp"
61 >
62 <Link to="/settings/recipes" className="button">
63 {intl.formatMessage(messages.getStarted)}
64 </Link>
65 </Appear>
66 </div>
67 </Appear>
68 )}
69 {services.map(service => (
70 <Webview
71 key={service.id}
72 service={service}
73 handleIPCMessage={handleIPCMessage}
74 setWebviewReference={setWebviewReference}
75 openWindow={openWindow}
76 />
77 ))}
78 </div>
79 );
80 }
81}
diff --git a/src/components/services/tabs/TabBarSortableList.js b/src/components/services/tabs/TabBarSortableList.js
new file mode 100644
index 000000000..c0a68d1a5
--- /dev/null
+++ b/src/components/services/tabs/TabBarSortableList.js
@@ -0,0 +1,44 @@
1import React from 'react';
2import { observer } from 'mobx-react';
3import { SortableContainer } from 'react-sortable-hoc';
4
5import TabItem from './TabItem';
6import { ctrlKey } from '../../../environment';
7
8export default SortableContainer(observer(({
9 services,
10 setActive,
11 reload,
12 toggleNotifications,
13 deleteService,
14 disableService,
15 openSettings,
16}) => (
17 <ul
18 className="tabs"
19 >
20 {services.map((service, index) => (
21 <TabItem
22 key={service.id}
23 clickHandler={() => setActive({ serviceId: service.id })}
24 service={service}
25 index={index}
26 shortcutIndex={index + 1}
27 reload={() => reload({ serviceId: service.id })}
28 toggleNotifications={() => toggleNotifications({ serviceId: service.id })}
29 deleteService={() => deleteService({ serviceId: service.id })}
30 disableService={() => disableService({ serviceId: service.id })}
31 openSettings={openSettings}
32 />
33 ))}
34 <li>
35 <button
36 className="sidebar__add-service"
37 onClick={() => openSettings({ path: 'recipes' })}
38 data-tip={`Add new service (${ctrlKey}+N)`}
39 >
40 <span className="mdi mdi-plus" />
41 </button>
42 </li>
43 </ul>
44)));
diff --git a/src/components/services/tabs/TabItem.js b/src/components/services/tabs/TabItem.js
new file mode 100644
index 000000000..9e03d2e21
--- /dev/null
+++ b/src/components/services/tabs/TabItem.js
@@ -0,0 +1,136 @@
1import { remote } from 'electron';
2import React, { Component } from 'react';
3import { defineMessages, intlShape } from 'react-intl';
4import PropTypes from 'prop-types';
5import { observer } from 'mobx-react';
6import classnames from 'classnames';
7import { SortableElement } from 'react-sortable-hoc';
8
9import ServiceModel from '../../../models/Service';
10import { ctrlKey } from '../../../environment';
11
12const { Menu } = remote;
13
14const messages = defineMessages({
15 reload: {
16 id: 'tabs.item.reload',
17 defaultMessage: '!!!Reload',
18 },
19 edit: {
20 id: 'tabs.item.edit',
21 defaultMessage: '!!!Edit',
22 },
23 disableNotifications: {
24 id: 'tabs.item.disableNotifications',
25 defaultMessage: '!!!Disable notifications',
26 },
27 enableNotifications: {
28 id: 'tabs.item.enableNotification',
29 defaultMessage: '!!!Enable notifications',
30 },
31 disableService: {
32 id: 'tabs.item.disableService',
33 defaultMessage: '!!!Disable Service',
34 },
35 deleteService: {
36 id: 'tabs.item.deleteService',
37 defaultMessage: '!!!Delete Service',
38 },
39});
40
41@observer
42class TabItem extends Component {
43 static propTypes = {
44 service: PropTypes.instanceOf(ServiceModel).isRequired,
45 clickHandler: PropTypes.func.isRequired,
46 shortcutIndex: PropTypes.number.isRequired,
47 reload: PropTypes.func.isRequired,
48 toggleNotifications: PropTypes.func.isRequired,
49 openSettings: PropTypes.func.isRequired,
50 deleteService: PropTypes.func.isRequired,
51 disableService: PropTypes.func.isRequired,
52 };
53
54 static contextTypes = {
55 intl: intlShape,
56 };
57
58 render() {
59 const {
60 service,
61 clickHandler,
62 shortcutIndex,
63 reload,
64 toggleNotifications,
65 deleteService,
66 disableService,
67 openSettings,
68 } = this.props;
69 const { intl } = this.context;
70
71
72 const menuTemplate = [{
73 label: service.name || service.recipe.name,
74 enabled: false,
75 }, {
76 type: 'separator',
77 }, {
78 label: intl.formatMessage(messages.reload),
79 click: reload,
80 }, {
81 label: intl.formatMessage(messages.edit),
82 click: () => openSettings({
83 path: `services/edit/${service.id}`,
84 }),
85 }, {
86 type: 'separator',
87 }, {
88 label: service.isNotificationEnabled
89 ? intl.formatMessage(messages.disableNotifications)
90 : intl.formatMessage(messages.enableNotifications),
91 click: () => toggleNotifications(),
92 }, {
93 label: intl.formatMessage(messages.disableService),
94 click: () => disableService(),
95 }, {
96 type: 'separator',
97 }, {
98 label: intl.formatMessage(messages.deleteService),
99 click: () => deleteService(),
100 }];
101 const menu = Menu.buildFromTemplate(menuTemplate);
102
103 return (
104 <li
105 className={classnames({
106 'tab-item': true,
107 'is-active': service.isActive,
108 'has-custom-icon': service.hasCustomIcon,
109 })}
110 onClick={clickHandler}
111 onContextMenu={() => menu.popup(remote.getCurrentWindow())}
112 data-tip={`${service.name} ${shortcutIndex <= 9 ? `(${ctrlKey}+${shortcutIndex})` : ''}`}
113 >
114 <img
115 src={service.icon}
116 className="tab-item__icon"
117 alt=""
118 />
119 {service.unreadDirectMessageCount > 0 && (
120 <span className="tab-item__message-count">
121 {service.unreadDirectMessageCount}
122 </span>
123 )}
124 {service.unreadIndirectMessageCount > 0
125 && service.unreadDirectMessageCount === 0
126 && service.isIndirectMessageBadgeEnabled && (
127 <span className="tab-item__message-count is-indirect">
128
129 </span>
130 )}
131 </li>
132 );
133 }
134}
135
136export default SortableElement(TabItem);
diff --git a/src/components/services/tabs/Tabbar.js b/src/components/services/tabs/Tabbar.js
new file mode 100644
index 000000000..fdb2c0a59
--- /dev/null
+++ b/src/components/services/tabs/Tabbar.js
@@ -0,0 +1,77 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4
5import TabBarSortableList from './TabBarSortableList';
6
7@observer
8export default class TabBar extends Component {
9 static propTypes = {
10 services: MobxPropTypes.arrayOrObservableArray.isRequired,
11 setActive: PropTypes.func.isRequired,
12 openSettings: PropTypes.func.isRequired,
13 enableToolTip: PropTypes.func.isRequired,
14 disableToolTip: PropTypes.func.isRequired,
15 reorder: PropTypes.func.isRequired,
16 reload: PropTypes.func.isRequired,
17 toggleNotifications: PropTypes.func.isRequired,
18 deleteService: PropTypes.func.isRequired,
19 updateService: PropTypes.func.isRequired,
20 }
21
22 onSortEnd = ({ oldIndex, newIndex }) => {
23 const {
24 enableToolTip,
25 reorder,
26 } = this.props;
27
28 enableToolTip();
29 reorder({ oldIndex, newIndex });
30 };
31
32 disableService = ({ serviceId }) => {
33 const { updateService } = this.props;
34
35 if (serviceId) {
36 updateService({
37 serviceId,
38 serviceData: {
39 isEnabled: false,
40 },
41 redirect: false,
42 });
43 }
44 }
45
46 render() {
47 const {
48 services,
49 setActive,
50 openSettings,
51 disableToolTip,
52 reload,
53 toggleNotifications,
54 deleteService,
55 } = this.props;
56
57 return (
58 <div>
59 <TabBarSortableList
60 services={services}
61 setActive={setActive}
62 onSortEnd={this.onSortEnd}
63 onSortStart={disableToolTip}
64 reload={reload}
65 toggleNotifications={toggleNotifications}
66 deleteService={deleteService}
67 disableService={this.disableService}
68 openSettings={openSettings}
69 distance={20}
70 axis="y"
71 lockAxis="y"
72 helperClass="is-reordering"
73 />
74 </div>
75 );
76 }
77}