express server !

master
km0 2 years ago
parent 533234ac68
commit d1ed945c61

1
.gitignore vendored

@ -0,0 +1 @@
node_modules

@ -1,7 +1,7 @@
# Concrete Label
# Collecting Labels
## A tool for annotating concrete and visual poetry (as well as picture, images, etc)
## A tool forked from [Concrete 🎏 Label](https://git.xpub.nl/kamo/concrete-label) for annotating visual things in a collective way
Working with NLTK for the analysis of text we noticed that the natural language toolkit lacks of something when it comes to the very materiality of the text: it ignores the layout, the shapes, the spaces as well all the other graphical information that a script may contains.
This starts from the __Selection Process / Filter / Interface__ sub group for the SP16 at XPUB 2021/2022. The group is: Jian Kimberley Supi Kamo.
Hence this is a little tool to annotate visual contents in order to be then processed by tools as NLTK
On the [wiki page](https://pzwiki.wdka.nl/mediadesign/Selection_Process_/_Filter_/_Interface) there are more infos. This is a first test of crowdsourcing annotate an image, to collect different inputs as well as different point of views.

@ -0,0 +1,68 @@
// Dependencies declaration & setup
// fs is for working with files, aka the JSON file we want to use to store our labels data
const fs = require('fs');
// express is for setting up the server and make it works when users do their things in the browser
const express = require('express');
const app = express();
const path = require('path');
// the port is setted in the etc/nginx/sites-enabled under the location /collecting-labels
const port = 3124;
// express middleware for working with JSON between the server (this) and the client (all the things in the public folder)
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
// serves the contents of the public folder as static resources
// i.e. then you can access it using https://hub.xpub.nl/soupboat/collecting-labels as root
app.use('/collecting-labels', express.static('public'))
// we dont need this now because express automatically uses the index.html file in the /public folder
// (still need to understand how to get a notification when someone connects, but maybe it is just a callbac to the app.use function of line 19whe
// app.get('/collecting-labels', function(req, res) {
// res.sendFile(path.join(__dirname, 'public', 'index.html'));
// console.log('Someone connected!')
// });
// when a post request is made to the https://hub.xpub.nl/soupboat/collecting-labels url this function starts
app.post('/collecting-labels', function(req, res) {
// with fs we read the file labels.json inside the public folder
// it is there because we then use the same file in the client for drawing all the labels stored in the server
// don't know if it is a good idea or not tho
// for now oke
// also: we need to check that the contents of the post request is what we want aka a label object, structured as we want ecc
// and not some other random thing, otherwise everything will break
fs.readFile('public/labels.json', function(err, data) {
if (err) throw err;
// parse a JSON out of the labels.json file
let storage = JSON.parse(data)
// insert the req.body (aka our label object) to the labels array present inside the labels.json file
storage.labels.push(req.body)
// overwrite the labels.json file with all the data
// this will not be efficient in the long term, but then we may switch to a proper database and not this scissor and glue thing
fs.writeFile('public/labels.json', JSON.stringify(storage), (err)=>{
if (err) throw err;
console.log('Data written to file');})
})
// reply to the client
// TODO: sayng oke everything worked nice
// for now it just echoes the incoming data
res.send(req.body)
});
// listen to the port we defined at the beginning aka wait for clients to connect there
app.listen(port, function() {
console.log(`🧶 → Collecting Labels on port ${port}!`)
});

374
package-lock.json generated

@ -0,0 +1,374 @@
{
"name": "test-express",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"accepts": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
"integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
"requires": {
"mime-types": "~2.1.24",
"negotiator": "0.6.2"
}
},
"array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
},
"body-parser": {
"version": "1.19.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
"integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
"requires": {
"bytes": "3.1.0",
"content-type": "~1.0.4",
"debug": "2.6.9",
"depd": "~1.1.2",
"http-errors": "1.7.2",
"iconv-lite": "0.4.24",
"on-finished": "~2.3.0",
"qs": "6.7.0",
"raw-body": "2.4.0",
"type-is": "~1.6.17"
}
},
"bytes": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
"integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
},
"content-disposition": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
"integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
"requires": {
"safe-buffer": "5.1.2"
}
},
"content-type": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
},
"cookie": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
"integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
},
"cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
},
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"requires": {
"ms": "2.0.0"
}
},
"depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
},
"destroy": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
},
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
},
"encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
},
"escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
},
"etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
},
"express": {
"version": "4.17.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
"integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
"requires": {
"accepts": "~1.3.7",
"array-flatten": "1.1.1",
"body-parser": "1.19.0",
"content-disposition": "0.5.3",
"content-type": "~1.0.4",
"cookie": "0.4.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "~1.1.2",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "~1.1.2",
"fresh": "0.5.2",
"merge-descriptors": "1.0.1",
"methods": "~1.1.2",
"on-finished": "~2.3.0",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.7",
"proxy-addr": "~2.0.5",
"qs": "6.7.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.1.2",
"send": "0.17.1",
"serve-static": "1.14.1",
"setprototypeof": "1.1.1",
"statuses": "~1.5.0",
"type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
}
},
"finalhandler": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
"integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
"requires": {
"debug": "2.6.9",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"on-finished": "~2.3.0",
"parseurl": "~1.3.3",
"statuses": "~1.5.0",
"unpipe": "~1.0.0"
}
},
"forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="
},
"fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
},
"http-errors": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
"integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
"requires": {
"depd": "~1.1.2",
"inherits": "2.0.3",
"setprototypeof": "1.1.1",
"statuses": ">= 1.5.0 < 2",
"toidentifier": "1.0.0"
}
},
"iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"requires": {
"safer-buffer": ">= 2.1.2 < 3"
}
},
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
"ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
},
"media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
},
"merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
},
"methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
},
"mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
},
"mime-db": {
"version": "1.51.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz",
"integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g=="
},
"mime-types": {
"version": "2.1.34",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz",
"integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==",
"requires": {
"mime-db": "1.51.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"negotiator": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
},
"on-finished": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
"integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
"requires": {
"ee-first": "1.1.1"
}
},
"parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
},
"path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
},
"proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
"requires": {
"forwarded": "0.2.0",
"ipaddr.js": "1.9.1"
}
},
"qs": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
},
"range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
},
"raw-body": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
"integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
"requires": {
"bytes": "3.1.0",
"http-errors": "1.7.2",
"iconv-lite": "0.4.24",
"unpipe": "1.0.0"
}
},
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"send": {
"version": "0.17.1",
"resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
"integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
"requires": {
"debug": "2.6.9",
"depd": "~1.1.2",
"destroy": "~1.0.4",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"fresh": "0.5.2",
"http-errors": "~1.7.2",
"mime": "1.6.0",
"ms": "2.1.1",
"on-finished": "~2.3.0",
"range-parser": "~1.2.1",
"statuses": "~1.5.0"
},
"dependencies": {
"ms": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
}
}
},
"serve-static": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
"integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
"requires": {
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
"send": "0.17.1"
}
},
"setprototypeof": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
"integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
},
"statuses": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
},
"toidentifier": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
},
"type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
"requires": {
"media-typer": "0.3.0",
"mime-types": "~2.1.24"
}
},
"unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
},
"utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
},
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
}
}
}

