@abi-software/map-utilities 1.4.3-beta.1 → 1.4.3-beta.3

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abi-software/map-utilities",
3
- "version": "1.4.3-beta.1",
3
+ "version": "1.4.3-beta.3",
4
4
  "files": [
5
5
  "dist/*",
6
6
  "src/*",
@@ -30,11 +30,6 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@abi-software/svg-sprite": "^1.0.1",
33
- "@citation-js/core": "^0.7.14",
34
- "@citation-js/plugin-bibtex": "^0.7.17",
35
- "@citation-js/plugin-csl": "^0.7.14",
36
- "@citation-js/plugin-doi": "^0.7.16",
37
- "@citation-js/plugin-pubmed": "^0.3.0",
38
33
  "@element-plus/icons-vue": "^2.3.1",
39
34
  "cytoscape": "^3.30.2",
40
35
  "element-plus": "2.8.4",
@@ -160,10 +160,6 @@ export default {
160
160
  type: Array,
161
161
  default: [],
162
162
  },
163
- connectivityFromMap: {
164
- type: Object,
165
- default: () => null,
166
- },
167
163
  },
168
164
  data: function () {
169
165
  return {
@@ -188,20 +184,26 @@ export default {
188
184
  connectivityGraphContainer: null,
189
185
  };
190
186
  },
191
- watch: {
192
- connectivityFromMap: function (oldVal, newVal) {
193
- if (oldVal != newVal) {
194
- this.showSpinner();
195
- this.start();
196
- }
197
- }
198
- },
199
187
  mounted() {
200
188
  this.showSpinner();
201
189
  this.updateTooltipContainer();
202
190
  this.refreshCache();
203
191
  this.loadCacheData();
204
- this.start();
192
+ this.run()
193
+ .then((res) => {
194
+ if (res?.success) {
195
+ this.showGraph(this.entry);
196
+ } else if (res?.error) {
197
+ this.loadingError = res.error;
198
+ } else {
199
+ this.loadingError = 'Loading error!';
200
+ }
201
+ this.hideSpinner();
202
+ })
203
+ .catch((error) => {
204
+ this.loadingError = 'Loading error!';
205
+ this.hideSpinner();
206
+ });
205
207
  },
206
208
  methods: {
207
209
  updateTooltipContainer: function () {
@@ -273,22 +275,6 @@ export default {
273
275
 
274
276
  sessionStorage.setItem('connectivity-graph-expiry', expiry);
275
277
  },
276
- start: function () {
277
- this.run()
278
- .then((res) => {
279
- if (res?.success) {
280
- this.showGraph(this.entry);
281
- } else if (res?.error) {
282
- this.loadingError = res.error;
283
- } else {
284
- this.loadingError = 'Loading error!';
285
- }
286
- })
287
- .catch((error) => {
288
- this.loadingError = 'Loading error!';
289
- this.hideSpinner();
290
- });
291
- },
292
278
  run: async function () {
293
279
  if (!this.schemaVersion) {
294
280
  this.schemaVersion = await this.getSchemaVersion();
@@ -319,24 +305,8 @@ export default {
319
305
  showGraph: async function (neuronPath) {
320
306
  const graphCanvas = this.$refs.graphCanvas;
321
307
 
322
- // Update label data
323
- if (this.connectivityFromMap) {
324
- this.cacheLabels(this.connectivityFromMap);
325
- await this.getCachedTermLabels();
326
- }
327
-
328
308
  this.connectivityGraph = new ConnectivityGraph(this.labelCache, graphCanvas);
329
- const connectivityInfo = this.knowledgeByPath.get(neuronPath);
330
-
331
- // Update connectivity
332
- if (this.connectivityFromMap) {
333
- connectivityInfo.axons = this.connectivityFromMap.axons;
334
- connectivityInfo.connectivity = this.connectivityFromMap.connectivity;
335
- connectivityInfo.dendrites = this.connectivityFromMap.dendrites;
336
- connectivityInfo.somas = this.connectivityFromMap.somas;
337
- }
338
-
339
- await this.connectivityGraph.addConnectivity(connectivityInfo);
309
+ await this.connectivityGraph.addConnectivity(this.knowledgeByPath.get(neuronPath));
340
310
 
341
311
  this.connectivityGraph.showConnectivity(graphCanvas);
342
312
 
@@ -352,8 +322,6 @@ export default {
352
322
  */
353
323
  this.$emit('tap-node', data);
354
324
  });
355
-
356
- this.hideSpinner();
357
325
  },
358
326
  query: async function (sql, params) {
359
327
  const url = `${this.mapServer}knowledge/query/`;
@@ -41,7 +41,7 @@
41
41
  <strong class="sub-title">Previous submissions:</strong>
42
42
  </el-row>
43
43
  <div class="entry" v-for="(sub, index) in prevSubs" :key="index">
44
- <el-row class="dialog-text">
44
+ <el-row class="dialog-text" v-if="sub.creator">
45
45
  <strong>{{ formatTime(sub.created) }}</strong>
46
46
  {{ sub.creator.name }}
47
47
  </el-row>
@@ -65,7 +65,7 @@
65
65
  </div>
66
66
  </template>
67
67
  </template>
68
- <template v-if="authenticated">
68
+ <template v-if="authenticated || offlineAnnotationEnabled">
69
69
  <template v-if="isEditable">
70
70
  <el-row class="dialog-spacer"></el-row>
71
71
  <el-row v-if="!editing">
@@ -217,6 +217,9 @@ export default {
217
217
  updatedCopyContent: function () {
218
218
  return this.getUpdateCopyContent();
219
219
  },
220
+ offlineAnnotationEnabled: function () {
221
+ return this.annotationEntry["offline"];
222
+ },
220
223
  },
221
224
  methods: {
222
225
  processEvidences: function(sub) {
@@ -262,7 +265,15 @@ export default {
262
265
  return new Date(dateString).toLocaleDateString(undefined, options);
263
266
  },
264
267
  updatePrevSubmissions: function () {
265
- if (this.$annotator && this.authenticated) {
268
+ if (this.offlineAnnotationEnabled) {
269
+ const offlineAnnotations = JSON.parse(sessionStorage.getItem('offline-annotation')) || [];
270
+ this.prevSubs = offlineAnnotations.filter((offline) => {
271
+ return (
272
+ offline.resource === this.annotationEntry.resourceId &&
273
+ offline.item.id === this.annotationEntry.featureId
274
+ )
275
+ });
276
+ } else if (this.$annotator && this.authenticated) {
266
277
  if (
267
278
  this.annotationEntry["resourceId"] &&
268
279
  this.annotationEntry["featureId"]
@@ -342,7 +353,6 @@ export default {
342
353
  this.$annotator
343
354
  ?.addAnnotation(this.userApiKey, userAnnotation)
344
355
  .then(() => {
345
- this.$emit("annotation", userAnnotation);
346
356
  this.errorMessage = "";
347
357
  this.resetSubmission();
348
358
  this.updatePrevSubmissions();
@@ -351,6 +361,7 @@ export default {
351
361
  this.errorMessage =
352
362
  "There is a problem with the submission, please try again later";
353
363
  });
364
+ this.$emit("annotation", userAnnotation);
354
365
  }
355
366
  }
356
367
  },
@@ -396,9 +407,11 @@ export default {
396
407
  if (this.prevSubs.length) {
397
408
  let annotationContent = '<div><strong>Annotations:</strong></div>\n<br>';
398
409
  this.prevSubs.map((sub, index) => {
399
- annotationContent += `<div><strong>Created:</strong>${this.formatTime(sub.created)}</div>\n<br>`;
400
- annotationContent += `<div><strong>Creator:</strong>${sub.creator.name}</div>\n<br>`;
401
- annotationContent += `<div><strong>Email:</strong>${sub.creator.email}</div>\n<br>`;
410
+ if (sub.creator) {
411
+ annotationContent += `<div><strong>Created:</strong>${this.formatTime(sub.created)}</div>\n<br>`;
412
+ annotationContent += `<div><strong>Creator:</strong>${sub.creator.name}</div>\n<br>`;
413
+ annotationContent += `<div><strong>Email:</strong>${sub.creator.email}</div>\n<br>`;
414
+ }
402
415
  if (sub.body.evidence.length) {
403
416
  let evidenceContent = '';
404
417
  sub.body.evidence.forEach((evi, index) => {
@@ -433,10 +446,10 @@ export default {
433
446
  this.creator = userData;
434
447
  if (!userData.orcid) this.creator.orcid = "0000-0000-0000-0000";
435
448
  this.authenticated = true;
436
- this.updatePrevSubmissions();
437
449
  } else {
438
450
  this.errorMessage = "";
439
451
  }
452
+ this.updatePrevSubmissions();
440
453
  });
441
454
  },
442
455
  };
@@ -6,7 +6,7 @@
6
6
  <CopyToClipboard label="Copy list to clipboard" :content="referecesListContent" />
7
7
  </div>
8
8
  </div>
9
- <div class="citation-tabs" v-if="useDOIFormatter ? referencesWithDOI : pubMedReferences.length">
9
+ <div class="citation-tabs" v-if="referencesWithDOI">
10
10
  <el-button
11
11
  link
12
12
  v-for="citationOption of citationOptions"
@@ -102,7 +102,7 @@
102
102
 
103
103
  <script>
104
104
  import CopyToClipboard from '../CopyToClipboard/CopyToClipboard.vue';
105
- import { delay, getCitationById } from '../utilities';
105
+ import { delay } from '../utilities';
106
106
 
107
107
  const CROSSCITE_API_HOST = 'https://citation.doi.org';
108
108
  const CITATION_OPTIONS = [
@@ -128,18 +128,11 @@ const LOADING_DELAY = 600;
128
128
 
129
129
  export default {
130
130
  name: "ExternalResourceCard",
131
- components: {
132
- CopyToClipboard,
133
- },
134
131
  props: {
135
132
  resources: {
136
133
  type: Array,
137
134
  default: () => [],
138
135
  },
139
- useDOIFormatter: {
140
- type: Boolean,
141
- default: true,
142
- }
143
136
  },
144
137
  data: function () {
145
138
  return {
@@ -345,14 +338,7 @@ export default {
345
338
 
346
339
  if (type === 'doi' || doi) {
347
340
  const doiID = type === 'doi' ? id : doi;
348
- const fetchCitationFromAPI = this.useDOIFormatter ?
349
- this.getCitationTextByDOI(doiID) :
350
- getCitationById(doiID, {
351
- type: 'doi',
352
- format: citationType
353
- });
354
-
355
- fetchCitationFromAPI.then((text) => {
341
+ this.getCitationTextByDOI(doiID).then((text) => {
356
342
  const formattedText = this.replaceLinkInText(text);
357
343
  reference.citation[citationType] = formattedText;
358
344
  this.updateCopyContents();
@@ -363,61 +349,45 @@ export default {
363
349
  };
364
350
  });
365
351
  } else if (type === 'pmid') {
366
- if (this.useDOIFormatter) {
367
- this.getDOIFromPubMedID(id).then((data) => {
368
- if (data?.result) {
369
- const resultObj = data.result[id];
370
- const articleIDs = resultObj?.articleids || [];
371
- const doiObj = articleIDs.find((item) => item.idtype === 'doi');
372
- const doiID = doiObj?.value;
373
-
374
- if (doiID) {
375
- reference['doi'] = doiID;
376
- this.getCitationTextByDOI(doiID).then((text) => {
377
- const formattedText = this.replaceLinkInText(text);
378
- reference.citation[citationType] = formattedText;
379
- this.updateCopyContents();
380
- }).catch((error) => {
381
- reference.citation['error'] = {
382
- type: citationType,
383
- ref: 'doi',
384
- };
385
- });
386
- } else {
387
- // If there has no doi in PubMed
388
- const { title, pubdate, authors } = resultObj;
389
- const authorNames = authors ? authors.map((author) => author.name) : [];
390
- const formattedText = this.formatCopyReference({
391
- title: title || '',
392
- date: pubdate || '',
393
- authors: authorNames,
394
- url: `https://pubmed.ncbi.nlm.nih.gov/${id}`,
395
- });
352
+ this.getDOIFromPubMedID(id).then((data) => {
353
+ if (data?.result) {
354
+ const resultObj = data.result[id];
355
+ const articleIDs = resultObj?.articleids || [];
356
+ const doiObj = articleIDs.find((item) => item.idtype === 'doi');
357
+ const doiID = doiObj?.value;
358
+
359
+ if (doiID) {
360
+ reference['doi'] = doiID;
361
+ this.getCitationTextByDOI(doiID).then((text) => {
362
+ const formattedText = this.replaceLinkInText(text);
396
363
  reference.citation[citationType] = formattedText;
397
364
  this.updateCopyContents();
398
- }
365
+ }).catch((error) => {
366
+ reference.citation['error'] = {
367
+ type: citationType,
368
+ ref: 'doi',
369
+ };
370
+ });
371
+ } else {
372
+ // If there has no doi in PubMed
373
+ const { title, pubdate, authors } = resultObj;
374
+ const authorNames = authors ? authors.map((author) => author.name) : [];
375
+ const formattedText = this.formatCopyReference({
376
+ title: title || '',
377
+ date: pubdate || '',
378
+ authors: authorNames,
379
+ url: `https://pubmed.ncbi.nlm.nih.gov/${id}`,
380
+ });
381
+ reference.citation[citationType] = formattedText;
382
+ this.updateCopyContents();
399
383
  }
400
- }).catch((error) => {
401
- reference.citation['error'] = {
402
- type: citationType,
403
- ref: 'pubmed',
404
- };
405
- });
406
- } else {
407
- getCitationById(id, {
408
- type: 'pmid',
409
- format: citationType
410
- }).then((text) => {
411
- const formattedText = this.replaceLinkInText(text);
412
- reference.citation[citationType] = formattedText;
413
- this.updateCopyContents();
414
- }).catch((error) => {
415
- reference.citation['error'] = {
416
- type: citationType,
417
- ref: 'pubmed',
418
- };
419
- });
420
- }
384
+ }
385
+ }).catch((error) => {
386
+ reference.citation['error'] = {
387
+ type: citationType,
388
+ ref: 'pubmed',
389
+ };
390
+ });
421
391
  }
422
392
  }
423
393
  },
@@ -51,18 +51,118 @@
51
51
  </div>
52
52
  <transition name="slide-fade">
53
53
  <div v-show="showDetails" class="content-container scrollbar">
54
- <connectivity-list
55
- :key="tooltipEntry.featureId[0]"
56
- :entry="tooltipEntry"
57
- :origins="origins"
58
- :components="components"
59
- :destinations="destinations"
60
- :originsWithDatasets="originsWithDatasets"
61
- :componentsWithDatasets="componentsWithDatasets"
62
- :destinationsWithDatasets="destinationsWithDatasets"
63
- :availableAnatomyFacets="availableAnatomyFacets"
64
- @connectivity-action-click="onConnectivityActionClick"
65
- ></connectivity-list>
54
+ {{ tooltipEntry.paths }}
55
+ <div v-if="tooltipEntry.origins && tooltipEntry.origins.length > 0" class="block">
56
+ <div class="attribute-title-container">
57
+ <span class="attribute-title">Origin</span>
58
+ <el-popover
59
+ width="250"
60
+ trigger="hover"
61
+ :teleported="false"
62
+ popper-class="popover-origin-help"
63
+ >
64
+ <template #reference>
65
+ <el-icon class="info"><el-icon-warning /></el-icon>
66
+ </template>
67
+ <span style="word-break: keep-all">
68
+ <i>Origin</i> {{ originDescription }}
69
+ </span>
70
+ </el-popover>
71
+ </div>
72
+ <div
73
+ v-for="(origin, i) in tooltipEntry.origins"
74
+ class="attribute-content"
75
+ :origin-item-label="origin"
76
+ :key="origin"
77
+ >
78
+ {{ capitalise(origin) }}
79
+ <div v-if="i != tooltipEntry.origins.length - 1" class="separator"></div>
80
+ </div>
81
+ <el-button
82
+ v-show="
83
+ tooltipEntry.originsWithDatasets && tooltipEntry.originsWithDatasets.length > 0
84
+ "
85
+ class="button"
86
+ id="open-dendrites-button"
87
+ @click="openDendrites"
88
+ >
89
+ Explore origin data
90
+ </el-button>
91
+ </div>
92
+ <div
93
+ v-if="tooltipEntry.components && tooltipEntry.components.length > 0"
94
+ class="block"
95
+ >
96
+ <div class="attribute-title-container">
97
+ <div class="attribute-title">Components</div>
98
+ </div>
99
+ <div
100
+ v-for="(component, i) in tooltipEntry.components"
101
+ class="attribute-content"
102
+ :component-item-label="component"
103
+ :key="component"
104
+ >
105
+ {{ capitalise(component) }}
106
+ <div
107
+ v-if="i != tooltipEntry.components.length - 1"
108
+ class="separator"
109
+ ></div>
110
+ </div>
111
+ </div>
112
+ <div
113
+ v-if="tooltipEntry.destinations && tooltipEntry.destinations.length > 0"
114
+ class="block"
115
+ >
116
+ <div class="attribute-title-container">
117
+ <span class="attribute-title">Destination</span>
118
+ <el-popover
119
+ width="250"
120
+ trigger="hover"
121
+ :teleported="false"
122
+ popper-class="popover-origin-help"
123
+ >
124
+ <template #reference>
125
+ <el-icon class="info"><el-icon-warning /></el-icon>
126
+ </template>
127
+ <span style="word-break: keep-all">
128
+ <i>Destination</i> is where the axons terminate
129
+ </span>
130
+ </el-popover>
131
+ </div>
132
+ <div
133
+ v-for="(destination, i) in tooltipEntry.destinations"
134
+ class="attribute-content"
135
+ :destination-item-label="destination"
136
+ :key="destination"
137
+ >
138
+ {{ capitalise(destination) }}
139
+ <div
140
+ v-if="i != tooltipEntry.destinations.length - 1"
141
+ class="separator"
142
+ ></div>
143
+ </div>
144
+ <el-button
145
+ v-show="
146
+ tooltipEntry.destinationsWithDatasets &&
147
+ tooltipEntry.destinationsWithDatasets.length > 0
148
+ "
149
+ class="button"
150
+ @click="openAxons"
151
+ >
152
+ Explore destination data
153
+ </el-button>
154
+ </div>
155
+
156
+ <el-button
157
+ v-show="
158
+ tooltipEntry.componentsWithDatasets &&
159
+ tooltipEntry.componentsWithDatasets.length > 0
160
+ "
161
+ class="button"
162
+ @click="openAll"
163
+ >
164
+ Search for data on components
165
+ </el-button>
66
166
 
67
167
  <external-resource-card :resources="resources" v-if="resources.length"></external-resource-card>
68
168
  </div>
@@ -71,25 +171,21 @@
71
171
  </template>
72
172
 
73
173
  <script>
74
- import {
75
- ArrowUp as ElIconArrowUp,
76
- ArrowDown as ElIconArrowDown,
77
- Warning as ElIconWarning,
78
- } from '@element-plus/icons-vue'
79
174
  import EventBus from "../EventBus.js";
80
- import ConnectivityList from '../ConnectivityList/ConnectivityList.vue';
81
- import ExternalResourceCard from './ExternalResourceCard.vue';
82
- import { capitalise, titleCase } from '../utilities.js';
175
+
176
+ const titleCase = (str) => {
177
+ return str.replace(/\w\S*/g, (t) => {
178
+ return t.charAt(0).toUpperCase() + t.substr(1).toLowerCase();
179
+ });
180
+ };
181
+
182
+ const capitalise = function (str) {
183
+ if (str) return str.charAt(0).toUpperCase() + str.slice(1);
184
+ return "";
185
+ };
83
186
 
84
187
  export default {
85
188
  name: "ProvenancePopup",
86
- components: {
87
- ElIconArrowUp,
88
- ElIconArrowDown,
89
- ElIconWarning,
90
- ConnectivityList,
91
- ExternalResourceCard,
92
- },
93
189
  props: {
94
190
  tooltipEntry: {
95
191
  type: Object,
@@ -107,30 +203,20 @@ export default {
107
203
  inject: ["getFeaturesAlert"],
108
204
  data: function () {
109
205
  return {
206
+ controller: undefined,
207
+ activeSpecies: undefined,
208
+ pubmedSearchUrl: "",
110
209
  loading: false,
210
+ showToolip: false,
111
211
  showDetails: false,
112
212
  originDescriptions: {
113
213
  motor: "is the location of the initial cell body of the circuit",
114
214
  sensory: "is the location of the initial cell body in the PNS circuit",
115
215
  },
116
- origins: [],
117
- components: [],
118
- destinations: [],
119
- originsWithDatasets: [],
120
216
  componentsWithDatasets: [],
121
- destinationsWithDatasets: [],
122
- availableAnatomyFacets: [],
217
+ uberons: [{ id: undefined, name: undefined }],
123
218
  };
124
219
  },
125
- watch: {
126
- tooltipEntry: {
127
- handler: function (val) {
128
- this.updateConnectionsData(val);
129
- },
130
- immediate: true,
131
- deep: true,
132
- }
133
- },
134
220
  computed: {
135
221
  featuresAlert() {
136
222
  return this.getFeaturesAlert();
@@ -163,10 +249,6 @@ export default {
163
249
  return text;
164
250
  },
165
251
  },
166
- mounted: function () {
167
- this.loadAvailableAnatomyFacets();
168
- this.updateConnectionsData(this.tooltipEntry);
169
- },
170
252
  methods: {
171
253
  titleCase: function (title) {
172
254
  return titleCase(title);
@@ -174,25 +256,29 @@ export default {
174
256
  capitalise: function (text) {
175
257
  return capitalise(text);
176
258
  },
177
- onConnectivityActionClick: function (data) {
178
- EventBus.emit('onActionClick', data);
259
+ openUrl: function (url) {
260
+ window.open(url, "_blank");
179
261
  },
180
- // Load available anatomy facets from the local storage if available.
181
- // The data is from Algolia in Sidebar.
182
- loadAvailableAnatomyFacets: function () {
183
- const availableAnatomyFacets = localStorage.getItem('available-anatomy-facets');
184
-
185
- if (availableAnatomyFacets) {
186
- this.availableAnatomyFacets = JSON.parse(availableAnatomyFacets);
187
- }
262
+ openAll: function () {
263
+ EventBus.emit("onActionClick", {
264
+ type: "Facets",
265
+ labels: this.tooltipEntry.componentsWithDatasets.map((a) => a.name),
266
+ });
188
267
  },
189
- updateConnectionsData: function (source) {
190
- this.origins = source.origins;
191
- this.components = source.components;
192
- this.destinations = source.destinations;
193
- this.originsWithDatasets = source.originsWithDatasets;
194
- this.componentsWithDatasets = source.componentsWithDatasets;
195
- this.destinationsWithDatasets = source.destinationsWithDatasets;
268
+ openAxons: function () {
269
+ EventBus.emit("onActionClick", {
270
+ type: "Facets",
271
+ labels: this.tooltipEntry.destinationsWithDatasets.map((a) => a.name),
272
+ });
273
+ },
274
+ openDendrites: function () {
275
+ EventBus.emit("onActionClick", {
276
+ type: "Facets",
277
+ labels: this.tooltipEntry.originsWithDatasets.map((a) => a.name),
278
+ });
279
+ },
280
+ pubmedSearchUrlUpdate: function (val) {
281
+ this.pubmedSearchUrl = val;
196
282
  },
197
283
  },
198
284
  };
@@ -405,10 +491,6 @@ export default {
405
491
  .block {
406
492
  padding-top: 0.5em;
407
493
  }
408
-
409
- .connectivity-list {
410
- padding-top: 1rem;
411
- }
412
494
  }
413
495
 
414
496
  .scrollbar::-webkit-scrollbar-track {
@@ -1,7 +1,6 @@
1
1
  import AnnotationPopup from "./Tooltip/AnnotationPopup.vue";
2
2
  import CreateTooltipContent from "./Tooltip/CreateTooltipContent.vue";
3
3
  import ConnectivityGraph from "./ConnectivityGraph/ConnectivityGraph.vue";
4
- import ConnectivityList from "./ConnectivityList/ConnectivityList.vue";
5
4
  import CopyToClipboard from "./CopyToClipboard/CopyToClipboard.vue";
6
5
  import DrawToolbar from "./DrawToolbar/DrawToolbar.vue";
7
6
  import HelpModeDialog from "./HelpModeDialog/HelpModeDialog.vue";
@@ -13,7 +12,6 @@ export {
13
12
  AnnotationPopup,
14
13
  CreateTooltipContent,
15
14
  ConnectivityGraph,
16
- ConnectivityList,
17
15
  CopyToClipboard,
18
16
  DrawToolbar,
19
17
  HelpModeDialog,