diff --git a/kahuna/public/js/components/gr-image-metadata/gr-image-metadata.html b/kahuna/public/js/components/gr-image-metadata/gr-image-metadata.html index cc59b11939..b9a0db74d0 100644 --- a/kahuna/public/js/components/gr-image-metadata/gr-image-metadata.html +++ b/kahuna/public/js/components/gr-image-metadata/gr-image-metadata.html @@ -358,8 +358,8 @@ -
People
-
+
People
+
@@ -597,22 +598,14 @@
-
  • - - {{label.data}} - -
  • + +
    @@ -642,12 +635,11 @@
    Keywords
    diff --git a/kahuna/public/js/components/gr-image-metadata/gr-image-metadata.js b/kahuna/public/js/components/gr-image-metadata/gr-image-metadata.js index e159971f43..3546ca237f 100644 --- a/kahuna/public/js/components/gr-image-metadata/gr-image-metadata.js +++ b/kahuna/public/js/components/gr-image-metadata/gr-image-metadata.js @@ -6,8 +6,8 @@ import template from './gr-image-metadata.html'; import '../../image/service'; import '../../edits/service'; import '../gr-description-warning/gr-description-warning'; -import { editOptions, overwrite } from '../../util/constants/editOptions'; import '../../util/storage'; +import { editOptions, overwrite } from '../../util/constants/editOptions'; import '../../services/image-accessor'; import '../../services/image-list'; import '../../services/label'; @@ -146,16 +146,40 @@ module.controller('grImageMetadataCtrl', [ }); }; - ctrl.addLabel = function (label) { - var imageArray = Array.from(ctrl.selectedImages); - labelService.batchAdd(imageArray, [label]); + ctrl.addLabelToImages = labelService.batchAdd; + ctrl.removeLabelFromImages = labelService.batchRemove; + ctrl.labelAccessor = (image) => imageAccessor.readLabels(image).map(label => label.data); + + ctrl.peopleAccessor = (image) => imageAccessor.readPeopleInImage(image); + ctrl.removePersonFromImages = (images, removedPerson) => { + images.map((image) => { + const maybeNewPeopleInImage = ctrl.peopleAccessor(image)?.filter((person) => person !== removedPerson); + const newPeopleInImage = maybeNewPeopleInImage ? maybeNewPeopleInImage : []; + editsService.batchUpdateMetadataField( + [image], + 'peopleInImage', + newPeopleInImage, + ctrl.descriptionOption + ); + }); + return Promise.resolve(ctrl.selectedImages); }; - - ctrl.removeLabel = function (label) { - var imageArray = Array.from(ctrl.selectedImages); - labelService.batchRemove(imageArray, label); + ctrl.addPersonToImages = (images, addedPerson) => { + images.map((image) => { + const currentPeopleInImage = ctrl.peopleAccessor(image); + const newPeopleInImage = currentPeopleInImage ? [...currentPeopleInImage, addedPerson] : [addedPerson]; + editsService.batchUpdateMetadataField( + [image], + 'peopleInImage', + newPeopleInImage, + ctrl.descriptionOption + ); + }); + return Promise.resolve(ctrl.selectedImages); }; + ctrl.keywordAccessor = (image) => imageAccessor.readMetadata(image).keywords; + const ignoredMetadata = [ 'title', 'description', 'copyright', 'keywords', 'byline', 'credit', 'subLocation', 'city', 'state', 'country', diff --git a/kahuna/public/js/edits/image-editor.html b/kahuna/public/js/edits/image-editor.html index f662fe69c9..5033304733 100644 --- a/kahuna/public/js/edits/image-editor.html +++ b/kahuna/public/js/edits/image-editor.html @@ -229,10 +229,16 @@

    Organisation and Grouping

    active="ctrl.inputtingLabel" class="result-editor__field-container__add-button"> - - + + diff --git a/kahuna/public/js/edits/image-editor.js b/kahuna/public/js/edits/image-editor.js index 25a7a18059..1936bdc60c 100644 --- a/kahuna/public/js/edits/image-editor.js +++ b/kahuna/public/js/edits/image-editor.js @@ -4,16 +4,19 @@ import './image-editor.css'; import {service} from './service'; import {imageService} from '../image/service'; +import '../services/label'; +import {imageAccessor} from '../services/image-accessor'; import {usageRightsEditor} from '../usage-rights/usage-rights-editor'; import {leases} from '../leases/leases'; import {archiver} from '../components/gr-archiver-status/gr-archiver-status'; import {collectionsApi} from '../services/api/collections-api'; import {rememberScrollTop} from '../directives/gr-remember-scroll-top'; - export var imageEditor = angular.module('kahuna.edits.imageEditor', [ service.name, imageService.name, + "kahuna.services.label", + imageAccessor.name, usageRightsEditor.name, archiver.name, collectionsApi.name, @@ -28,6 +31,8 @@ imageEditor.controller('ImageEditorCtrl', [ 'editsService', 'editsApi', 'imageService', + 'labelService', + 'imageAccessor', 'collections', 'mediaApi', @@ -37,6 +42,8 @@ imageEditor.controller('ImageEditorCtrl', [ editsService, editsApi, imageService, + labelService, + imageAccessor, collections, mediaApi) { @@ -67,6 +74,12 @@ imageEditor.controller('ImageEditorCtrl', [ ctrl.undelete = undelete; + ctrl.imageAsArray = [ctrl.image]; + + ctrl.addLabelToImages = labelService.batchAdd; + ctrl.removeLabelFromImages = labelService.batchRemove; + ctrl.labelAccessor = (image) => imageAccessor.readLabels(image).map(label => label.data); + //TODO put collections in their own directive ctrl.addCollection = false; ctrl.addToCollection = addToCollection; @@ -135,6 +148,7 @@ imageEditor.controller('ImageEditorCtrl', [ function onSave() { return ctrl.image.get().then(newImage => { ctrl.image = newImage; + ctrl.imageAsArray = [newImage]; ctrl.usageRights = imageService(ctrl.image).usageRights; updateUsageRightsCategory(); ctrl.status = ctrl.image.data.valid ? 'ready' : 'invalid'; diff --git a/kahuna/public/js/edits/index.js b/kahuna/public/js/edits/index.js index 95040f8f08..6913ff8adf 100644 --- a/kahuna/public/js/edits/index.js +++ b/kahuna/public/js/edits/index.js @@ -1,7 +1,7 @@ import angular from 'angular'; -import './labeller'; +import './list-editor'; export var edits = angular.module('kahuna.edits', [ - 'kahuna.edits.labeller' + 'kahuna.edits.listEditor' ]); diff --git a/kahuna/public/js/edits/labeller-compact.html b/kahuna/public/js/edits/labeller-compact.html deleted file mode 100644 index 83d52561f8..0000000000 --- a/kahuna/public/js/edits/labeller-compact.html +++ /dev/null @@ -1,16 +0,0 @@ -
    - -
    diff --git a/kahuna/public/js/edits/labeller.html b/kahuna/public/js/edits/labeller.html deleted file mode 100644 index 1b0a8ccd29..0000000000 --- a/kahuna/public/js/edits/labeller.html +++ /dev/null @@ -1,33 +0,0 @@ -
    - - - - - - - -
    diff --git a/kahuna/public/js/edits/labeller.js b/kahuna/public/js/edits/labeller.js deleted file mode 100644 index d3dc3ff57e..0000000000 --- a/kahuna/public/js/edits/labeller.js +++ /dev/null @@ -1,129 +0,0 @@ -import angular from 'angular'; -import template from './labeller.html'; -import templateCompact from './labeller-compact.html'; -import './labeller.css'; - -import '../search/query-filter'; -import '../services/label'; - -export var labeller = angular.module('kahuna.edits.labeller', [ - 'kahuna.search.filters.query', - 'kahuna.services.label' -]); - -labeller.controller('LabellerCtrl', [ - '$rootScope', - '$scope', - '$window', - '$timeout', - 'labelService', - function($rootScope, - $scope, - $window, - $timeout, - labelService) { - - var ctrl = this; - - $scope.$watch(() => ctrl.image.data.userMetadata.data.labels, newLabels => { - ctrl.labels = newLabels; - }); - - function saveFailed(e) { - console.error(e); - $window.alert('Something went wrong when saving, please try again!'); - } - - ctrl.addLabels = labels => { - ctrl.adding = true; - - labelService.add(ctrl.image, labels) - .then(img => { - ctrl.image = img; - ctrl.labels = ctrl.image.data.userMetadata.data.labels; - }) - .catch(saveFailed) - .finally(() => { - ctrl.adding = false; - }); - }; - - ctrl.labelsBeingRemoved = new Set(); - ctrl.removeLabel = label => { - ctrl.labelsBeingRemoved.add(label); - - labelService.remove(ctrl.image, label) - .then(img => { - ctrl.image = img; - ctrl.labels = ctrl.image.data.userMetadata.data.labels; - }) - .catch(saveFailed) - .finally(() => { - ctrl.labelsBeingRemoved.delete(label); - }); - }; - - ctrl.removeLabels = () => { - ctrl.labels.data.map(label => ctrl.removeLabel(label.data)); - }; - - const batchAddLabelsEvent = 'events:batch-apply:add-labels'; - const batchRemoveLabelsEvent = 'events:batch-apply:remove-labels'; - - if (Boolean(ctrl.withBatch)) { - $scope.$on(batchAddLabelsEvent, (e, labels) => ctrl.addLabels(labels)); - $scope.$on(batchRemoveLabelsEvent, () => ctrl.removeLabels()); - - ctrl.batchApplyLabels = () => { - var labels = ctrl.labels.data.map(label => label.data); - - if (labels.length > 0) { - $rootScope.$broadcast(batchAddLabelsEvent, labels); - } else { - ctrl.confirmDelete = true; - - $timeout(() => { - ctrl.confirmDelete = false; - }, 5000); - } - }; - - ctrl.batchRemoveLabels = () => { - ctrl.confirmDelete = false; - $rootScope.$broadcast(batchRemoveLabelsEvent); - }; - } - -}]); - -labeller.directive('uiLabeller', [function() { - return { - restrict: 'E', - scope: { - // Annoying that we can't make a uni-directional binding - // as we don't really want to modify the original - image: '=', - withBatch: '=?' - }, - controller: 'LabellerCtrl', - controllerAs: 'ctrl', - bindToController: true, - template: template - }; -}]); - -labeller.directive('uiLabellerCompact', [function() { - return { - restrict: 'E', - scope: { - // Annoying that we can't make a uni-directional binding - // as we don't really want to modify the original - image: '=', - disabled: '=' - }, - controller: 'LabellerCtrl', - controllerAs: 'ctrl', - bindToController: true, - template: templateCompact - }; -}]); diff --git a/kahuna/public/js/edits/list-editor-compact.html b/kahuna/public/js/edits/list-editor-compact.html new file mode 100644 index 0000000000..281064dff2 --- /dev/null +++ b/kahuna/public/js/edits/list-editor-compact.html @@ -0,0 +1,15 @@ +
    + +
    diff --git a/kahuna/public/js/edits/list-editor-info-panel.html b/kahuna/public/js/edits/list-editor-info-panel.html new file mode 100644 index 0000000000..e1c862da12 --- /dev/null +++ b/kahuna/public/js/edits/list-editor-info-panel.html @@ -0,0 +1,16 @@ +
  • + + {{element.data}} + +
  • diff --git a/kahuna/public/js/edits/list-editor-upload.html b/kahuna/public/js/edits/list-editor-upload.html new file mode 100644 index 0000000000..c53d881259 --- /dev/null +++ b/kahuna/public/js/edits/list-editor-upload.html @@ -0,0 +1,32 @@ +
    + + + + + + + +
    diff --git a/kahuna/public/js/edits/labeller.css b/kahuna/public/js/edits/list-editor.css similarity index 57% rename from kahuna/public/js/edits/labeller.css rename to kahuna/public/js/edits/list-editor.css index 009991eb1f..473a4f8fd2 100644 --- a/kahuna/public/js/edits/labeller.css +++ b/kahuna/public/js/edits/list-editor.css @@ -1,5 +1,5 @@ -ui-labeller, -ui-labeller-compact { +ui-list-editor, +ui-list-editor-compact { display: block; } @@ -8,73 +8,72 @@ ui-labeller-compact { display: flex; } -.label { +.element { color: white; display: inline-flex; justify-content: space-between; margin: 0 5px 5px 0; } -.label--removing { +.element--removing { background-color: #99ddff; } /* -label--partial is part of gr-panel. -TODO refactor gr-panel to use common labeller component +element--partial is part of gr-panel. */ -.label--partial .label__value, -.label--partial .label__add, -.label--partial .label__remove { +.element--partial .element__value, +.element--partial .element__add, +.element--partial .element__remove { background-color: white; color: #00adee; } -.label__value, -.label__remove { +.element__value, +.element__remove { color: white; background-color: #00adee; } -.label__link:hover, -.label__remove:hover { +.element__link:hover, +.element__remove:hover { color: #00adee; background-color: white; } -.label__add, -.label__value { +.element__add, +.element__value { border-top-left-radius: 2px; border-bottom-left-radius: 2px; } -.label--partial .label__value { +.element--partial .element__value { border-radius: 0px; } -.label__value { +.element__value { padding: 0 5px; } -.label__value--compact { +.element__value--compact { border-radius: 2px; } -.label__remove { +.element__remove { border-top-right-radius: 2px; border-bottom-right-radius: 2px; } -.label button:hover gr-icon { +.element button:hover gr-icon { color: #333; } -.label button:active gr-icon { +.element button:active gr-icon { background-color: #008fc5; color: #333; } -.label--partial button:active gr-icon { +.element--partial button:active gr-icon { background-color: #CFCFCF; color: #333; } @@ -88,3 +87,9 @@ TODO refactor gr-panel to use common labeller component .labeller__apply-all:hover { color: #FFF; } + +.image-info__keywords .element__value { + background-color: #222; + color: #999; + border-radius: 8px; +} \ No newline at end of file diff --git a/kahuna/public/js/edits/list-editor.js b/kahuna/public/js/edits/list-editor.js new file mode 100644 index 0000000000..068cae3104 --- /dev/null +++ b/kahuna/public/js/edits/list-editor.js @@ -0,0 +1,181 @@ +import angular from 'angular'; +import templateUpload from './list-editor-upload.html'; +import templateCompact from './list-editor-compact.html'; +import templateInfoPanel from './list-editor-info-panel.html'; +import './list-editor.css'; +import '../services/image-list'; + +import '../search/query-filter'; + +export var listEditor = angular.module('kahuna.edits.listEditor', [ + 'kahuna.search.filters.query', + 'kahuna.services.image-logic' +]); + +listEditor.controller('ListEditorCtrl', [ + '$rootScope', + '$scope', + '$window', + '$timeout', + 'imageLogic', + 'imageList', + function($rootScope, + $scope, + $window, + $timeout, + imageLogic, + imageList) { + var ctrl = this; + + const retrieveElementsWithOccurrences = (images) => imageList.getOccurrences(images.flatMap(img => ctrl.accessor(img))); + + $scope.$watchCollection('ctrl.images', updatedImages => updateHandler(updatedImages)); + + const updateHandler = (maybeUpdatedImages) => { + ctrl.images.forEach((img, index) => { + const updatedImage = maybeUpdatedImages.find(x => imageLogic.isSameImage(x, img)); + if (updatedImage) { + ctrl.images[index] = updatedImage; + } + }); + + ctrl.listWithOccurrences = retrieveElementsWithOccurrences(ctrl.images); + ctrl.plainList = ctrl.listWithOccurrences.map(x => x.data); + }; + + const updateListener = $rootScope.$on('images-updated', (e, updatedImages) => { + updateHandler(updatedImages); + }); + + ctrl.listWithOccurrences = retrieveElementsWithOccurrences(ctrl.images); + ctrl.plainList = ctrl.listWithOccurrences.map(x => x.data); + + function saveFailed(e) { + console.error(e); + $window.alert('Something went wrong when saving, please try again!'); + } + + ctrl.addElements = elements => { + ctrl.adding = true; + + ctrl.addToImages(ctrl.images, elements) + .then(imgs => { + updateHandler(imgs); + }) + .catch(saveFailed) + .finally(() => { + ctrl.adding = false; + }); + }; + + ctrl.elementsBeingRemoved = new Set(); + ctrl.removeElement = element => { + ctrl.elementsBeingRemoved.add(element); + + ctrl.removeFromImages(ctrl.images, element) + .then(imgs => { + updateHandler(imgs); + }) + .catch(saveFailed) + .finally(() => { + ctrl.elementsBeingRemoved.delete(element); + }); + }; + + ctrl.removeAll = () => { + ctrl.plainList.forEach(element => ctrl.removeFromImages(ctrl.images, element)); + }; + + const batchAddEvent = 'events:batch-apply:add-all'; + const batchRemoveEvent = 'events:batch-apply:remove-all'; + + if (Boolean(ctrl.withBatch)) { + $scope.$on(batchAddEvent, (e, elements) => ctrl.addElements(elements)); + $scope.$on(batchRemoveEvent, () => ctrl.removeAll()); + + ctrl.batchApply = () => { + var elements = ctrl.plainList; + + if (elements.length > 0) { + $rootScope.$broadcast(batchAddEvent, elements); + } else { + ctrl.confirmDelete = true; + + $timeout(() => { + ctrl.confirmDelete = false; + }, 5000); + } + }; + + ctrl.batchRemove = () => { + ctrl.confirmDelete = false; + $rootScope.$broadcast(batchRemoveEvent); + }; + } + + $scope.$on('$destroy', function() { + updateListener(); + }); +}]); + +listEditor.directive('uiListEditorUpload', [function() { + return { + restrict: 'E', + scope: { + // Annoying that we can't make a uni-directional binding + // as we don't really want to modify the original + images: '<', + withBatch: '=?', + addToImages: '=', + removeFromImages: '=', + accessor: '=', + queryFilter: '@', + elementName: '@', + elementNamePlural: '@' + }, + controller: 'ListEditorCtrl', + controllerAs: 'ctrl', + bindToController: true, + template: templateUpload + }; +}]); + +listEditor.directive('uiListEditorCompact', [function() { + return { + restrict: 'E', + scope: { + // Annoying that we can't make a uni-directional binding + // as we don't really want to modify the original + images: '<', + disabled: '=', + removeFromImages: '=', + accessor: '=', + queryFilter: '@' + }, + controller: 'ListEditorCtrl', + controllerAs: 'ctrl', + bindToController: true, + template: templateCompact + }; +}]); + +listEditor.directive('uiListEditorInfoPanel', [function() { + return { + restrict: 'E', + scope: { + // Annoying that we can't make a uni-directional binding + // as we don't really want to modify the original + images: '<', + disabled: '<', + addToImages: '<', + removeFromImages: '<', + accessor: '<', + queryFilter: '@', + elementName: '@' + }, + controller: 'ListEditorCtrl', + controllerAs: 'ctrl', + bindToController: true, + template: templateInfoPanel + }; +}]); diff --git a/kahuna/public/js/preview/image-large.html b/kahuna/public/js/preview/image-large.html index 25a7e04e49..7027046f70 100644 --- a/kahuna/public/js/preview/image-large.html +++ b/kahuna/public/js/preview/image-large.html @@ -70,15 +70,19 @@ - - + ng-if="!ctrl.inputtingLabel" + add-to-images="ctrl.addLabelToImages" + remove-from-images="ctrl.removeLabelFromImages" + accessor="ctrl.labelAccessor" + query-filter="queryLabelFilter"> + diff --git a/kahuna/public/js/preview/image.html b/kahuna/public/js/preview/image.html index 433ec5e8cd..51a26c1e78 100644 --- a/kahuna/public/js/preview/image.html +++ b/kahuna/public/js/preview/image.html @@ -62,15 +62,19 @@ - - + ng-if="!ctrl.inputtingLabel" + add-to-images="ctrl.addLabelToImages" + remove-from-images="ctrl.removeLabelFromImages" + accessor="ctrl.labelAccessor" + query-filter="queryLabelFilter"> + diff --git a/kahuna/public/js/preview/image.js b/kahuna/public/js/preview/image.js index 5ce7f15fd4..6055bb9f13 100644 --- a/kahuna/public/js/preview/image.js +++ b/kahuna/public/js/preview/image.js @@ -9,6 +9,8 @@ import templateLarge from './image-large.html'; import '../image/service'; import '../imgops/service'; import '../services/image/usages'; +import '../services/label'; +import '../services/image-accessor'; import '../components/gr-add-label/gr-add-label'; import '../components/gr-archiver-status/gr-archiver-status'; import '../components/gr-syndication-icon/gr-syndication-icon'; @@ -16,6 +18,8 @@ import '../components/gr-syndication-icon/gr-syndication-icon'; export var image = angular.module('kahuna.preview.image', [ 'gr.image.service', 'gr.image-usages.service', + 'kahuna.services.label', + 'kahuna.services.image-accessor', 'gr.addLabel', 'gr.archiverStatus', 'gr.syndicationIcon', @@ -30,19 +34,33 @@ image.controller('uiPreviewImageCtrl', [ '$window', 'imageService', 'imageUsagesService', + 'labelService', + 'imageAccessor', function ( $scope, inject$, $rootScope, $window, imageService, - imageUsagesService) { + imageUsagesService, + labelService, + imageAccessor) { var ctrl = this; + $scope.$watch(() => ctrl.image, (newImage) => { + ctrl.imageAsArray = [newImage]; + }); + + ctrl.addLabelToImages = labelService.batchAdd; + ctrl.removeLabelFromImages = labelService.batchRemove; + ctrl.labelAccessor = (image) => imageAccessor.readLabels(image).map(label => label.data); + ctrl.imageAsArray = [ctrl.image]; + const updateImage = (updatedImage) => { ctrl.states = imageService(updatedImage).states; ctrl.image = updatedImage; ctrl.flagState = ctrl.states.costState; + ctrl.imageAsArray = [updatedImage]; }; const freeImagesUpdateListener = $rootScope.$on('images-updated', (e, updatedImages) => { diff --git a/kahuna/public/js/services/image-accessor.js b/kahuna/public/js/services/image-accessor.js index ba833deee2..b226600da8 100644 --- a/kahuna/public/js/services/image-accessor.js +++ b/kahuna/public/js/services/image-accessor.js @@ -15,6 +15,9 @@ imageAccessor.factory('imageAccessor', function() { /* == Readers == (return data) */ + function readId(image) { + return image.data.id; + } function readCost(image) { return image.data.cost; @@ -25,6 +28,10 @@ imageAccessor.factory('imageAccessor', function() { return userMetadata.data.labels.data; } + function readPeopleInImage(image) { + return image.data.metadata.peopleInImage; + } + function readLeases(image) { return image.data.leases.data; } @@ -80,6 +87,7 @@ imageAccessor.factory('imageAccessor', function() { } return { + readId, readCost, readLabels, readLeases, @@ -87,6 +95,7 @@ imageAccessor.factory('imageAccessor', function() { readExtraInfo, readUsageRights, readPersistedReasons, + readPeopleInImage, isPersisted, isArchived, readCollections, diff --git a/kahuna/public/js/services/image-logic.js b/kahuna/public/js/services/image-logic.js index d39d1d0dbe..eebb38fe09 100644 --- a/kahuna/public/js/services/image-logic.js +++ b/kahuna/public/js/services/image-logic.js @@ -11,6 +11,10 @@ export const imageLogic = angular.module('kahuna.services.image-logic', [ */ imageLogic.factory('imageLogic', ['imageAccessor', function(imageAccessor) { + function isSameImage(image1, image2) { + return imageAccessor.readId(image1) === imageAccessor.readId(image2); + } + function canBeDeleted(image) { return image.getAction('delete').then(action => !! action); } @@ -101,6 +105,7 @@ imageLogic.factory('imageLogic', ['imageAccessor', function(imageAccessor) { } return { + isSameImage, canBeDeleted, canBeArchived, getArchivedState, diff --git a/kahuna/public/stylesheets/main.css b/kahuna/public/stylesheets/main.css index 14a2c70524..b9aaf0b9c6 100644 --- a/kahuna/public/stylesheets/main.css +++ b/kahuna/public/stylesheets/main.css @@ -1697,7 +1697,7 @@ FIXME: what to do with touch devices float: left; } -.image-info__keyword { +.image-info__keyword li { display: inline-block; background-color: #222; color: #999; @@ -1708,7 +1708,7 @@ FIXME: what to do with touch devices font-size: 1.3rem; } -.image-info__keyword a { +.image-info__keyword li a { color: inherit; } @@ -2418,7 +2418,7 @@ FIXME: what to do with touch devices .image-info__description, .image-info__special-instructions, .label__value, - .image-info__keyword { + .image-info__keyword li { color: black !important; } @@ -2439,7 +2439,7 @@ FIXME: what to do with touch devices width: auto; } - .image-info__description, .image-info__keyword { + .image-info__description, .image-info__keyword li { max-height: 80px; overflow: hidden; }