@ -0,0 +1,14 @@
{
"name": "test-express",
"version": "1.0.0",
"description": "Bla bla bla",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.17.1"
}
}

@ -0,0 +1,51 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="session.js" defer></script>
<script src="labels.js" defer></script>
<script src="picture.js" defer></script>
<script src="panels.js" defer></script>
<script src="text-export.js" defer></script>
<link rel="stylesheet" href="style.css" />
<title>Collecting Labels</title>
</head>
<body>
<main id="container">
<figure class="background-container">
<img id="background-image" draggable="false" src="assets/map_description_H.jpg" />
</figure>
<div id="editor"></div>
<div class="text-input">
<form class="modal">
<textarea id="input" placeholder="Describe this area" type="text"></textarea>
<button id="insert" type="submit">Insert</button>
<button id="cancel">x</button>
</form>
</div>
</main>
<nav>
<button id="show-transcription">Export</button>
<button id="show-info">?</button>
<button id="show-image">IMG</button>
<button id="load-labels">Load</button>
</nav>
<aside class="info" id="info-panel">
<button class="close">X</button>
<h1 class="title">Concrete 🎏 Label</h1>
<p>
What do we need to know: - text labels-contents - order - position - size - who
wrote it ? (random id? name? nothing at all?) - timestamp ? -
</p>
</aside>
<aside class="transcription" id="transcription-panel">
<button class="close">X</button>
<h1 class="title">Label Transcription</h1>
<ol class="labels-contents"></ol>
<button id="export-text">Export</button>
</aside>
</body>
</html>

