Skip to content

Commit 79cf689

Browse files
authored
Merge pull request #241 from DigitalSlideArchive/annotation-metadata
Support annotation metadata
2 parents 9429f16 + e38076a commit 79cf689

File tree

7 files changed

+149
-25
lines changed

7 files changed

+149
-25
lines changed

histomicsui/web_client/dialogs/saveAnnotation.js

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ import _ from 'underscore';
22
import $ from 'jquery';
33
import tinycolor from 'tinycolor2';
44

5-
import AccessWidget from '@girder/core/views/widgets/AccessWidget';
6-
import View from '@girder/core/views/View';
75
import { AccessType } from '@girder/core/constants';
86
import { formatDate, DATE_SECOND } from '@girder/core/misc';
7+
import AccessWidget from '@girder/core/views/widgets/AccessWidget';
8+
// import MetadataWidget from '@girder/core/views/widgets/MetadataWidget';
9+
import View from '@girder/core/views/View';
910

10-
import saveAnnotation from '../templates/dialogs/saveAnnotation.pug';
11+
import MetadataWidget from '../panels/MetadataWidget';
1112
import '../stylesheets/dialogs/saveAnnotation.styl';
13+
import saveAnnotation from '../templates/dialogs/saveAnnotation.pug';
1214

1315
/**
1416
* Create a modal dialog with fields to edit the properties of
@@ -40,7 +42,6 @@ var SaveAnnotation = View.extend({
4042

4143
if (showStyleEditor) {
4244
const elements = this.annotation.get('annotation').elements;
43-
console.assert(elements.length > 0); // otherwise we wouldn't show the style editor
4445
const firstElement = elements[0];
4546
if (elements.every((d) => d.lineWidth === firstElement.lineWidth)) {
4647
defaultStyles.lineWidth = firstElement.lineWidth;
@@ -66,6 +67,28 @@ var SaveAnnotation = View.extend({
6667
})
6768
).girderModal(this);
6869
this.$('.h-colorpicker').colorpicker();
70+
71+
if (this.annotation.id) {
72+
if (!this.annotation.meta) {
73+
this.annotation._meta = Object.assign({}, (this.annotation.get('annotation') || {}).attributes || {});
74+
}
75+
// copy the metadata to a place that is expected for the widget
76+
if (!this.metadataWidget) {
77+
this.metadataWidget = new MetadataWidget({
78+
item: this.annotation,
79+
parentView: this,
80+
fieldName: '_meta',
81+
accessLevel: this.annotation.get('_accessLevel'),
82+
panel: false,
83+
noSave: true
84+
});
85+
}
86+
this.metadataWidget.setItem(this.annotation);
87+
this.metadataWidget.accessLevel = this.annotation.get('_accessLevel');
88+
this.metadataWidget.setElement(this.$('.hui-annotation-metadata')).render();
89+
}
90+
91+
this.$el.find('.modal-dialog').addClass('hui-save-annotation-dialog');
6992
return this;
7093
},
7194

@@ -85,6 +108,9 @@ var SaveAnnotation = View.extend({
85108
},
86109

87110
cancel(evt) {
111+
if (this.annotation) {
112+
delete this.annotation._meta;
113+
}
88114
evt.preventDefault();
89115
this.$el.modal('hide');
90116
},
@@ -144,6 +170,8 @@ var SaveAnnotation = View.extend({
144170
name: this.$('#h-annotation-name').val(),
145171
description: this.$('#h-annotation-description').val()
146172
});
173+
this.annotation.attributes.annotation.attributes = this.annotation._meta;
174+
delete this.annotation._meta;
147175
this.trigger('g:submit');
148176
this.$el.modal('hide');
149177
},
@@ -171,6 +199,7 @@ var dialog = new SaveAnnotation({
171199
*/
172200
function show(annotation, options) {
173201
_.defaults(options, { title: 'Create annotation' });
202+
delete annotation._meta;
174203
dialog.annotation = annotation;
175204
dialog.options = options;
176205
dialog.setElement('#g-dialog-container').render();

histomicsui/web_client/panels/MetadataPlot.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ var MetadataPlot = Panel.extend({
2929
},
3030
'click .h-panel-maximize': function (event) {
3131
this.$el.html('');
32-
this.expand();
32+
this.expand(event);
3333
this.$('.s-panel-content').addClass('in');
3434
let panelElem = this.$el.closest('.s-panel');
3535
let maximize = !panelElem.hasClass('h-panel-maximized');

histomicsui/web_client/panels/MetadataWidget.js

Lines changed: 86 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { AccessType } from '@girder/core/constants';
66
import { confirm } from '@girder/core/dialog';
77
import events from '@girder/core/events';
88
import { localeSort } from '@girder/core/misc';
9+
import View from '@girder/core/views/View';
910

1011
import JsonMetadatumEditWidgetTemplate from '@girder/core/templates/widgets/jsonMetadatumEditWidget.pug';
1112
import JsonMetadatumViewTemplate from '@girder/core/templates/widgets/jsonMetadatumView.pug';
@@ -20,7 +21,21 @@ import 'bootstrap/js/dropdown';
2021
import metadataWidgetTemplate from '../templates/panels/metadataWidget.pug';
2122
import '../stylesheets/panels/metadataWidget.styl';
2223

23-
var MetadatumWidget = Panel.extend({
24+
function getMetadataRecord(item, fieldName) {
25+
if (item[fieldName]) {
26+
return item[fieldName];
27+
}
28+
let meta = item.attributes;
29+
fieldName.split('.').forEach((part) => {
30+
if (!meta[part]) {
31+
meta[part] = {};
32+
}
33+
meta = meta[part];
34+
});
35+
return meta;
36+
}
37+
38+
var MetadatumWidget = View.extend({
2439
className: 'g-widget-metadata-row',
2540

2641
events: {
@@ -38,6 +53,7 @@ var MetadatumWidget = Panel.extend({
3853
this.parentView = settings.parentView;
3954
this.fieldName = settings.fieldName;
4055
this.apiPath = settings.apiPath;
56+
this.noSave = settings.noSave;
4157
this.onMetadataEdited = settings.onMetadataEdited;
4258
this.onMetadataAdded = settings.onMetadataAdded;
4359
},
@@ -46,8 +62,8 @@ var MetadatumWidget = Panel.extend({
4662
var newMode = this.parentView.modes[to];
4763

4864
if (_.has(newMode, 'validation') &&
49-
_.has(newMode.validation, 'from') &&
50-
_.has(newMode.validation.from, from)) {
65+
_.has(newMode.validation, 'from') &&
66+
_.has(newMode.validation.from, from)) {
5167
var validate = newMode.validation.from[from][0];
5268
var msg = newMode.validation.from[from][1];
5369

@@ -85,6 +101,7 @@ var MetadatumWidget = Panel.extend({
85101
parentView: this,
86102
fieldName: this.fieldName,
87103
apiPath: this.apiPath,
104+
noSave: this.noSave,
88105
onMetadataEdited: this.onMetadataEdited,
89106
onMetadataAdded: this.onMetadataAdded
90107
}, overrides || {});
@@ -105,6 +122,7 @@ var MetadatumWidget = Panel.extend({
105122
parentView: this,
106123
fieldName: this.fieldName,
107124
apiPath: this.apiPath,
125+
noSave: this.noSave,
108126
onMetadataEdited: this.onMetadataEdited,
109127
onMetadataAdded: this.onMetadataAdded
110128
};
@@ -142,7 +160,7 @@ var MetadatumWidget = Panel.extend({
142160
}
143161
});
144162

145-
var MetadatumEditWidget = Panel.extend({
163+
var MetadatumEditWidget = View.extend({
146164
events: {
147165
'click .g-widget-metadata-cancel-button': 'cancelEdit',
148166
'click .g-widget-metadata-save-button': 'save',
@@ -161,6 +179,7 @@ var MetadatumEditWidget = Panel.extend({
161179
key: this.$el.find('.g-widget-metadata-key-input').val(),
162180
value: this.getCurrentValue()
163181
});
182+
return false;
164183
}
165184
},
166185

@@ -173,6 +192,7 @@ var MetadatumEditWidget = Panel.extend({
173192
this.newDatum = settings.newDatum;
174193
this.fieldName = settings.fieldName;
175194
this.apiPath = settings.apiPath;
195+
this.noSave = settings.noSave;
176196
this.onMetadataEdited = settings.onMetadataEdited;
177197
this.onMetadataAdded = settings.onMetadataAdded;
178198
},
@@ -187,6 +207,11 @@ var MetadatumEditWidget = Panel.extend({
187207
event.stopImmediatePropagation();
188208
const target = $(event.currentTarget);
189209
var metadataList = target.parent().parent();
210+
if (this.noSave) {
211+
delete getMetadataRecord(this.item, this.fieldName)[this.key];
212+
metadataList.remove();
213+
return;
214+
}
190215
var params = {
191216
text: 'Are you sure you want to delete the metadatum <b>' +
192217
_.escape(this.key) + '</b>?',
@@ -222,14 +247,14 @@ var MetadatumEditWidget = Panel.extend({
222247
event.stopImmediatePropagation();
223248
const target = $(event.currentTarget);
224249
var curRow = target.parent(),
225-
tempKey = curRow.find('.g-widget-metadata-key-input').val(),
250+
tempKey = curRow.find('.g-widget-metadata-key-input').val().trim(),
226251
tempValue = (value !== undefined) ? value : curRow.find('.g-widget-metadata-value-input').val();
227252
if (this.newDatum && tempKey === '') {
228253
events.trigger('g:alert', {
229254
text: 'A key is required for all metadata.',
230255
type: 'warning'
231256
});
232-
return;
257+
return false;
233258
}
234259
var saveCallback = () => {
235260
this.key = tempKey;
@@ -260,6 +285,18 @@ var MetadatumEditWidget = Panel.extend({
260285
if (this.onMetadataAdded) {
261286
this.onMetadataAdded(tempKey, tempValue, saveCallback, errorCallback);
262287
} else {
288+
if (this.noSave) {
289+
if (getMetadataRecord(this.item, this.fieldName)[tempKey] !== undefined) {
290+
events.trigger('g:alert', {
291+
text: tempKey + ' is already a metadata key',
292+
type: 'warning'
293+
});
294+
return false;
295+
}
296+
getMetadataRecord(this.item, this.fieldName)[tempKey] = tempValue;
297+
this.parentView.parentView.render();
298+
return;
299+
}
263300
this.item.addMetadata(tempKey, tempValue, saveCallback, errorCallback, {
264301
field: this.fieldName,
265302
path: this.apiPath
@@ -269,6 +306,20 @@ var MetadatumEditWidget = Panel.extend({
269306
if (this.onMetadataEdited) {
270307
this.onMetadataEdited(tempKey, this.key, tempValue, saveCallback, errorCallback);
271308
} else {
309+
if (this.noSave) {
310+
tempKey = tempKey === '' ? this.key : tempKey;
311+
if (tempKey !== this.key && getMetadataRecord(this.item, this.fieldName)[tempKey] !== undefined) {
312+
events.trigger('g:alert', {
313+
text: tempKey + ' is already a metadata key',
314+
type: 'warning'
315+
});
316+
return false;
317+
}
318+
delete getMetadataRecord(this.item, this.fieldName)[this.key];
319+
getMetadataRecord(this.item, this.fieldName)[tempKey] = tempValue;
320+
this.parentView.parentView.render();
321+
return;
322+
}
272323
this.item.editMetadata(tempKey, this.key, tempValue, saveCallback, errorCallback, {
273324
field: this.fieldName,
274325
path: this.apiPath
@@ -307,6 +358,7 @@ var JsonMetadatumEditWidget = MetadatumEditWidget.extend({
307358
text: 'The field contains invalid JSON and can not be saved.',
308359
type: 'warning'
309360
});
361+
return false;
310362
}
311363
},
312364

@@ -345,7 +397,7 @@ var MetadataWidget = Panel.extend({
345397
this.addMetadata(event, 'simple');
346398
},
347399
'click .h-panel-maximize': function (event) {
348-
this.expand();
400+
this.expand(event);
349401
this.$('.s-panel-content').addClass('in');
350402
let panelElem = this.$el.closest('.s-panel');
351403
let maximize = !panelElem.hasClass('h-panel-maximized');
@@ -382,6 +434,8 @@ var MetadataWidget = Panel.extend({
382434
this.apiPath = settings.apiPath;
383435
this.accessLevel = settings.accessLevel;
384436
this.onMetadataEdited = settings.onMetadataEdited;
437+
this.panel = settings.panel === undefined ? true : settings.panel;
438+
this.noSave = settings.noSave;
385439
// the event is created
386440
this.on('h-metadata-panel-update', (event) => {
387441
this.renderMetadataWidgetHeader(event);
@@ -456,6 +510,7 @@ var MetadataWidget = Panel.extend({
456510
apiPath: this.apiPath,
457511
accessLevel: this.accessLevel,
458512
parentView: this,
513+
noSave: this.noSave,
459514
onMetadataEdited: this.onMetadataEdited,
460515
onMetadataAdded: this.onMetadataAdded
461516
});
@@ -468,6 +523,7 @@ var MetadataWidget = Panel.extend({
468523
fieldName: this.fieldName,
469524
apiPath: this.apiPath,
470525
accessLevel: this.accessLevel,
526+
noSave: this.noSave,
471527
newDatum: true,
472528
parentView: widget,
473529
onMetadataEdited: this.onMetadataEdited,
@@ -478,20 +534,32 @@ var MetadataWidget = Panel.extend({
478534
},
479535

480536
renderMetadataWidgetHeader: function () {
481-
// pervent automatically collapse the widget after rendering again
537+
// prevent automatically collapsing the widget after rendering again
482538
this.render();
483539
},
484540

485541
render: function () {
486542
if (this.item && this.item.id) {
487-
const imageId = this.item.id;
488-
const apiPath = `/item/${imageId}/metadata`;
489-
this.item.getAccessLevel((accessLevel) => {
490-
var metaDict = this.item.get(this.fieldName) || {};
543+
let func = this.item.getAccessLevel;
544+
if (this.item.get('_modelType') === 'annotation') {
545+
func = (callback) => {
546+
const accessLevel = this.item.getAccessLevel();
547+
callback(accessLevel);
548+
};
549+
}
550+
func.call(this.item, (accessLevel) => {
551+
const fieldParts = this.fieldName.split('.');
552+
let metaDict = this.item.get(fieldParts[0]) || {};
553+
fieldParts.slice(1).forEach((part) => {
554+
metaDict = metaDict[part] || {};
555+
});
556+
if (this.item[this.fieldName]) {
557+
metaDict = this.item[this.fieldName];
558+
}
491559
var metaKeys = Object.keys(metaDict);
492560
metaKeys.sort(localeSort);
493-
var firstKey = (metaKeys)[0];
494-
var firstValue = metaDict[firstKey];
561+
const firstKey = (metaKeys)[0];
562+
let firstValue = metaDict[firstKey];
495563
if (_.isObject(firstValue)) {
496564
// if the value is a json object, JSON.stringify to make it more readable
497565
firstValue = JSON.stringify(firstValue);
@@ -503,8 +571,9 @@ var MetadataWidget = Panel.extend({
503571
firstValue: firstValue,
504572
accessLevel: this.item.attributes._accessLevel,
505573
AccessType: AccessType,
574+
panel: this.panel,
506575
// if never rendered, the jquery selector will be empty and won't be visible
507-
collapsed: !this.$('.s-panel-content').hasClass('in') && !this.$el.closest('.s-panel').hasClass('h-panel-maximized')
576+
collapsed: this.panel && !this.$('.s-panel-content').hasClass('in') && !this.$el.closest('.s-panel').hasClass('h-panel-maximized')
508577
}));
509578
// Append each metadatum
510579
_.each(metaKeys, function (metaKey) {
@@ -515,7 +584,8 @@ var MetadataWidget = Panel.extend({
515584
accessLevel: this.item.attributes._accessLevel,
516585
parentView: this,
517586
fieldName: this.fieldName,
518-
apiPath: apiPath,
587+
apiPath: this.apiPath,
588+
noSave: this.noSave,
519589
onMetadataEdited: this.onMetadataEdited,
520590
onMetadataAdded: this.onMetadataAdded
521591
}).render().$el);

histomicsui/web_client/stylesheets/dialogs/saveAnnotation.styl

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,23 @@
33
float left
44
.hui-info-list-entry
55
user-select text
6+
.hui-annotation-metadata.hui-annotation-metadata-dialog
7+
.s-panel-title-container
8+
background-color #f0f0f0
9+
padding 5px 6px
10+
color #555
11+
font-size 16px
12+
font-weight bold
13+
margin-top 8px
14+
position relative
15+
i.icon-up-open, i.icon-down-open
16+
display none
17+
.g-widget-metadata-add-button
18+
margin-top 0
19+
@media (min-width: 768px)
20+
.modal-dialog.hui-save-annotation-dialog
21+
width inherit
22+
@media (min-width: 900px)
23+
.modal-dialog.hui-save-annotation-dialog
24+
width 70%
25+
max-width 900px

histomicsui/web_client/templates/dialogs/saveAnnotation.pug

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,10 @@
2828
i.icon-share
2929
| Unique ID: #{model.id}
3030
if model.get('_version')
31-
|
31+
= " "
3232
i.icon-angle-circled-down
3333
| Global Version: #{model.get('_version')}
34+
.hui-annotation-metadata.hui-annotation-metadata-dialog
3435
if showStyleEditor
3536
hr
3637
h4 Reset the style for all point, line, and polygon elements in this annotation

0 commit comments

Comments
 (0)