diff --git a/public_html/locale/en.json b/public_html/locale/en.json index ac61480..35a5da5 100644 --- a/public_html/locale/en.json +++ b/public_html/locale/en.json @@ -21,7 +21,7 @@ "thumb-dimensions": "Thumbnail: {{width}}\u00d7{{height}} px.", "crop-dimensions": "Crop: {{width}}\u00d7{{height}} px.", "cropped-dimensions": "Cropped: {{width}}\u00d7{{height}} px.", - "cropform-select-region": "Select a crop region by click-and-drag, or try the magic border locator<\/a>.", + "cropform-select-region": "Select a crop region by click-and-drag, or try the magic border locator<\/a>, or select a structured data region<\/a>.", "left": "Left", "top": "Top", "width": "Width", diff --git a/src/index.html b/src/index.html index 722cf1b..62dbdc5 100644 --- a/src/index.html +++ b/src/index.html @@ -267,7 +267,7 @@

- cropform-select-region + cropform-select-region

diff --git a/src/js/app.js b/src/js/app.js index 6b65048..45c355b 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -13,7 +13,7 @@ config(['$translateProvider', function($translateProvider) { $translateProvider.preferredLanguage('en'); }]). -service('LoginService', ['$http', '$rootScope', function($http, $rootScope) { +service('LoginService', ['$http', '$rootScope', '$q', function($http, $rootScope, $q) { // console.log('Init LoginService'); @@ -349,6 +349,135 @@ controller('AppCtrl', ['$scope', '$http', '$timeout', '$q', '$window', '$httpPar }); }; + $scope.loadStructuredDataRegions = function() { + // load page ID + $http.get('https://' + $scope.currentUrlParams.site + '/w/api.php?' + $httpParamSerializer({ + action: 'query', + titles: 'File:' + $scope.currentUrlParams.title, + format: 'json', + formatversion: 2, + origin: '*' + })) // TODO perhaps we can already get the page ID in fetchImage()? + .then(function(res) { + // load entity + var pageId = res.data.query.pages[0].pageid; + var entityId = 'M' + pageId; + return $http.get('https://' + $scope.currentUrlParams.site + '/w/api.php?' + $httpParamSerializer({ + action: 'wbgetentities', + ids: entityId, + props: 'claims', + format: 'json', + formatversion: 2, + origin: '*' + })).then(function(res) { + return res.data.entities[entityId]; + }); + }) + .then(function(entity) { + // find "depicts" statements with region qualifiers + var depictsStatements = (entity.statements || {})['P180'] || []; + var depictsStatementsHavingRegionQualifiers = depictsStatements.filter(function(statement) { + if (statement.mainsnak.snaktype !== 'value') { + return false; + } + var regionQualifiers = (statement.qualifiers || {})['P2677'] || []; + if (regionQualifiers.length !== 1) { + return false; + } + var regionQualifier = regionQualifiers[0]; + if (regionQualifier.snaktype !== 'value') { + return false; + } + var region = regionQualifier.datavalue.value; + if (!/^pct:(?:(?:100|[1-9]?\d(?:\.\d+)?),){3}(?:100|[1-9]?\d(?:\.\d+)?)$/.test(region)) { + return false; + } + return true; + }); + var depictedItemIdsWithRegions = depictsStatementsHavingRegionQualifiers.map(function(statement) { + return [statement.mainsnak.datavalue.value.id, statement.qualifiers['P2677'][0].datavalue.value]; + }); + // load labels of the values + var itemIds = depictedItemIdsWithRegions.map(function(pair) { + return pair[0]; + }); + itemIds = itemIds.filter(function(itemId, index) { + return itemIds.indexOf(itemId) === index; + }); + var itemLabelsDeferred = $q.defer(); + itemLabelsDeferred.resolve({}); + var itemLabelsPromise = itemLabelsDeferred.promise; + var itemIdsChunk; + var language = 'en'; // TODO which language? + while ((itemIdsChunk = itemIds.splice(0, 50)).length > 0) { // we can get up to 50 entities at once + (function(ids) { // IIFE ensures that the below function always sees the correct itemIdsChunk, not the last (empty) one + itemLabelsPromise = itemLabelsPromise.then(function(labels) { + return $http.get('https://www.wikidata.org/w/api.php?' + $httpParamSerializer({ + action: 'wbgetentities', + ids: ids, + props: 'labels', + languages: language, + languagefallback: 1, + format: 'json', + formatversion: 2, + origin: '*' + })) + .then(function(res) { + for (var itemId in res.data.entities) { + try { + labels[itemId] = res.data.entities[itemId].labels[language].value; + } catch (e) { // entity missing, or no label in this language or any fallback language + labels[itemId] = itemId; + } + } + return labels; + }); + }); + }(itemIdsChunk.join('|'))); + } + // return regions with labels + return itemLabelsPromise.then(function(labels) { + return depictedItemIdsWithRegions.map(function(pair) { + var itemId = pair[0]; + var region = pair[1]; + return [itemId, region, labels[itemId]]; + }); + }); + }) + .then(function(triples) { + if (triples.length === 0) { + alert('No structured data regions available.'); // TODO better alert + return; + } + // select one of the regions + var message = 'Which region?'; + for (var i in triples) { + message += '\n' + i + ': ' + triples[i][2]; + } + var tripleIndex = prompt(message); // TODO better prompt + if (tripleIndex === null) { + return; // user selected "cancel" + } + if (!(tripleIndex in triples)) { + throw new Error('Invalid index ' + tripleIndex); + } + // apply it + var triple = triples[tripleIndex]; + var itemId = triple[0]; + var region = triple[1]; + var label = triple[2]; + var match = region.match(/^pct:(100|[1-9]?\d(?:\.\d+)?),(100|[1-9]?\d(?:\.\d+)?),(100|[1-9]?\d(?:\.\d+)?),(100|[1-9]?\d(?:\.\d+)?)$/); + $scope.$broadcast('crop-input-changed', { + left: parseFloat(match[1]) * $scope.metadata.original.width / (100 * pixelratio[0]), + top: parseFloat(match[2]) * $scope.metadata.original.height / (100 * pixelratio[1]), + width: parseFloat(match[3]) * $scope.metadata.original.width / (100 * pixelratio[0]), + height: parseFloat(match[4]) * $scope.metadata.original.height / (100 * pixelratio[1]) + }); + // TODO add a prominent "depicts" statement for itemId to the cropped image? + }) + .catch(console.error); // TODO better error handling + }; + $scope.cropMethodChanged = function() { LocalStorageService.set('croptool-cropmethod', $scope.cropmethod); while ($scope.rotation.angle < 0) {