@ -0,0 +1,261 @@
// Get the container to use as a canvas
const container = document.getElementById("container");
const editor = document.getElementById("editor");
const textForm = document.getElementsByClassName("text-input")[0];
const input = document.getElementById("input");
const insert = document.getElementById("insert");
const cancel = document.getElementById("cancel");
// List of labels
let labels = [];
let labelsObj = [];
let closing = false;
// Start is where the mouse is pressed, End is where the mouse is released
let startX;
let startY;
let endX;
let endY;
// Minimum size for the label to be created
let minimumSizeX = 25;
let minimumSizeY = 25;
// Boolean for showing the editor during drawing
let showEditor = false;
// Store the coordinates and trigger the function
container.addEventListener("mousedown", (e) => {
// Avoid inserting a new label if the user is clicking on a close button)
if (e.target.tagName !== "BUTTON" && e.target.tagName !== "INPUT") {
startX = e.x;
startY = e.y;
// activate the editor
showEditor = true;
editor.classList.add("show-editor");
}
});
container.addEventListener("mouseup", (e) => {
if (e.target.tagName !== "BUTTON" && e.target.tagName !== "INPUT") {
endX = e.x;
endY = e.y;
// disable the editor
showEditor = false;
editor.classList.remove("show-editor");
editor.style.width = 0;
editor.style.height = 0;
// draw label
drawLabel();
}
});
// Edit the editor box using transform instead of left / top in order to be more efficient
// (but still with width and height ehm idk if this affects the performance a lot)
// (and it is something we must care of because this event is called like every frame that the mouse is dragged)
container.addEventListener("mousemove", (e) => {
if (showEditor) {
let minX = Math.min(startX, e.x);
let minY = Math.min(startY, e.y);
let maxX = Math.max(startX, e.x);
let maxY = Math.max(startY, e.y);
let width = maxX - minX;
let height = maxY - minY;
// Apply a different class when the sizes pass the minimum size
// (i don't know if is good made like this)
if (width > minimumSizeX && height > minimumSizeY) {
editor.classList.add("can-draw");
} else {
editor.classList.remove("can-draw");
}
editor.style.transform = `translate(${minX}px, ${minY}px)`;
editor.style.width = `${maxX - minX}px`;
editor.style.height = `${maxY - minY}px`;
}
});
// Check the mouse direction and create the Label
// The origin points of the label (because is positioned with top left) are always the lowest x and y values
// The width and height are the greater x and y values (because width and height cannot be negative)
function drawLabel() {
let minX = Math.min(startX, endX);
let minY = Math.min(startY, endY);
let maxX = Math.max(startX, endX);
let maxY = Math.max(startY, endY);
let width = maxX - minX;
let height = maxY - minY;
if (width > minimumSizeX && height > minimumSizeY) {
// Create a label and push it into the array of labels
let temporaryLabel = createLabel(minX, minY, width, height, labels.length);
temporaryLabel.classList.add("temporary");
temporaryLabel.id = "temporary-label";
container.appendChild(temporaryLabel);
new Promise(function (resolve, reject) {
// then if the user click insert and there is a value in the input-- > resolve the promise and return the text input to create the label,
// if the user click cancel-- > reject the promise and don't create the label
textForm.classList.add("visible");
input.focus();
// Insert button
insert.addEventListener("click", (e) => {
e.preventDefault();
if (input.value) {
resolve();
}
});
// Cancel button
cancel.addEventListener("click", (e) => {
e.preventDefault();
textForm.classList.remove("visible");
reject("no input");
});
}).then(() => {
// Create the label
let label = createLabel(minX, minY, width, height, labels.length);
// Add the text input to the label
let text = document.createElement("p");
text.classList.add("label--text");
text.innerHTML = input.value;
label.appendChild(text);
let labelObj = {
position: {
x: minX,
y: minY,
},
size: {
width: width,
height: height,
},
index: labels.length,
text: input.value,
timestap: Date.now(),
userID: userID,
};
uploadLabel(labelObj);
labelsObj.push(labelObj);
labels.push(label);
container.appendChild(label);
createLabelTranscription(label);
// Reset the modal
input.value = "";
input.blur();
let tempLabel = document.getElementById("temporary-label");
container.removeChild(tempLabel);
textForm.classList.remove("visible");
});
}
}
// Create the label element
function createLabel(x, y, width, height, index) {
let label = document.createElement("div");
label.classList.add("label");
label.style.left = `${x}px`;
label.style.top = `${y}px`;
label.style.width = `${width}px`;
label.style.height = `${height}px`;
// data attribute index maybe we will need it later maybe not
// with the index number of the label
label.setAttribute("data-index", index);
// Insert the number in the label
let labelNumber = document.createElement("p");
labelNumber.classList.add("label--number");
labelNumber.innerHTML = index + 1;
label.appendChild(labelNumber);
// Add a button for deleting the label
// TODO: reactive numbering oh no
let close = document.createElement("button");
close.classList.add("label--close");
close.innerHTML = "x";
close.addEventListener("click", (e) => {
label.remove();
});
label.appendChild(close);
return label;
}
const transcriptionPanel = document.getElementById("transcription-panel");
const transcriptionList = transcriptionPanel.querySelector("ol");
function createLabelTranscription(label) {
let transcription = document.createElement("li");
transcription.innerHTML = label.querySelector(".label--text").innerHTML;
transcriptionList.appendChild(transcription);
}
function uploadLabel(obj){
fetch('https://hub.xpub.nl/soupboat/collecting-labels/', {
method: 'POST', // or 'PUT'
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(obj),
})
.then(response => response.json())
.then(data => {
console.log('Success:', data);
})
.catch((error) => {
console.error('Error:', error);
});
}
const load = document.getElementById('load-labels')
load.addEventListener('click', (e)=>{
loadLabels()
})
function loadLabels(){
fetch("./labels.json")
.then((response) => {
return response.json();
})
.then((data) => {
data.labels.forEach((label, index) => {
let labelElement = createLabel(label.position.x, label.position.y, label.size.width, label.size.height, index)
// THIS IS TEMPORARY
labelElement.style.backgroundColor = `hsla(${Math.floor(label.userID/10000000000*255)}, 100%, 75%, 0.2)`
// Add the text input to the label
let text = document.createElement("p");
text.classList.add("label--text");
text.innerHTML = label.text;
labelElement.appendChild(text);
labelElement.classList.add('loaded')
container.appendChild(labelElement)
});
})
}

