sequenceserver 3.1.2 → 3.2.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.
- checksums.yaml +4 -4
- data/lib/sequenceserver/api_errors.rb +18 -0
- data/lib/sequenceserver/blast/hit.rb +1 -2
- data/lib/sequenceserver/blast/job.rb +15 -5
- data/lib/sequenceserver/blast/report.rb +5 -1
- data/lib/sequenceserver/blast/tasks.rb +1 -1
- data/lib/sequenceserver/database.rb +5 -1
- data/lib/sequenceserver/job.rb +26 -10
- data/lib/sequenceserver/makeblastdb.rb +2 -1
- data/lib/sequenceserver/routes.rb +38 -13
- data/lib/sequenceserver/version.rb +1 -1
- data/lib/sequenceserver/zip_file_generator.rb +1 -1
- data/lib/sequenceserver.rb +1 -1
- data/public/404.html +8 -16
- data/public/css/app.css +794 -111
- data/public/css/app.min.css +1 -1
- data/public/css/sequenceserver.min.css +1 -8
- data/public/js/alignment_exporter.js +16 -28
- data/public/js/circos.js +103 -96
- data/public/{packages/circosJS@1.7.0.js → js/circosjs.js} +156 -778
- data/public/js/cloud_share_modal.js +95 -98
- data/public/js/collapse_preferences.js +13 -15
- data/public/js/databases.js +4 -3
- data/public/js/databases_tree.js +51 -30
- data/public/js/error_modal.js +35 -22
- data/public/js/exporter.js +2 -0
- data/public/js/form.js +55 -17
- data/public/js/grapher.js +94 -92
- data/public/js/hit.js +24 -22
- data/public/js/hits.js +283 -0
- data/public/js/hits_overview.js +8 -27
- data/public/js/hsp.js +140 -144
- data/public/js/jquery_world.js +25 -16
- data/public/js/kablammo.js +14 -10
- data/public/js/length_distribution.js +23 -37
- data/public/js/mailto.js +1 -3
- data/public/js/null_plugins/report_plugins.js +4 -5
- data/public/js/options.js +19 -25
- data/public/js/query.js +36 -35
- data/public/js/report/alignment_results.js +18 -0
- data/public/js/report/graphical_overview.js +38 -0
- data/public/js/report/report_header.js +28 -0
- data/public/js/report/run_summary.js +43 -0
- data/public/js/report.js +124 -348
- data/public/js/report_root.js +10 -8
- data/public/js/search.js +31 -11
- data/public/js/search_button.js +85 -78
- data/public/js/sequence.js +169 -180
- data/public/js/sequence_modal.js +58 -65
- data/public/js/share_url.js +5 -5
- data/public/js/sidebar.js +152 -89
- data/public/js/svgExporter.js +8 -3
- data/public/js/tests/alignment_exporter.spec.js +38 -0
- data/public/js/tests/cloud_share_modal.spec.js +75 -0
- data/public/js/tests/form.spec.js +1 -1
- data/public/js/tests/report.spec.js +37 -15
- data/public/js/tests/visualisation_helpers.spec.js +28 -0
- data/public/js/visualisation_helpers.js +24 -27
- data/public/packages/jquery-ui@1.13.3.js +19070 -0
- data/public/sequenceserver-report.min.js +3 -2481
- data/public/sequenceserver-report.min.js.LICENSE.txt +213 -0
- data/public/sequenceserver-report.min.js.map +1 -0
- data/public/sequenceserver-search.min.js +3 -2382
- data/public/sequenceserver-search.min.js.LICENSE.txt +262 -0
- data/public/sequenceserver-search.min.js.map +1 -0
- data/views/error.erb +1 -1
- data/views/layout.erb +122 -133
- data/views/report.erb +3 -2
- data/views/search.erb +10 -14
- metadata +21 -28
- data/public/config.js +0 -147
- data/public/css/fonts.css +0 -31
- data/public/css/grapher.css +0 -670
- data/public/css/sequenceserver.css +0 -527
- data/public/packages/jquery-ui@1.11.4.js +0 -16624
- data/public/vendor/github/mbostock/d3@3.5.6/d3.js +0 -9506
- data/public/vendor/github/mbostock/d3@3.5.6.js +0 -1
- data/views/search_layout.erb +0 -153
|
@@ -15,6 +15,7 @@ export default class CloudShareModal extends React.Component {
|
|
|
15
15
|
email: '',
|
|
16
16
|
agreeToTos: false,
|
|
17
17
|
shareableurl: '',
|
|
18
|
+
isModalVisible: false,
|
|
18
19
|
};
|
|
19
20
|
this.modalRef = createRef();
|
|
20
21
|
}
|
|
@@ -28,13 +29,11 @@ export default class CloudShareModal extends React.Component {
|
|
|
28
29
|
this.setState({ [name]: inputValue });
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
handleSubmit = (e) => {
|
|
32
|
+
handleSubmit = async (e) => {
|
|
32
33
|
e.preventDefault();
|
|
33
34
|
|
|
34
35
|
const { email } = this.state;
|
|
35
|
-
const
|
|
36
|
-
const match = window.location.pathname.match(regex);
|
|
37
|
-
const jobId = match[1];
|
|
36
|
+
const jobId = this.getJobIdFromPath();
|
|
38
37
|
|
|
39
38
|
this.setState({ formState: 'loading' });
|
|
40
39
|
|
|
@@ -43,40 +42,55 @@ export default class CloudShareModal extends React.Component {
|
|
|
43
42
|
sender_email: email
|
|
44
43
|
};
|
|
45
44
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
.then(response => response.json())
|
|
54
|
-
.then(data => {
|
|
55
|
-
if (data.shareable_url) {
|
|
56
|
-
// Successful response
|
|
57
|
-
this.setState({ formState: 'results', shareableurl: data.shareable_url });
|
|
58
|
-
} else if (data.errors) {
|
|
59
|
-
// Error response with specific error messages
|
|
60
|
-
const errorMessages = data.errors;
|
|
61
|
-
this.setState({ formState: 'error', errorMessages });
|
|
62
|
-
} else {
|
|
63
|
-
// Generic error message
|
|
64
|
-
throw new Error('Unknown error submitting form');
|
|
65
|
-
}
|
|
66
|
-
})
|
|
67
|
-
.catch(error => {
|
|
68
|
-
this.setState({
|
|
69
|
-
formState: 'error',
|
|
70
|
-
errorMessages: [error.message]
|
|
71
|
-
});
|
|
45
|
+
try {
|
|
46
|
+
const response = await fetch('/cloud_share', {
|
|
47
|
+
method: 'POST',
|
|
48
|
+
headers: {
|
|
49
|
+
'Content-Type': 'application/json'
|
|
50
|
+
},
|
|
51
|
+
body: JSON.stringify(requestData)
|
|
72
52
|
});
|
|
53
|
+
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
throw new Error('Network response was not ok');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const data = await response.json();
|
|
59
|
+
|
|
60
|
+
if (data.shareable_url) {
|
|
61
|
+
this.setState({ formState: 'results', shareableurl: data.shareable_url });
|
|
62
|
+
} else if (data.errors) {
|
|
63
|
+
this.setState({ formState: 'error', errorMessages: data.errors });
|
|
64
|
+
} else {
|
|
65
|
+
throw new Error('Unknown error submitting form');
|
|
66
|
+
}
|
|
67
|
+
} catch (error) {
|
|
68
|
+
this.setState({
|
|
69
|
+
formState: 'error',
|
|
70
|
+
errorMessages: [error.message]
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
getJobIdFromPath = () => {
|
|
76
|
+
const regex = /\/([^/]+)(?:\/|#|\?|$)/;
|
|
77
|
+
const match = window.location.pathname.match(regex);
|
|
78
|
+
return match ? match[1] : match;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
show = () => {
|
|
82
|
+
this.modalRef.current?.showModal();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
hide = () => {
|
|
86
|
+
this.modalRef.current?.close();
|
|
73
87
|
}
|
|
74
88
|
|
|
75
89
|
renderLoading() {
|
|
76
90
|
return (
|
|
77
|
-
<div className="text-center">
|
|
91
|
+
<div className="text-center pt-3">
|
|
78
92
|
<i className="fa fa-spinner fa-3x fa-spin"></i>
|
|
79
|
-
<p>Uploading the job to SequenceServer Cloud, please wait...</p>
|
|
93
|
+
<p className="my-3">Uploading the job to SequenceServer Cloud, please wait...</p>
|
|
80
94
|
</div>
|
|
81
95
|
);
|
|
82
96
|
}
|
|
@@ -93,18 +107,13 @@ export default class CloudShareModal extends React.Component {
|
|
|
93
107
|
return (
|
|
94
108
|
<>
|
|
95
109
|
{
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
</div>
|
|
104
|
-
</div>
|
|
105
|
-
);
|
|
106
|
-
}, this)
|
|
107
|
-
)
|
|
110
|
+
errorMessages.map((errorMessage, index) => (
|
|
111
|
+
<div key={`fastan-${index}`} className="fastan">
|
|
112
|
+
<div className="pt-0 px-0 pb-px">
|
|
113
|
+
<div className="text-danger text-lg border border-danger p-2 my-2">{errorMessage}</div>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
))
|
|
108
117
|
}
|
|
109
118
|
{this.renderForm()}
|
|
110
119
|
</>
|
|
@@ -117,46 +126,52 @@ export default class CloudShareModal extends React.Component {
|
|
|
117
126
|
|
|
118
127
|
return(
|
|
119
128
|
<form onSubmit={this.handleSubmit}>
|
|
120
|
-
<div className="
|
|
121
|
-
<label htmlFor="emailInput">Your Email Address</label>
|
|
129
|
+
<div className="px-6 py-4 text-sm">
|
|
130
|
+
<label htmlFor="emailInput" className="text-seqblue hover:text-orange cursor-pointer mb-0">Your Email Address</label>
|
|
122
131
|
<input
|
|
123
132
|
type="email"
|
|
124
133
|
id="emailInput"
|
|
125
|
-
className="
|
|
134
|
+
className="block w-full rounded-md border-0 py-1.5 px-3 mb-3 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-seqblue sm:text-sm sm:leading-6"
|
|
126
135
|
placeholder="person@example.com"
|
|
127
136
|
name="email"
|
|
128
137
|
value={email}
|
|
129
138
|
required="required"
|
|
130
139
|
onChange={this.handleChange}
|
|
131
140
|
/>
|
|
141
|
+
<p className="mb-3">
|
|
142
|
+
By submitting this form you agree to upload this SequenceServer result set to <a href="https://sequenceserver.com/cloud/" target="_bank" className="text-seqblue hover:text-seqorange">SenquenceServer Cloud</a>
|
|
143
|
+
, where it will become available on the internet to everyone with the link. You also agree that your email address will be stored on SequenceServer databases as proof of authentication for support and similar purposes.
|
|
144
|
+
</p>
|
|
145
|
+
<div className="form-check">
|
|
146
|
+
<input
|
|
147
|
+
type="checkbox"
|
|
148
|
+
id="tosCheckbox"
|
|
149
|
+
className="form-check-input"
|
|
150
|
+
name="agreeToTos"
|
|
151
|
+
checked={agreeToTos}
|
|
152
|
+
onChange={this.handleChange}
|
|
153
|
+
/>
|
|
154
|
+
<label htmlFor="tosCheckbox" className="pl-2">
|
|
155
|
+
I agree to the <b><a href="https://sequenceserver.com/cloud/terms_and_conditions" target="_blank" className="text-seqblue hover:text-seqorange">Terms and Conditions of Service</a></b>
|
|
156
|
+
</label>
|
|
157
|
+
</div>
|
|
132
158
|
</div>
|
|
133
|
-
<
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
className="form-check-input"
|
|
143
|
-
name="agreeToTos"
|
|
144
|
-
checked={agreeToTos}
|
|
145
|
-
onChange={this.handleChange}
|
|
146
|
-
/>
|
|
147
|
-
<label htmlFor="tosCheckbox" className="form-check-label">
|
|
148
|
-
I agree to the <b><a href="https://sequenceserver.com/cloud/terms_and_conditions" target="_blank">Terms and Conditions of Service</a></b>
|
|
149
|
-
</label>
|
|
159
|
+
<div className="bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6">
|
|
160
|
+
<button
|
|
161
|
+
type="submit"
|
|
162
|
+
style={{ backgroundColor: isSubmitDisabled ? '#C74F13' : '#1B557A' }}
|
|
163
|
+
className='border-seqblue py-2 px-3 rounded-md text-white'
|
|
164
|
+
disabled={isSubmitDisabled}
|
|
165
|
+
>
|
|
166
|
+
Submit
|
|
167
|
+
</button>
|
|
150
168
|
</div>
|
|
151
|
-
<button type="submit" className="btn btn-primary" disabled={isSubmitDisabled}>
|
|
152
|
-
Submit
|
|
153
|
-
</button>
|
|
154
169
|
</form>
|
|
155
170
|
)
|
|
156
171
|
}
|
|
157
172
|
|
|
158
173
|
render() {
|
|
159
|
-
const { formState } = this.state;
|
|
174
|
+
const { formState, isModalVisible } = this.state;
|
|
160
175
|
|
|
161
176
|
let content;
|
|
162
177
|
switch (formState) {
|
|
@@ -176,41 +191,23 @@ export default class CloudShareModal extends React.Component {
|
|
|
176
191
|
}
|
|
177
192
|
|
|
178
193
|
return (
|
|
179
|
-
<div className="
|
|
180
|
-
<
|
|
181
|
-
<div className="
|
|
182
|
-
<div className="
|
|
183
|
-
<h3
|
|
194
|
+
<div className="relative">
|
|
195
|
+
<dialog ref={this.modalRef} className="fixed p-4 w-full max-w-2xl bg-transparent focus:outline-none">
|
|
196
|
+
<div className="relative flex flex-col rounded-lg bg-white shadow">
|
|
197
|
+
<div className="flex items-start justify-between rounded-t border-b p-5">
|
|
198
|
+
<h3 className="text-xl font-medium text-gray-900">
|
|
199
|
+
Share to SequenceServer Cloud
|
|
200
|
+
</h3>
|
|
201
|
+
<button className="ml-auto inline-flex items-center rounded-lg bg-transparent p-1.5 text-sm text-gray-400 hover:bg-gray-200" onClick={this.hide}>
|
|
202
|
+
<i className="fa-solid fa-xmark hover:text-black"></i>
|
|
203
|
+
</button>
|
|
184
204
|
</div>
|
|
185
|
-
<div className="
|
|
205
|
+
<div className="dialog-content">
|
|
186
206
|
{content}
|
|
187
207
|
</div>
|
|
188
208
|
</div>
|
|
189
|
-
</
|
|
209
|
+
</dialog>
|
|
190
210
|
</div>
|
|
191
211
|
);
|
|
192
212
|
}
|
|
193
|
-
|
|
194
|
-
/*
|
|
195
|
-
* Returns jQuery reference to the main modal container.
|
|
196
|
-
*/
|
|
197
|
-
modal() {
|
|
198
|
-
return $(this.modalRef.current);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Shows share dialogue.
|
|
203
|
-
*/
|
|
204
|
-
show() {
|
|
205
|
-
this.setState({ requestCompleted: false }, () => {
|
|
206
|
-
this.modal().modal("show");
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Hide share dialogue.
|
|
212
|
-
*/
|
|
213
|
-
hide() {
|
|
214
|
-
this.modal().modal("hide");
|
|
215
|
-
}
|
|
216
213
|
}
|
|
@@ -1,26 +1,24 @@
|
|
|
1
1
|
export default class CollapsePreferences {
|
|
2
|
-
constructor(component) {
|
|
2
|
+
constructor(component, defaultCollapsed = false) {
|
|
3
3
|
this.component = component;
|
|
4
|
-
this.
|
|
4
|
+
this.defaultCollapsed = defaultCollapsed;
|
|
5
|
+
this.storageKey = 'sequenceServerCollapseStates';
|
|
6
|
+
this.collapseStates = JSON.parse(localStorage.getItem(this.storageKey)) || {};
|
|
5
7
|
}
|
|
6
8
|
|
|
7
9
|
toggleCollapse() {
|
|
8
10
|
let currentlyCollapsed = this.component.state.collapsed;
|
|
9
|
-
|
|
10
11
|
this.component.setState({ collapsed: !currentlyCollapsed });
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if (currentlyCollapsed) {
|
|
15
|
-
localStorage.setItem('collapsePreferences', JSON.stringify(collapsePreferences.filter((name) => name !== this.component.name)));
|
|
16
|
-
} else {
|
|
17
|
-
let uniqueCollapsePreferences = [... new Set(collapsePreferences.concat([this.component.name]))];
|
|
18
|
-
localStorage.setItem('collapsePreferences', JSON.stringify(uniqueCollapsePreferences));
|
|
19
|
-
}
|
|
13
|
+
this.collapseStates[this.component.name] = !currentlyCollapsed;
|
|
14
|
+
localStorage.setItem(this.storageKey, JSON.stringify(this.collapseStates));
|
|
20
15
|
}
|
|
21
16
|
|
|
22
17
|
preferenceStoredAsCollapsed() {
|
|
23
|
-
|
|
18
|
+
if (this.component.name in this.collapseStates) {
|
|
19
|
+
return this.collapseStates[this.component.name];
|
|
20
|
+
}
|
|
21
|
+
return this.defaultCollapsed;
|
|
24
22
|
}
|
|
25
23
|
|
|
26
24
|
renderCollapseIcon() {
|
|
@@ -28,10 +26,10 @@ export default class CollapsePreferences {
|
|
|
28
26
|
}
|
|
29
27
|
|
|
30
28
|
minusIcon() {
|
|
31
|
-
return <i className="fa fa-
|
|
29
|
+
return <i className="print:!hidden fa-regular fa-square-minus"></i>;
|
|
32
30
|
}
|
|
33
31
|
|
|
34
32
|
plusIcon() {
|
|
35
|
-
return <i className="fa fa-
|
|
33
|
+
return <i className="print:!hidden fa-regular fa-square-plus"></i>;
|
|
36
34
|
}
|
|
37
|
-
}
|
|
35
|
+
}
|
data/public/js/databases.js
CHANGED
|
@@ -76,7 +76,7 @@ export class Databases extends Component {
|
|
|
76
76
|
|
|
77
77
|
// Toggle button.
|
|
78
78
|
var toggleState = '[Select all]';
|
|
79
|
-
var toggleClass = 'px-2 text-
|
|
79
|
+
var toggleClass = 'px-2 text-base md:text-lg';
|
|
80
80
|
var toggleShown = this.databases(category).length > 1;
|
|
81
81
|
var toggleDisabled = this.state.type && this.state.type !== category;
|
|
82
82
|
if (toggleShown && toggleDisabled) {
|
|
@@ -94,7 +94,7 @@ export class Databases extends Component {
|
|
|
94
94
|
<div className={columnClass} key={'DB_' + category}>
|
|
95
95
|
<div>
|
|
96
96
|
<div className="border-b border-seqorange mb-2">
|
|
97
|
-
<h4
|
|
97
|
+
<h4 className="font-medium inline text-base md:text-lg">
|
|
98
98
|
{panelTitle}
|
|
99
99
|
</h4>
|
|
100
100
|
<button
|
|
@@ -113,7 +113,7 @@ export class Databases extends Component {
|
|
|
113
113
|
this.databases(category),
|
|
114
114
|
_.bind(function (database, index) {
|
|
115
115
|
return (
|
|
116
|
-
<li key={'DB_' + category + index}>
|
|
116
|
+
<li key={'DB_' + category + index} className="text-base md:text-lg leading-tight md:leading-tight">
|
|
117
117
|
{this.renderDatabase(database)}
|
|
118
118
|
</li>
|
|
119
119
|
);
|
|
@@ -152,6 +152,7 @@ export class Databases extends Component {
|
|
|
152
152
|
data-type={database.type}
|
|
153
153
|
disabled={isDisabled}
|
|
154
154
|
checked={isChecked}
|
|
155
|
+
className="checkbox-database"
|
|
155
156
|
onChange={_.bind(function () {
|
|
156
157
|
this.handleDatabaseSelectionClick(database);
|
|
157
158
|
}, this)}
|
data/public/js/databases_tree.js
CHANGED
|
@@ -16,29 +16,37 @@ export default class extends Databases {
|
|
|
16
16
|
var tree_id = '#' + category + '_database_tree';
|
|
17
17
|
// hack that is needed to sync the selected tree db with the hidden main db
|
|
18
18
|
window.jstree_node_change_timeout = null;
|
|
19
|
+
window.jstree_rapid_timeout = null
|
|
20
|
+
const _this = this;
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
if (window.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
$(tree_id).on('select_node.jstree deselect_node.jstree', function (_, _data) {
|
|
23
|
+
if (window.jstree_node_change_timeout) clearTimeout(window.jstree_node_change_timeout);
|
|
24
|
+
if (window.jstree_rapid_timeout) clearTimeout(window.jstree_rapid_timeout);
|
|
25
|
+
|
|
26
|
+
// Set a timeout to handle jsTree node changes with some delay to avoid rapid re-triggering
|
|
27
|
+
window.jstree_rapid_timeout = setTimeout(function () {
|
|
28
|
+
// Set another timeout to handle the retrieval and processing of selected nodes
|
|
29
|
+
window.jstree_node_change_timeout = setTimeout(function () {
|
|
30
|
+
// Filter out the current category to get a list of other categories
|
|
31
|
+
const otherCategories = _this.categories().filter(item => item !== category);
|
|
32
|
+
|
|
33
|
+
// Uncheck all nodes in the trees of the other categories
|
|
34
|
+
otherCategories.forEach((value) => $(`#${value}_database_tree`).jstree('uncheck_all'));
|
|
35
|
+
// Get all selected nodes from the current tree.
|
|
36
|
+
// Note: This will also include folders. To filter only database nodes, ensure the ID length is 32.
|
|
37
|
+
// These IDs correspond to specific elements in the hidden main form.
|
|
38
|
+
const selected = $(tree_id).jstree('get_selected').filter(selected => selected.length === 32);
|
|
39
|
+
|
|
40
|
+
// Find database entries in the current category whose IDs match the selected nodes
|
|
41
|
+
const foundDatabases = _this.databases(category).filter(db => selected.includes(db.id));
|
|
26
42
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
// get all selected tree dbs. Also includes folders. Therefore, the id must have a length of 32
|
|
32
|
-
// this id is used to find the corresponding element from the hidden main form
|
|
33
|
-
var selected = $(tree_id).jstree('get_selected').filter(selected => selected.length == 32);
|
|
34
|
-
$.each(selected, function (index, value) {
|
|
35
|
-
// select hidden element to trigger original sequenceserver behavior, like blast algorithm, ...
|
|
36
|
-
$('input[value="' + value + '"]').click();
|
|
37
|
-
});
|
|
38
|
-
}, 100);
|
|
39
|
-
}, 100);
|
|
43
|
+
// Update the selected databases in the view model with the found databases
|
|
44
|
+
_this.selectDatabases(foundDatabases);
|
|
45
|
+
}, 100); // Delay to ensure jsTree updates are processed
|
|
46
|
+
}, 100); // Delay to prevent rapid changes causing multiple triggers
|
|
40
47
|
});
|
|
41
48
|
|
|
49
|
+
|
|
42
50
|
$(tree_id).jstree({
|
|
43
51
|
'core': {
|
|
44
52
|
'data': this.props.tree[category]
|
|
@@ -50,6 +58,10 @@ export default class extends Databases {
|
|
|
50
58
|
});
|
|
51
59
|
}
|
|
52
60
|
|
|
61
|
+
selectDatabases(databases) {
|
|
62
|
+
this.setState({ currentlySelectedDatabases: databases });
|
|
63
|
+
}
|
|
64
|
+
|
|
53
65
|
handleTreeSearch(category, tree_id, search_id) {
|
|
54
66
|
var search_for = $('#' + search_id).val();
|
|
55
67
|
$('#' + tree_id).jstree(true).search(search_for);
|
|
@@ -59,15 +71,18 @@ export default class extends Databases {
|
|
|
59
71
|
// Panel name and column width.
|
|
60
72
|
var panelTitle = category[0].toUpperCase() +
|
|
61
73
|
category.substring(1).toLowerCase() + ' databases';
|
|
62
|
-
var columnClass = this.categories().length === 1 ? 'col-
|
|
63
|
-
'col-md-6';
|
|
74
|
+
var columnClass = this.categories().length === 1 ? 'col-span-2' : '';
|
|
64
75
|
|
|
65
76
|
// Toggle button.
|
|
66
77
|
var toggleState = '[Select all]';
|
|
67
|
-
var toggleClass = '
|
|
78
|
+
var toggleClass = 'px-2 text-base md:text-lg hidden';
|
|
68
79
|
var toggleShown = this.databases(category).length > 1;
|
|
69
80
|
var toggleDisabled = this.state.type && this.state.type !== category;
|
|
70
|
-
if (toggleShown && toggleDisabled)
|
|
81
|
+
if (toggleShown && toggleDisabled) {
|
|
82
|
+
toggleClass += ' text-gray-400';
|
|
83
|
+
} else {
|
|
84
|
+
toggleClass += ' text-seqblue';
|
|
85
|
+
}
|
|
71
86
|
if (!toggleShown) toggleClass += ' hidden';
|
|
72
87
|
if (this.nselected() === this.databases(category).length) {
|
|
73
88
|
toggleState = '[Deselect all]';
|
|
@@ -76,22 +91,28 @@ export default class extends Databases {
|
|
|
76
91
|
// JSX.
|
|
77
92
|
return (
|
|
78
93
|
<div className={columnClass} key={'DB_' + category}>
|
|
79
|
-
<div
|
|
80
|
-
<div className=
|
|
81
|
-
<h4
|
|
94
|
+
<div>
|
|
95
|
+
<div className="border-b border-seqorange mb-2" id="database_list">
|
|
96
|
+
<h4 className="font-medium text-base md:text-lg">{panelTitle}</h4>
|
|
82
97
|
{
|
|
83
98
|
this.renderDatabaseSearch(category)
|
|
84
99
|
}
|
|
85
|
-
<button
|
|
86
|
-
|
|
100
|
+
<button
|
|
101
|
+
type="button"
|
|
102
|
+
className={toggleClass}
|
|
103
|
+
disabled={toggleDisabled}
|
|
104
|
+
onClick={function () {
|
|
105
|
+
this.handleToggle(toggleState, category);
|
|
106
|
+
}.bind(this)}
|
|
107
|
+
>
|
|
87
108
|
{toggleState}
|
|
88
109
|
</button>
|
|
89
110
|
</div>
|
|
90
|
-
<ul className={'
|
|
111
|
+
<ul className={'databases hidden text-lg ' + category}>
|
|
91
112
|
{
|
|
92
113
|
_.map(this.databases(category), _.bind(function (database, index) {
|
|
93
114
|
return (
|
|
94
|
-
<li
|
|
115
|
+
<li key={'DB_' + category + index}>
|
|
95
116
|
{this.renderDatabase(database)}
|
|
96
117
|
</li>
|
|
97
118
|
);
|
data/public/js/error_modal.js
CHANGED
|
@@ -2,7 +2,7 @@ import React, { createRef } from 'react';
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Takes errorData object with title, message, and more_info keys as props. The
|
|
5
|
-
* component displays a
|
|
5
|
+
* component displays a tailwind modal when mounted. errorData.title is used
|
|
6
6
|
* to set modal title. errorData.message is inserted as HTML text in modal
|
|
7
7
|
* body. And errorData.more_info is shown using a pre tag in modal body.
|
|
8
8
|
*
|
|
@@ -13,46 +13,59 @@ export default class ErrorModal extends React.Component {
|
|
|
13
13
|
|
|
14
14
|
constructor(props) {
|
|
15
15
|
super(props);
|
|
16
|
-
this.state = {
|
|
17
|
-
|
|
16
|
+
this.state = {
|
|
17
|
+
errorData: {},
|
|
18
|
+
isModalVisible: false
|
|
19
|
+
};
|
|
20
|
+
this.modalRef = createRef();
|
|
18
21
|
}
|
|
19
22
|
|
|
20
|
-
// HTML for Bootstrap modal.
|
|
21
23
|
render() {
|
|
24
|
+
const { isModalVisible, errorData } = this.state;
|
|
25
|
+
|
|
22
26
|
return (
|
|
23
|
-
<div
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
<div className="relative">
|
|
28
|
+
<dialog ref={this.modalRef} className="fixed p-4 w-full max-w-2xl bg-transparent focus:outline-none">
|
|
29
|
+
<div className="relative flex flex-col rounded-lg bg-white shadow">
|
|
30
|
+
<div className="flex items-start justify-between rounded-t border-b p-5">
|
|
31
|
+
<h3 className="text-xl font-medium text-gray-900">
|
|
32
|
+
{errorData.title}
|
|
33
|
+
</h3>
|
|
34
|
+
<button className="ml-auto inline-flex items-center rounded-lg bg-transparent p-1.5 text-gray-400 hover:bg-gray-200" onClick={this.hide}>
|
|
35
|
+
<i className="fa-solid fa-xmark hover:text-black"></i>
|
|
36
|
+
</button>
|
|
29
37
|
</div>
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
<p dangerouslySetInnerHTML={{ __html: this.state.errorData.message}}></p>
|
|
33
|
-
|
|
38
|
+
<div className="pt-2 px-6 pb-6 mt-2 text-sm">
|
|
39
|
+
<p dangerouslySetInnerHTML={{ __html: errorData.message }} className="mb-4"></p>
|
|
34
40
|
{
|
|
35
|
-
|
|
36
|
-
<pre className="
|
|
37
|
-
{this.state.errorData.more_info}
|
|
38
|
-
</pre>
|
|
41
|
+
errorData.more_info &&
|
|
42
|
+
<pre className="p-2 bg-slate-200 overflow-auto max-h-56">{errorData.more_info}</pre>
|
|
39
43
|
}
|
|
40
44
|
</div>
|
|
41
45
|
</div>
|
|
42
|
-
</
|
|
46
|
+
</dialog>
|
|
43
47
|
</div>
|
|
44
48
|
);
|
|
45
49
|
}
|
|
50
|
+
|
|
46
51
|
/**
|
|
47
52
|
* Shows error viewer.
|
|
48
53
|
*/
|
|
49
|
-
show (errorData, beforeShow) {
|
|
50
|
-
this.setState({errorData: errorData});
|
|
54
|
+
show = (errorData, beforeShow) => {
|
|
55
|
+
this.setState({ errorData: errorData });
|
|
56
|
+
|
|
51
57
|
// Caller can specify an amount of time to wait for before showing the
|
|
52
58
|
// modal. This is helpful if the caller wants to finish some work
|
|
53
59
|
// before showing error modal.
|
|
54
60
|
setTimeout(() => {
|
|
55
|
-
|
|
61
|
+
this.modalRef.current?.showModal();
|
|
56
62
|
}, beforeShow || 0);
|
|
57
63
|
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Hide dialogue.
|
|
67
|
+
*/
|
|
68
|
+
hide = () => {
|
|
69
|
+
this.modalRef.current?.close();
|
|
70
|
+
}
|
|
58
71
|
}
|