-
Notifications
You must be signed in to change notification settings - Fork 9
/
datatip-view.js
167 lines (153 loc) · 4.27 KB
/
datatip-view.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
'use babel';
/** @jsx etch.dom */
const ReactDOMServer = require('react-dom/server');
const etch = require('etch');
const createDOMPurify = require('dompurify');
/**
* a reference to the DOMpurify function to make safe HTML strings
* @type {DOMPurify}
*/
const domPurify = createDOMPurify();
/**
* an etch component that can host already prepared HTML text
*/
class HtmlView {
/**
* creates the HTML view component and hands over the HTML to embedd
* @param {String} html the HTML string to embedd into the HTML view component
*/
constructor({ html }) {
this.rootElement = document.createElement('div');
this.rootElement.className = "datatip-marked-container";
this.rootElement.addEventListener("wheel", this.onMouseWheel, { passive: true });
if (html){
this.rootElement.innerHTML = domPurify.sanitize(html);
}
}
/**
* cleanup the HTML view component
*/
destroy() {
this.rootElement.removeEventListener("wheel", this.onMouseWheel);
}
/**
* returns the root element of the HTML view component
* @return {HTMLElement} the root element wrapping the HTML content
*/
get element() {
return this.rootElement;
}
/**
* handles the mouse wheel event to enable scrolling over long text
* @param {MouseWheelEvent} evt the mouse wheel event being triggered
*/
onMouseWheel(evt) {
evt.stopPropagation();
}
}
/**
* an etch component that hosts a code snippet incl. syntax highlighting
*/
class SnippetView {
/**
* creates a snippet view component handing in the snippet
* @param {String} snippet the code snippet to be embedded
*/
constructor({ snippet }) {
this.rootElement = document.createElement('div');
this.rootElement.className = "datatip-container";
if (snippet) {
this.rootElement.innerHTML = domPurify.sanitize(snippet);
}
}
/**
* returns the root element of the snippet view component
* @return {HTMLElement} the root element wrapping the HTML content
*/
get element() {
return this.rootElement;
}
}
/**
* an etch component that can host an externally given React component
*/
class ReactView {
/**
* creates a React view component handing over the React code to be rendered
* @param {String} component the React component to be rendered
*/
constructor({ component }) {
this.rootElement = document.createElement('span');
if (component) {
this.rootElement.innerHTML = domPurify.sanitize(ReactDOMServer.renderToStaticMarkup(component()));
}
}
/**
* returns the root element of the React view component
* @return {HTMLElement} the root element wrapping the HTML content
*/
get element() {
return this.rootElement;
}
}
/**
* an etch component for a data tip
*/
module.exports = class DataTipView {
/**
* creates a data tip view component
* @param {any} properties the properties of this data tip view
* @param {Array<JSX.Element>} children potential child nodes of this data tip view
*/
constructor(properties, children) {
this.properties = properties;
this.children = children || [];
this.updateChildren();
etch.initialize(this);
}
/**
* renders the data tip view component
* @return {JSX.Element} the data tip view element
*/
render() {
return (
<div className="datatip-element">
{this.children}
</div>
);
}
/**
* updates the internal state of the data tip view
*/
update(props, children) {
// perform custom update logic here...
// then call `etch.update`, which is async and returns a promise
this.properties = props;
this.children = children || [];
this.updateChildren();
return etch.update(this)
}
/**
* clean up the data tip view
* @return {Promise} a promise object to keep track of the asynchronous operation
*/
async destroy() {
await etch.destroy(this)
}
/**
* internal helper function to figure out the structure of the data tip view
* to be rendered
*/
updateChildren() {
const { component, snippet, html} = this.properties;
if (component) {
this.children.push(<ReactView component={ component } />);
}
if (snippet) {
this.children.push(<SnippetView snippet={ snippet } />);
}
if (html) {
this.children.push(<HtmlView html={ html } />);
}
}
}