@ -0,0 +1,270 @@
html,
body {
margin: 0;
font-family: Arial, Helvetica, sans-serif;
width: 100%;
overflow: hidden;
}
.test-form{
position: fixed;
top: 0;
left: 0;
z-index: 500;
}
#container {
width: 100%;
height: 100vh;
background-color: #fff;
}
#editor {
position: absolute;
display: none;
border: 1px solid white;
opacity: 0.5;
width: 0;
height: 0;
z-index: 100;
}
#editor.can-draw {
opacity: 1;
}
#editor.show-editor {
display: block;
}
.label {
position: absolute;
background-color: rgba(250, 99, 72, 0.2);
/* border: 1px solid currentColor; */
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.2);
overflow: hidden;
}
.label.temporary {
background: none;
border: 1px dashed tomato;
box-shadow: none;
}
.label.temporary .label--number,
.label.temporary .label--close {
display: none;
}
.label--number {
display: inline-block;
margin: 0;
padding: 0 4px;
user-select: none;
background-color: white;
}
.label--close {
position: absolute;
right: 0;
border: none;
padding: 0 4px;
font-size: 1rem;
background: none;
color: white;
cursor: pointer;
}
.label--text {
margin: 1ch 0;
padding: 0 1ch;
overflow: hidden;
width: 100%;
height: 100%;
text-overflow: ellipsis;
white-space: pre;
}
.text-input {
display: none;
position: absolute;
z-index: 200;
width: 100%;
height: 100vh;
justify-content: center;
align-items: center;
background-color: rgba(255, 99, 71, 0.95);
}
.text-input.visible {
display: flex;
}
.modal {
padding: 64px;
}
.modal input {
font-size: 1.5rem;
background: none;
border: none;
color: white;
border-bottom: 1px solid white;
}
.modal input:focus {
outline: none;
background-color: rgba(255, 255, 255, 0.25);
}
.text-input button {
color: white;
font-weight: bold;
background: none;
border: none;
cursor: pointer;
font-size: 1.5rem;
}
#cancel {
font-weight: normal;
}
.background-container {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
}
.background-container img {
max-width: 70w;
max-height: 70vh;
object-fit: contain;
user-select: none;
pointer-events: none;
}
.background-container img.visible {
display: initial;
}
.hidden {
display: none;
}
.info,
.transcription {
position: absolute;
right: 0;
bottom: 0;
top: 0;
z-index: 50;
padding: 24px;
margin: 0;
width: 25%;
line-height: 1.6;
background-color: #111;
color: white;
transform: translateX(100%);
transition: transform 0.4s ease-out;
}
.transcription.active,
.info.active {
transform: translateX(0);
transition: transform 0.6s ease-in;
}
.transcription .title,
.info .title {
margin: 0;
}
.transcription ol {
padding: 0;
list-style-position: inside;
font-size: 1.125rem;
}
#show-info,
#show-transcription,
.close,
button {
background: none;
display: inline-block;
min-width: 24px;
height: 24px;
border-radius: 24px;
padding: 0 4px;
border: 1px solid currentColor;
color: tomato;
cursor: pointer;
}
#show-transcription:hover,
#show-info:hover {
border: 1px solid tomato;
background-color: tomato;
color: white;
}
.close {
position: absolute;
right: 24px;
top: 32px;
color: white;
}
#export-text:hover,
.close:hover {
border: 1px solid white;
background-color: white;
color: #111;
}
#export-text {
color: white;
}
nav {
position: absolute;
top: 0;
left: 0;
right: 0;
z-index: 50;
padding: 24px;
text-align: right;
pointer-events: none;
}
nav > * {
pointer-events: all;
}
img.hidden {
display: none;
}

