diff options
-rwxr-xr-x | bin/mrCoopDiff.js | 91 | ||||
-rw-r--r-- | www/mrPreview.html | 110 |
2 files changed, 201 insertions, 0 deletions
diff --git a/bin/mrCoopDiff.js b/bin/mrCoopDiff.js new file mode 100755 index 0000000..080aa28 --- /dev/null +++ b/bin/mrCoopDiff.js @@ -0,0 +1,91 @@ +#!/usr/bin/env node + +/** + * Takes a MapRoulette Cooperative Challenge file and creates a JSON with before and after for visualisation + */ + +const fs = require('fs') +const { Readable, Transform, pipeline } = require('stream') +const ndjson = require('ndjson') +const cloneDeep = require('clone-deep') + +const argv = require('yargs/yargs')(process.argv.slice(2)) + .argv + +if (argv._.length < 2) { + console.error("Usage: ./mrCoopDiff.js input.json output.json") + process.exit(1) +} + +const inputFile = argv._[0] +const outputFile = argv._[1] + +if (!fs.existsSync(inputFile)) { + console.error(`${inputFile} not found`) + process.exit(1) +} + +let challengeCount = 0 +const features = {} + +const applyOperations = new Transform({ + readableObjectMode: true, + writableObjectMode: true, + transform(challenge, encoding, callback) { + challengeCount++ + + if (!argv.quiet) { + if (process.stdout.isTTY && challengeCount % 1000 === 0) { + process.stdout.write(` ${challengeCount.toLocaleString()}\r`) + } + } + + for (const feature of challenge.features) { + const key = `${feature.properties['@type']}/${feature.properties['@id']}` + if (key in features) { + console.log(`${key} was found a second time`) + } + + features[key] = { + before: feature + } + } + + if (challenge && challenge.cooperativeWork && challenge.cooperativeWork.operations) { + for (const operation of challenge.cooperativeWork.operations) { + if (operation.operationType === 'modifyElement' && operation.data) { + const id = operation.data.id + if (id in features) { + const afterFeature = cloneDeep(features[id].before) + for (const featureOperation of operation.data.operations) { + if (featureOperation.operation === 'setTags') { + for (const [key, value] of Object.entries(featureOperation.data)) { + afterFeature.properties[key] = value + } + } + } + features[id].after = afterFeature + } + } + } + } + + callback() + } +}) + +console.log('Converting challenges into diff previews') +pipeline( + fs.createReadStream(inputFile), + ndjson.parse(), + applyOperations, + err => { + if (err) { + console.log(err) + process.exit(1) + } else { + fs.writeFileSync(outputFile, JSON.stringify(features)) + process.exit(0) + } + } +) diff --git a/www/mrPreview.html b/www/mrPreview.html new file mode 100644 index 0000000..8fcc3fa --- /dev/null +++ b/www/mrPreview.html @@ -0,0 +1,110 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset='utf-8' /> + <title>MapRoulette Preview</title> + <meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' /> + <style> + body { margin:0; padding:0; } + #map { position:absolute; top:0; bottom:0; width:100%; } + + tr:nth-child(odd) { + background-color: #eee + } + tr:nth-child(even) { + background-color: #ddd; + } + + .modify { + background-color: orange; + } + .add { + background-color: lightgreen; + } + .remove { + background-color: red; + } + .row { + margin: 10px; + border-width: 1px; + border-color: black; + border-style: solid; + } + </style> +</head> +<body> + <div id="content"></div> +<script> + fetch('changes.json') + .then(res => res.json()) + .then(changes => { + renderChanges(changes) + }) + function renderChanges(changes) { + const content = document.getElementById('content') + for (const [id, change] of Object.entries(changes)) { + const header = document.createElement('h2') + header.textContent = id + content.appendChild(header) + + const table = document.createElement('table') + + // table header + const headerRow = document.createElement('tr') + + const headerTag = document.createElement('th') + headerTag.textContent = 'Tag' + headerRow.appendChild(headerTag) + + const headerBefore = document.createElement('th') + headerBefore.textContent = 'Before' + headerRow.appendChild(headerBefore) + + const headerAfter = document.createElement('th') + headerAfter.textContent = 'After' + headerRow.appendChild(headerAfter) + + table.appendChild(headerRow) + + // properties + const distinctKeys = [...new Set([...Object.keys(change.before.properties), ...Object.keys(change.after.properties)])] + for (const key of distinctKeys) { + const tr = document.createElement('tr') + + const tdKey = document.createElement('td') + tdKey.textContent = key + + const tdBefore = document.createElement('td') + const beforeValue = change.before.properties[key] + tdBefore.textContent = beforeValue + + const tdAfter = document.createElement('td') + const afterValue = change.after.properties[key] + tdAfter.textContent = afterValue + + if (beforeValue && afterValue && beforeValue !== afterValue) { + tdBefore.classList.add('modify') + tdAfter.classList.add('modify') + } + + if (!beforeValue && afterValue) { + tdAfter.classList.add('add') + } + + if (beforeValue && !afterValue) { + tdAfter.classList.add('remove') + } + + tr.appendChild(tdKey) + tr.appendChild(tdBefore) + tr.appendChild(tdAfter) + + table.appendChild(tr) + } + + content.appendChild(table) + } + } +</script> +</body> +</html> |