Before

Width:  |  Height:  |  Size: 227 KiB

After

Width:  |  Height:  |  Size: 227 KiB

Before

Width:  |  Height:  |  Size: 224 KiB

After

Width:  |  Height:  |  Size: 224 KiB

@ -11,12 +11,12 @@
<script src="text-export.js" defer></script>
<link rel="stylesheet" href="style.css" />
<title>Concrete Label</title>
<title>Collecting Labels</title>
</head>
<body>
<main id="container">
<figure class="background-container">
<img id="background-image" draggable="false" src="./assets/map_description_H.jpg" />
<img id="background-image" draggable="false" src="assets/map_description_H.jpg" />
</figure>
<div id="editor"></div>
<div class="text-input">
@ -31,6 +31,7 @@
<button id="show-transcription">Export</button>
<button id="show-info">?</button>
<button id="show-image">IMG</button>
<button id="load-labels">Load</button>
</nav>
<aside class="info" id="info-panel">
<button class="close">X</button>

@ -149,8 +149,7 @@ function drawLabel() {
userID: userID,
};
console.log(labelObj);
uploadLabel(labelObj);
labelsObj.push(labelObj);
labels.push(label);
container.appendChild(label);
@ -207,3 +206,56 @@ function createLabelTranscription(label) {
transcriptionList.appendChild(transcription);
}
function uploadLabel(obj){
fetch('https://hub.xpub.nl/soupboat/collecting-labels/', {
method: 'POST', // or 'PUT'
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(obj),
})
.then(response => response.json())
.then(data => {
console.log('Success:', data);
})
.catch((error) => {
console.error('Error:', error);
});
}
const load = document.getElementById('load-labels')
load.addEventListener('click', (e)=>{
loadLabels()
})
function loadLabels(){
fetch("./labels.json")
.then((response) => {
return response.json();
})
.then((data) => {
data.labels.forEach((label, index) => {
let labelElement = createLabel(label.position.x, label.position.y, label.size.width, label.size.height, index)
// THIS IS TEMPORARY
labelElement.style.backgroundColor = `hsla(${Math.floor(label.userID/10000000000*255)}, 100%, 75%, 0.2)`
// Add the text input to the label
let text = document.createElement("p");
text.classList.add("label--text");
text.innerHTML = label.text;
labelElement.appendChild(text);
labelElement.classList.add('loaded')
container.appendChild(labelElement)
});
})
}

@ -0,0 +1 @@
{"labels":[{"position":{"x":245,"y":269},"size":{"width":127,"height":77},"index":0,"text":"Bonjur","timestap":1636505364359,"userID":5917242946},{"position":{"x":475,"y":419},"size":{"width":168,"height":171},"index":1,"text":"Hello","timestap":1636505369697,"userID":5917242946},{"position":{"x":334,"y":543},"size":{"width":270,"height":133},"index":2,"text":"Sa va","timestap":1636505373974,"userID":5917242946},{"position":{"x":531,"y":215},"size":{"width":219,"height":262},"index":3,"text":"Salut sa va bonsur","timestap":1636505396534,"userID":5917242946},{"position":{"x":219,"y":622},"size":{"width":480,"height":104},"index":4,"text":"Sur sur sur sur sur","timestap":1636505403564,"userID":5917242946},{"position":{"x":109,"y":228},"size":{"width":140,"height":138},"index":0,"text":"Ciao","timestap":1636505537559,"userID":4929690076},{"position":{"x":373,"y":419},"size":{"width":438,"height":260},"index":1,"text":"Buongiorno come va","timestap":1636505542934,"userID":4929690076},{"position":{"x":118,"y":106},"size":{"width":328,"height":242},"index":0,"text":"Danke","timestap":1636505672560,"userID":989803860},{"position":{"x":183,"y":221},"size":{"width":243,"height":84},"index":1,"text":"Hello sa va bonsur","timestap":1636505719337,"userID":989803860},{"position":{"x":353,"y":392},"size":{"width":330,"height":288},"index":2,"text":"Sur sur sur sur sur","timestap":1636505724397,"userID":989803860},{"position":{"x":572,"y":200},"size":{"width":251,"height":117},"index":3,"text":"Hellooooo","timestap":1636505729749,"userID":989803860},{"position":{"x":694,"y":335},"size":{"width":113,"height":101},"index":4,"text":"Molto bene dai","timestap":1636505734953,"userID":989803860},{"position":{"x":61,"y":225},"size":{"width":62,"height":53},"index":0,"text":"chair","timestap":1636506949742,"userID":6931972952},{"position":{"x":287,"y":345},"size":{"width":72,"height":63},"index":1,"text":"Chair","timestap":1636506956730,"userID":6931972952},{"position":{"x":71,"y":447},"size":{"width":148,"height":160},"index":0,"text":"Hohohoho","timestap":1636508092129,"userID":6082067129},{"position":{"x":136,"y":710},"size":{"width":259,"height":113},"index":1,"text":"Molto bene ora vediamo","timestap":1636508098344,"userID":6082067129},{"position":{"x":513,"y":81},"size":{"width":139,"height":107},"index":2,"text":"Etc etc etc","timestap":1636508103961,"userID":6082067129},{"position":{"x":184,"y":397},"size":{"width":150,"height":153},"index":0,"text":"eheheh","timestap":1636508196663,"userID":3863434946},{"position":{"x":1033,"y":219},"size":{"width":302,"height":109},"index":0,"text":"nooooo","timestap":1636508284500,"userID":272153411},{"position":{"x":1284,"y":332},"size":{"width":108,"height":159},"index":1,"text":"oke","timestap":1636508287570,"userID":272153411},{"position":{"x":979,"y":427},"size":{"width":237,"height":266},"index":2,"text":"for now it works like this","timestap":1636508293279,"userID":272153411},{"position":{"x":1315,"y":553},"size":{"width":254,"height":123},"index":3,"text":"need to fix the positioning tho aha oke now goodnight","timestap":1636508304727,"userID":272153411},{"position":{"x":542,"y":235},"size":{"width":113,"height":43},"index":0,"text":"wiw","timestap":1636508346975,"userID":4366086302}]}

@ -0,0 +1 @@
const userID = Math.round(Math.random() * 10000000000);

@ -6,10 +6,18 @@ body {
overflow: hidden;
}
.test-form{
position: fixed;
top: 0;
left: 0;
z-index: 500;
}
#container {
width: 100%;
height: 100vh;
background-color: #fcc;
background-color: #fff;
}
#editor {
Loading…
Cancel
Save