diff --git a/.eslintrc.js b/.eslintrc.js
index ae235cb..387f733 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -1,7 +1,7 @@
module.exports = {
"env": {
"browser": true,
- "es2017": true,
+ "es2018": true,
"node": true,
"mocha": true
},
diff --git a/app/app.js b/app/app.js
index 4088f49..84ab8e3 100644
--- a/app/app.js
+++ b/app/app.js
@@ -97,6 +97,21 @@ const start = async function () {
});
+ // CORS
+ app.use(function (req, res, next) {
+ // Website you wish to allow to connect
+ res.setHeader('Access-Control-Allow-Origin', '*'); // 'http://localhost:8888');
+ // Request methods you wish to allow
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
+ // Request headers you wish to allow
+ res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
+ // Set to true if you need the website to include cookies in the requests sent
+ // to the API (e.g. in case you use sessions)
+ res.setHeader('Access-Control-Allow-Credentials', true);
+ // Pass to next layer of middleware
+ next();
+ });
+
// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
if (process.env.NODE_ENV !== 'production') {
@@ -106,7 +121,6 @@ const start = async function () {
app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));
app.use(express.static(path.join(__dirname, 'public')));
-
/* setup DB */
// var checkAnyoneUser = function () {
diff --git a/app/controller/api/api.spec.js b/app/controller/api/api.spec.js
index 426bef6..826b163 100644
--- a/app/controller/api/api.spec.js
+++ b/app/controller/api/api.spec.js
@@ -1,606 +1,602 @@
-const assert = require('assert')
-const chai = require('chai')
+/* eslint-disable max-lines */
+// disable eslint rule no-unused-expressions to work with chai
+/* eslint-disable no-unused-expressions */
+const assert = require('assert');
+const chai = require('chai');
const bodyParser = require('body-parser');
-const chaiHttp = require('chai-http')
-const expect = chai.expect
-const sinon = require('sinon')
-const fs = require('fs')
+const chaiHttp = require('chai-http');
+const {expect} = chai;
+const sinon = require('sinon');
+const fs = require('fs');
-const express = require('express')
-const app = express()
+const express = require('express');
+const app = express();
-function buildFileID({source, slice}) {
- return `${source}&slice=${slice}`;
-}
-app.use(bodyParser.urlencoded({ extended: true }))
+app.use(bodyParser.urlencoded({ extended: true }));
/**
* simulate authenication status
* testing authentication is not the aim of this test suite
* these tests only check if api works as intended
*/
-let authenticatedUser = null
+let authenticatedUser = null;
const USER_ANONYMOUSE = {
- username: 'anyone'
-}
+ username: 'anyone'
+};
const USER_BOB = {
- username: 'bob'
-}
+ username: 'bob'
+};
const USER_ALICE = {
- username: 'alice'
-}
-const USER_CINDY = {
- username: 'cindy'
-}
+ username: 'alice'
+};
+// const USER_CINDY = {
+// username: 'cindy'
+// };
app.use((req, res, next) => {
- if (authenticatedUser) {
- req.user = authenticatedUser
- }
- next()
-})
-app.use(require('./index'))
+ if (authenticatedUser) {
+ req.user = authenticatedUser;
+ }
+ next();
+});
+app.use(require('./index'));
-chai.use(chaiHttp)
+chai.use(chaiHttp);
describe('Mocha works', () => {
- it('mocha works in api.spec.js', () => {
- assert.strictEqual(1, 1)
- })
-})
+ it('mocha works in api.spec.js', () => {
+ assert.strictEqual(1, 1);
+ });
+});
describe('sinon works', () => {
- it('fake called works', () => {
- const fake = sinon.fake()
- expect(fake.called).to.be.false
- fake()
- expect(fake.called).to.be.true
- })
-
- it('fake calls with arguements works', () => {
- const fake = sinon.fake()
- const arg = {
- hello: 'world'
- }
- fake(arg)
- assert(fake.calledWith({
- hello: 'world'
- }))
- })
-
- it('stub can dynamically change return val', () => {
- const stub = sinon.stub()
- let flag = true
- stub.callsFake(() => flag)
- expect(stub()).to.equal(true)
-
- flag = false
- expect(stub()).to.equal(false)
- })
-})
+ it('fake called works', () => {
+ const fake = sinon.fake();
+ expect(fake.called).to.be.false;
+ fake();
+ expect(fake.called).to.be.true;
+ });
+
+ it('fake calls with arguements works', () => {
+ const fake = sinon.fake();
+ const arg = {
+ hello: 'world'
+ };
+ fake(arg);
+ assert(fake.calledWith({
+ hello: 'world'
+ }));
+ });
+
+ it('stub can dynamically change return val', () => {
+ const stub = sinon.stub();
+ let flag = true;
+ stub.callsFake(() => flag);
+ expect(stub()).to.equal(true);
+
+ flag = false;
+ expect(stub()).to.equal(false);
+ });
+});
describe('controller/api/index.js', () => {
- const annotationInDb = {
- Regions: [{annotation: {uid: 'abc'}}, {annotation: {uid: 'cde'}}]
- }
- let _server,
- queryPublicProject = false,
- publicProject = {
- owner: USER_BOB.username,
- collaborators: {
- list: [ USER_ALICE, USER_BOB, USER_ANONYMOUSE ]
- }
- },
- privateProject = {
- owner: USER_ALICE.username,
- collaborators: {
- list: [ USER_ALICE, USER_BOB ]
- }
- },
- port = 10002,
- url = `http://127.0.0.1:${port}`,
-
- returnFoundAnnotation = true,
- updateAnnotations = sinon.stub().resolves(),
- insertAnnotations = sinon.stub().resolves(),
- findAnnotations = sinon.stub().callsFake(() => Promise.resolve( returnFoundAnnotation ? annotationInDb : {Regions: []} )),
- queryProject = sinon.stub().callsFake(() => Promise.resolve(queryPublicProject ? publicProject : privateProject))
-
- before(() => {
- app.db = {
- updateAnnotations,
- insertAnnotations,
- findAnnotations,
- queryProject
- }
- _server = app.listen(port, () => console.log(`mocha test listening at ${port}`))
- })
-
- beforeEach(() => {
- updateAnnotations.resetHistory()
- insertAnnotations.resetHistory()
- findAnnotations.resetHistory()
- queryProject.resetHistory()
- })
-
- after(() => {
- _server.close()
- })
-
- describe('saveFromGUI', () => {
-
- describe('/GET', () => {
-
- describe('public project', () => {
- before(() => {
- queryPublicProject = true
- })
-
- it('if user not part of project, should return 200', (done) => {
- authenticatedUser = null
- chai.request(url)
- .get('/')
- .query({
- project: 'testProject'
- })
- .end((err, res) => {
- expect(!!err).to.be.false
- expect(res).to.have.status(200)
- done()
- })
- })
-
- it('if user is part of project, should return status 200', (done) => {
- authenticatedUser = USER_BOB
- chai.request(url)
- .get('/')
- .query({
- project: 'testProject'
- })
- .end((err, res) => {
- expect(!!err).to.be.false
- expect(res).to.have.status(200)
- done()
- })
- })
+ const annotationInDb = [{annotation: {uid: 'abc'}}, {annotation: {uid: 'cde'}}];
+ let _server,
+ queryPublicProject = false;
+ const returnFoundAnnotation = true;
+ const findAnnotations = sinon.stub().callsFake(() => Promise.resolve(returnFoundAnnotation ? annotationInDb : [])),
+ insertAnnotations = sinon.stub().resolves(),
+ port = 10002,
+ privateProject = {
+ owner: USER_ALICE.username,
+ collaborators: {
+ list: [USER_ALICE, USER_BOB]
+ }
+ },
+ publicProject = {
+ owner: USER_BOB.username,
+ collaborators: {
+ list: [USER_ALICE, USER_BOB, USER_ANONYMOUSE]
+ }
+ },
+ queryProject = sinon.stub().callsFake(() => Promise.resolve(queryPublicProject ? publicProject : privateProject)),
+ updateAnnotations = sinon.stub().resolves(),
+ url = `http://127.0.0.1:${port}`;
+
+ before(() => {
+ app.db = {
+ updateAnnotations,
+ insertAnnotations,
+ findAnnotations,
+ queryProject
+ };
+ _server = app.listen(port, () => console.log(`mocha test listening at ${port}`));
+ });
+
+ beforeEach(() => {
+ updateAnnotations.resetHistory();
+ insertAnnotations.resetHistory();
+ findAnnotations.resetHistory();
+ queryProject.resetHistory();
+ });
+
+ after(() => {
+ _server.close();
+ });
+
+ describe('saveFromGUI', () => {
+
+ describe('/GET', () => {
+
+ describe('public project', () => {
+ before(() => {
+ queryPublicProject = true;
+ });
+
+ it('if user not part of project, should return 200', (done) => {
+ authenticatedUser = null;
+ chai.request(url)
+ .get('/')
+ .query({
+ project: 'testProject'
})
-
- describe('private project', () => {
-
- before(() => {
- queryPublicProject = false
- })
-
- it('if user not part of project, should return 403', (done) => {
- authenticatedUser = null
- chai.request(url)
- .get('/')
- .query({
- project: 'testProject'
- })
- .end((err, res) => {
- expect(!!err).to.be.false
- expect(res).to.have.status(403)
- done()
- })
- })
-
- it('if user is part of project, should return status 200', (done) => {
- authenticatedUser = USER_BOB
- chai.request(url)
- .get('/')
- .query({
- project: 'testProject'
- })
- .end((err, res) => {
- expect(!!err).to.be.false
- expect(res).to.have.status(200)
- done()
- })
- })
+ .end((err, res) => {
+ expect(Boolean(err)).to.be.false;
+ expect(res).to.have.status(200);
+ done();
+ });
+ });
+
+ it('if user is part of project, should return status 200', (done) => {
+ authenticatedUser = USER_BOB;
+ chai.request(url)
+ .get('/')
+ .query({
+ project: 'testProject'
})
+ .end((err, res) => {
+ expect(Boolean(err)).to.be.false;
+ expect(res).to.have.status(200);
+ done();
+ });
+ });
+ });
- describe('fetching annotation from different project querys project as expected', () => {
- const getTest = project => chai.request(url)
- .get('/')
- .query(
- project ? ({ project }) : ({})
- )
- .then((res) => {
+ describe('private project', () => {
- /**
+ before(() => {
+ queryPublicProject = false;
+ });
+
+ it('if user not part of project, should return 403', (done) => {
+ authenticatedUser = null;
+ chai.request(url)
+ .get('/')
+ .query({
+ project: 'testProject'
+ })
+ .end((err, res) => {
+ expect(Boolean(err)).to.be.false;
+ expect(res).to.have.status(403);
+ done();
+ });
+ });
+
+ it('if user is part of project, should return status 200', (done) => {
+ authenticatedUser = USER_BOB;
+ chai.request(url)
+ .get('/')
+ .query({
+ project: 'testProject'
+ })
+ .end((err, res) => {
+ expect(Boolean(err)).to.be.false;
+ expect(res).to.have.status(200);
+ done();
+ });
+ });
+ });
+
+ describe('fetching annotation from different project querys project as expected', () => {
+ const getTest = (project) => chai.request(url)
+ .get('/')
+ .query(
+ project ? ({ project }) : ({})
+ )
+ .then(() => {
+
+ /**
* queryProject will only be called if user is querying annotation from a specific project
* then, their authorisation will be assessed
*/
- if (project) {
- assert(queryProject.called)
+ if (project) {
+ assert(queryProject.called);
- /**
+ /**
* if project not provided, as if project is an empty string
*/
- assert(queryProject.calledWith({ shortname: project }))
- } else {
- /**
+ assert(queryProject.calledWith({ shortname: project }));
+ } else {
+
+ /**
* if user is querying public (default) work space, query project would not be called
*/
- assert(queryProject.notCalled)
- }
- })
-
- for (const p of ['', 'test1', 'test2', null]){
- it(`fetching project: "${p}"`, done => {
-
- getTest(p)
- .then(() => done())
- .catch(done)
- })
- }
- })
- })
-
- describe('/POST', () => {
- describe('findAnnotation', () => {
- it('should be called with correct arg', done => {
-
- const project = 'projectIsAlreadyTestedAbove'
- const getTest = ({ source, slice, project }) => chai.request(url)
- .get('/')
- .query({
- source,
- slice,
- project
- })
- .then(res => {
- findAnnotations.callArg
- assert(findAnnotations.calledWith({
- fileID: `${source}&slice=${slice}`,
- project
- }))
- })
-
- Promise.all([
- getTest({ source: 'test1', slice: '123', project }),
- getTest({ source: 'test2', slice: '1234', project }),
- getTest({ source: '', slice: '', project }),
- chai.request(url)
- .get('/')
- .query({
- project
- })
- .then((res) => {
- assert(findAnnotations.calledWith({
- fileID: `undefined&slice=undefined`,
- project
- }))
- })
- ]).then(() => done())
- .catch(done)
- })
- })
-
- describe('varying users', () => {
- const sendItem = {
- action: 'save',
- source: '/path/to/json.json',
- slice: 24,
- Hash: 'testworld',
- annotation: '{"Regions": [], "RegionsToRemove": []}',
- project: 'alreadyTestedPreviously'
- }
- const {
- action,
- source,
- slice,
- ...rest
- } = sendItem
- let res
-
- beforeEach(async () => {
- res = await chai.request(url)
- .post('/')
- .set('content-type', 'application/x-www-form-urlencoded')
- .send(sendItem)
- })
-
- it('ok', () => {
- assert(true)
- })
-
- describe('unauthenticated user', () => {
- before(() => {
- authenticatedUser = null
- })
-
- it('should return 403', () => {
- const { status } = res
- expect(status).to.equal(403)
- })
- })
-
- describe('authenicated user, with correct permission', () => {
- before(() => {
- authenticatedUser = USER_BOB
- })
-
- it('should return 200', () => {
- const { status } = res
- expect(status).to.equal(200)
- })
-
- it('updateAnnotations should be called', () => {
- assert(updateAnnotations.called)
- })
-
- it('insertAnnotations should be called', () => {
- assert(insertAnnotations.called)
- })
-
- // it('updateAnnotation called with correct arg', () => {
- // assert(updateAnnotations.calledWith({
- // fileID: '/path/to/json.json&slice=24',
- // user: USER_BOB.username,
- // ...rest
- // }))
- // })
- })
- })
- })
- })
+ assert(queryProject.notCalled);
+ }
+ });
- describe('saveFromAPI', () => {
+ for (const p of ['', 'test1', 'test2', null]) {
+ it(`fetching project: "${p}"`, (done) => {
- let FILENAME1 = `FILENAME1.json`
- let FILENAME2 = `FILENAME2.json`
- const correctJson = [
+ getTest(p)
+ .then(() => done())
+ .catch(done);
+ });
+ }
+ });
+ });
+
+ describe('/POST', () => {
+ describe('findAnnotation', () => {
+ it('should be called with correct arg', (done) => {
+
+ const projectName = 'projectIsAlreadyTestedAbove';
+ const getTest = ({ source, slice, project }) => chai.request(url)
+ .get('/')
+ .query({
+ source,
+ slice,
+ project
+ })
+ .then(() => {
+ findAnnotations.callArg;
+ assert(findAnnotations.calledWith({
+ fileID: `${source}&slice=${slice}`,
+ project
+ }));
+ });
+
+ Promise.all([
+ getTest({ source: 'test1', slice: '123', project: projectName }),
+ getTest({ source: 'test2', slice: '1234', project: projectName }),
+ getTest({ source: '', slice: '', project: projectName }),
+ chai.request(url)
+ .get('/')
+ .query({
+ project: projectName
+ })
+ .then(() => {
+ assert(findAnnotations.calledWith({
+ fileID: `undefined&slice=undefined`,
+ project: projectName
+ }));
+ })
+ ]).then(() => done())
+ .catch(done);
+ });
+ });
+
+ describe('varying users', () => {
+ const sendItem = {
+ action: 'save',
+ source: '/path/to/json.json',
+ slice: 24,
+ Hash: 'testworld',
+ annotation: '{"Regions": [], "RegionsToRemove": []}',
+ project: 'alreadyTestedPreviously'
+ };
+ let res;
+
+ beforeEach(async () => {
+ res = await chai.request(url)
+ .post('/')
+ .set('content-type', 'application/x-www-form-urlencoded')
+ .send(sendItem);
+ });
+
+ it('ok', () => {
+ assert(true);
+ });
+
+ describe('unauthenticated user', () => {
+ before(() => {
+ authenticatedUser = null;
+ });
+
+ it('should return 403', () => {
+ const { status } = res;
+ expect(status).to.equal(403);
+ });
+ });
+
+ describe('authenicated user, with correct permission', () => {
+ before(() => {
+ authenticatedUser = USER_BOB;
+ });
+
+ it('should return 200', () => {
+ const { status } = res;
+ expect(status).to.equal(200);
+ });
+
+ it('updateAnnotations should be called', () => {
+ assert(updateAnnotations.called);
+ });
+
+ it('insertAnnotations should be called', () => {
+ assert(insertAnnotations.called);
+ });
+
+ // it('updateAnnotation called with correct arg', () => {
+ // assert(updateAnnotations.calledWith({
+ // fileID: '/path/to/json.json&slice=24',
+ // user: USER_BOB.username,
+ // ...rest
+ // }))
+ // })
+ });
+ });
+ });
+ });
+
+ describe('saveFromAPI', () => {
+
+ const FILENAME1 = `FILENAME1.json`;
+ const FILENAME2 = `FILENAME2.json`;
+ const correctJson = [
+ {
+ "annotation": {
+ "path": [
+ "Path",
{
- "annotation": {
- "path": [
- "Path",
- {
- "applyMatrix": true,
- "segments": [
- [345, 157],
- [386, 159],
- [385, 199]
- ],
- "closed": true,
- "fillColor": [0.1, 0.7, 0.6, 0.5],
- "strokeColor": [0, 0, 0],
- "strokeScaling": false
- }
- ],
- "name": "Contour 1"
- }
- },
+ "applyMatrix": true,
+ "segments": [
+ [345, 157],
+ [386, 159],
+ [385, 199]
+ ],
+ "closed": true,
+ "fillColor": [0.1, 0.7, 0.6, 0.5],
+ "strokeColor": [0, 0, 0],
+ "strokeScaling": false
+ }
+ ],
+ "name": "Contour 1"
+ }
+ },
+ {
+ "annotation": {
+ "path": [
+ "Path",
{
- "annotation": {
- "path": [
- "Path",
- {
- "applyMatrix": true,
- "segments": [
- [475, 227],
- [502, 155],
- [544, 221]
- ],
- "closed": true,
- "fillColor": [0.0, 0.0, 0.6, 0.5],
- "strokeColor": [0, 0, 0],
- "strokeScaling": false
- }
- ],
- "name": "Contour 2"
- }
+ "applyMatrix": true,
+ "segments": [
+ [475, 227],
+ [502, 155],
+ [544, 221]
+ ],
+ "closed": true,
+ "fillColor": [0.0, 0.0, 0.6, 0.5],
+ "strokeColor": [0, 0, 0],
+ "strokeScaling": false
}
- ]
-
- const incorrectJSON = {
- hello: "world"
+ ],
+ "name": "Contour 2"
}
+ }
+ ];
- const getQueryParam = ({ action = 'append' } = {}) => ({
- source: '/path/to/json.json',
- slice: 24,
- Hash: 'hello world',
- action,
- project: 'alreadyTestedPreviously'
- })
+ const incorrectJSON = {
+ hello: "world"
+ };
- let readFileStub
+ const getQueryParam = ({ action = 'append' } = {}) => ({
+ source: '/path/to/json.json',
+ slice: 24,
+ Hash: 'hello world',
+ action,
+ project: 'alreadyTestedPreviously'
+ });
- before(() => {
- readFileStub = sinon.stub(fs, 'readFileSync')
- readFileStub.withArgs(FILENAME1).returns(Buffer.from(JSON.stringify(correctJson)))
- readFileStub.withArgs(FILENAME2).returns(Buffer.from(JSON.stringify(incorrectJSON)))
- })
-
- after(() => {
- readFileStub.restore()
- })
-
- const getTest = (queryParam, { illFormedJson = false } = {}) => chai.request(url)
- .post('/upload')
- .attach(
- 'data',
- illFormedJson
- ? fs.readFileSync(FILENAME2)
- : fs.readFileSync(FILENAME1),
- illFormedJson
- ? FILENAME2
- : FILENAME1
- )
- .query(queryParam)
-
-
- describe('/POST', () => {
-
- describe('authenciation status behave expectedly', () => {
- describe('unauthenicated user', () => {
- describe('action=save', () => {
-
- let res
- before(async () => {
- authenticatedUser = null
- res = await getTest({ ...getQueryParam({ action: 'save' }) })
- })
-
- it('returns 401', () => {
- assert(res.status === 401)
- })
-
- it('status text is as expected', () => {
- expect(res.body).to.deep.equal({
- msg: 'Invalid user'
- })
- })
-
- it('findAnnotation is NOT called', () => {
- assert(findAnnotations.notCalled)
- })
-
- it('updateAnnotations NOT called', () => {
- assert(updateAnnotations.notCalled)
- })
-
- it('insertAnnotations NOT called', () => {
- assert(insertAnnotations.notCalled)
- }) })
- describe('action=append', () => {
-
- let res
- before(async () => {
- authenticatedUser = null
- res = await getTest({ ...getQueryParam({ action: 'append' }) })
- })
-
- it('returns 401', () => {
- assert(res.status === 401)
- })
-
- it('status text is as expected', () => {
- expect(res.body).to.deep.equal({
- msg: 'Invalid user'
- })
- })
-
- it('findAnnotation is NOT called', () => {
- assert(findAnnotations.notCalled)
- })
-
- it('updateAnnotation NOT called', () => {
- assert(updateAnnotations.notCalled)
- })
-
- it('insertAnnotation NOT called', () => {
- assert(insertAnnotations.notCalled)
- }) })
- })
-
- // describe('authenicated user', () => {
-
- // describe('action=save', () => {
- // let res, param
- // before(async () => {
- // authenticatedUser = USER_BOB
-
- // param = getQueryParam({ action: 'save' })
-
- // const { source: paramSource, slice, action, project, ...rest } = param
- // res = await getTest(param)
- // })
-
- // it('returns 200', () => {
- // assert(res.status === 200)
- // })
-
- // it('return body text is as expected', () => {
- // expect(res.body).to.deep.equal({
- // msg: 'Annotation successfully saved'
- // })
- // })
-
- // /**
- // * action=save does not call findAnnotation
- // */
- // it('findAnnotation not called', () => {
- // assert(findAnnotations.notCalled)
- // })
-
- // it('updateAnnotation is called', () => {
- // assert(updateAnnotation.called)
- // })
-
- // it('updateAnnotation called with correct param', () => {
-
- // const { source, slice, ...rest } = param
-
- // console.log('--------------------')
- // console.log(updateAnnotation.firstCall.args)
- // assert(updateAnnotation.calledWith({
- // fileID: buildFileID({ source, slice }),
- // user: USER_BOB.username,
- // project,
- // annotation: JSON.stringify({
- // Regions: correctJson.map(v => v.annotation)
- // }),
- // ...rest,
- // }))
- // })
- // })
-
- // describe('action=append', () => {
- // let res2
-
- // before(async () => {
- // authenticatedUser = USER_BOB
- // res2 = await getTest({ ...getQueryParam({ action: 'append' }) })
- // })
-
- // it('returns 200', () => {
- // assert(res2.status === 200)
- // })
-
- // it('body text as expected', () => {
- // expect(res2.body).to.deep.equal({
- // msg: 'Annotation successfully saved'
- // })
- // })
-
- // it('findAnnotation is called', () => {
- // assert(findAnnotations.called)
- // })
-
- // it('findAnnotation called with correct param', () => {
- // assert(findAnnotations.calledWith({
- // fileID: buildFileID({ source, slice}),
- // user: USER_BOB.username,
- // project
- // }))
- // })
-
- // it('update annotation called', () => {
- // assert(updateAnnotation.called)
- // })
-
- // it('updatedannotation called with correct param', () => {
-
- // assert(updateAnnotation.calledWith({
- // fileID: buildFileID({ source, slice}),
- // user: USER_BOB.username,
- // project,
- // annotation: JSON.stringify({
- // Regions: correctJson.map(v => v.annotation)
- // }),
- // ...rest,
-
- // }))
- // })
- // })
- // })
- })
- })
- })
-})
+ let readFileStub;
+
+ before(() => {
+ readFileStub = sinon.stub(fs, 'readFileSync');
+ readFileStub.withArgs(FILENAME1).returns(Buffer.from(JSON.stringify(correctJson)));
+ readFileStub.withArgs(FILENAME2).returns(Buffer.from(JSON.stringify(incorrectJSON)));
+ });
+
+ after(() => {
+ readFileStub.restore();
+ });
+
+ const getTest = (queryParam, { illFormedJson = false } = {}) => chai.request(url)
+ .post('/upload')
+ .attach(
+ 'data',
+ illFormedJson
+ // eslint-disable-next-line no-sync
+ ? fs.readFileSync(FILENAME2)
+ // eslint-disable-next-line no-sync
+ : fs.readFileSync(FILENAME1),
+ illFormedJson
+ ? FILENAME2
+ : FILENAME1
+ )
+ .query(queryParam);
+
+
+ describe('/POST', () => {
+
+ describe('authenciation status behave expectedly', () => {
+ describe('unauthenicated user', () => {
+ describe('action=save', () => {
+
+ let res;
+ before(async () => {
+ authenticatedUser = null;
+ res = await getTest({ ...getQueryParam({ action: 'save' }) });
+ });
+
+ it('returns 401', () => {
+ assert(res.status === 401);
+ });
+
+ it('status text is as expected', () => {
+ expect(res.body).to.deep.equal({
+ msg: 'Invalid user'
+ });
+ });
+
+ it('findAnnotation is NOT called', () => {
+ assert(findAnnotations.notCalled);
+ });
+
+ it('updateAnnotations NOT called', () => {
+ assert(updateAnnotations.notCalled);
+ });
+
+ it('insertAnnotations NOT called', () => {
+ assert(insertAnnotations.notCalled);
+ });
+ });
+ describe('action=append', () => {
+
+ let res;
+ before(async () => {
+ authenticatedUser = null;
+ res = await getTest({ ...getQueryParam({ action: 'append' }) });
+ });
+
+ it('returns 401', () => {
+ assert(res.status === 401);
+ });
+
+ it('status text is as expected', () => {
+ expect(res.body).to.deep.equal({
+ msg: 'Invalid user'
+ });
+ });
+
+ it('findAnnotation is NOT called', () => {
+ assert(findAnnotations.notCalled);
+ });
+
+ it('updateAnnotation NOT called', () => {
+ assert(updateAnnotations.notCalled);
+ });
+
+ it('insertAnnotation NOT called', () => {
+ assert(insertAnnotations.notCalled);
+ });
+ });
+ });
+
+ // describe('authenicated user', () => {
+
+ // describe('action=save', () => {
+ // let res, param
+ // before(async () => {
+ // authenticatedUser = USER_BOB
+
+ // param = getQueryParam({ action: 'save' })
+
+ // const { source: paramSource, slice, action, project, ...rest } = param
+ // res = await getTest(param)
+ // })
+
+ // it('returns 200', () => {
+ // assert(res.status === 200)
+ // })
+
+ // it('return body text is as expected', () => {
+ // expect(res.body).to.deep.equal({
+ // msg: 'Annotation successfully saved'
+ // })
+ // })
+
+ // /**
+ // * action=save does not call findAnnotation
+ // */
+ // it('findAnnotation not called', () => {
+ // assert(findAnnotations.notCalled)
+ // })
+
+ // it('updateAnnotation is called', () => {
+ // assert(updateAnnotation.called)
+ // })
+
+ // it('updateAnnotation called with correct param', () => {
+
+ // const { source, slice, ...rest } = param
+
+ // console.log('--------------------')
+ // console.log(updateAnnotation.firstCall.args)
+ // assert(updateAnnotation.calledWith({
+ // fileID: buildFileID({ source, slice }),
+ // user: USER_BOB.username,
+ // project,
+ // annotation: JSON.stringify({
+ // Regions: correctJson.map(v => v.annotation)
+ // }),
+ // ...rest,
+ // }))
+ // })
+ // })
+
+ // describe('action=append', () => {
+ // let res2
+
+ // before(async () => {
+ // authenticatedUser = USER_BOB
+ // res2 = await getTest({ ...getQueryParam({ action: 'append' }) })
+ // })
+
+ // it('returns 200', () => {
+ // assert(res2.status === 200)
+ // })
+
+ // it('body text as expected', () => {
+ // expect(res2.body).to.deep.equal({
+ // msg: 'Annotation successfully saved'
+ // })
+ // })
+
+ // it('findAnnotation is called', () => {
+ // assert(findAnnotations.called)
+ // })
+
+ // it('findAnnotation called with correct param', () => {
+ // assert(findAnnotations.calledWith({
+ // fileID: buildFileID({ source, slice}),
+ // user: USER_BOB.username,
+ // project
+ // }))
+ // })
+
+ // it('update annotation called', () => {
+ // assert(updateAnnotation.called)
+ // })
+
+ // it('updatedannotation called with correct param', () => {
+
+ // assert(updateAnnotation.calledWith({
+ // fileID: buildFileID({ source, slice}),
+ // user: USER_BOB.username,
+ // project,
+ // annotation: JSON.stringify({
+ // Regions: correctJson.map(v => v.annotation)
+ // }),
+ // ...rest,
+
+ // }))
+ // })
+ // })
+ // })
+ });
+ });
+ });
+});
diff --git a/app/controller/api/index.js b/app/controller/api/index.js
index 8c420ee..5258335 100644
--- a/app/controller/api/index.js
+++ b/app/controller/api/index.js
@@ -1,195 +1,179 @@
const express = require('express');
-const router = express.Router();
+const router = new express.Router();
const multer = require('multer');
const fs = require('fs');
-const TMP_DIR = process.env.TMP_DIR
+const {TMP_DIR} = process.env;
const storage = TMP_DIR
- ? multer.diskStorage({
- destination: TMP_DIR
- })
- : multer.memoryStorage()
+ ? multer.diskStorage({
+ destination: TMP_DIR
+ })
+ : multer.memoryStorage();
+
+const buildFileID = function ({ source, slice }) {
+ return `${source}&slice=${slice}`;
+};
// API routes
+// eslint-disable-next-line max-statements
router.get('/', async function (req, res) {
- console.warn("call to GET api");
- console.warn(req.query);
+ console.warn("call to GET api");
+ console.warn(req.query);
- // current user
- const username = (req.user && req.user.username) || 'anyone';
- console.log(`current user: ${username}`);
+ // current user
+ const username = (req.user && req.user.username) || 'anyone';
+ console.log(`current user: ${username}`);
- // project name
- const project = (req.query.project) || '';
- console.log(`current project: ${project}`);
+ // project name
+ const project = (req.query.project) || '';
+ console.log(`current project: ${project}`);
- if(project) {
- // project owner and project users
- const result = await req.app.db.queryProject({shortname: project});
- const owner = result
+ if(project) {
+ // project owner and project users
+ const result = await req.app.db.queryProject({shortname: project});
+ const owner = result
&& result.owner;
- const users = result
+ const users = result
&& result.collaborators
&& result.collaborators.list
- && result.collaborators.list.map((u)=>u.username) || [];
- console.log(`project owner: ${owner}, users: ${users}`);
+ && result.collaborators.list.map((u) => u.username) || [];
+ console.log(`project owner: ${owner}, users: ${users}`);
- // check if user is among the allowed project's users
- userIndex = [...users, owner].indexOf(username);
- if(userIndex<0) {
- res.status(403).send(`User ${username} not part of project ${project}`);
+ // check if user is among the allowed project's users
+ const userIndex = [...users, owner].indexOf(username);
+ if(userIndex<0) {
+ res.status(403).send(`User ${username} not part of project ${project}`);
- return;
- }
+ return;
}
+ }
- // include backups
- const backup = (typeof req.query.backup === "undefined")?false:true;
-
- const query = {
- fileID: buildFileID(req.query),
- // user: { $in: [...users, username] },
- project: project,
- };
- console.log("api get query", query);
-
- let annotations;
- try {
- annotations = await req.app.db.findAnnotations(query, backup);
- } catch(err) {
- throw new Error(err);
- }
+ // include backups
+ const backup = typeof req.query.backup !== "undefined";
- res.status(200).send(annotations);
-});
+ const query = {
+ fileID: buildFileID(req.query),
+ // user: { $in: [...users, username] },
+ project: project
+ };
+ console.log("api get query", query);
-function buildFileID({source, slice}) {
- return `${source}&slice=${slice}`;
-}
+ const annotations = await req.app.db.findAnnotations(query, backup);
+ res.status(200).send(annotations);
+});
+
+// eslint-disable-next-line max-statements
const updateAnnotation = async (req, {
- fileID,
- user,
- project,
- Hash,
- annotationString
+ fileID,
+ user,
+ project,
+ Hash,
+ annotationString
}) => {
- // get new annotations and ID
- const annotation = JSON.parse(annotationString);
- const {Regions: newAnnotations, RegionsToRemove: uidsToRemove} = annotation;
-
- // get previous annotations
- let prevAnnotations;
- try {
- prevAnnotations = await req.app.db.findAnnotations({fileID, user, project});
- } catch(err) {
- throw new Error(err);
+ // get new annotations and ID
+ const annotation = JSON.parse(annotationString);
+ const {Regions: newAnnotations, RegionsToRemove: uidsToRemove} = annotation;
+
+ // get previous annotations
+ const prevAnnotations = await req.app.db.findAnnotations({fileID, user, project});
+
+ // update previous annotations with new annotations,
+ // overwrite what already exists, remove those marked for removal
+ for(const prevAnnot of prevAnnotations) {
+ const {uid} = prevAnnot.annotation;
+ let foundInPrevious = false;
+ for(const newAnnot of newAnnotations) {
+ if(newAnnot.uid === uid) {
+ foundInPrevious = true;
+ break;
+ }
}
- // update previous annotations with new annotations,
- // overwrite what already exists, remove those marked for removal
- for(const prevAnnot of prevAnnotations.Regions) {
- const {uid} = prevAnnot.annotation;
- let foundInPrevious = false;
- for(const newAnnot of newAnnotations) {
- if(newAnnot.uid === uid) {
- foundInPrevious = true;
+ let markedForRemoval = false;
+ if(uidsToRemove) {
+ for(const uidToRemove of uidsToRemove) {
+ if(uid === uidToRemove) {
+ markedForRemoval = true;
break;
}
}
-
- let markedForRemoval = false;
- if(uidsToRemove) {
- for(const uidToRemove of uidsToRemove) {
- if(uid === uidToRemove) {
- markedForRemoval = true;
- break;
- }
- }
- }
-
- if(!foundInPrevious && !markedForRemoval) {
- newAnnotations.push(prevAnnot.annotation);
- }
- }
- annotation.Regions = newAnnotations;
-
- // mark previous version as backup
- try {
- await req.app.db.updateAnnotations(
- Object.assign(
- {},
- { fileID, /*user,*/ project }, // update annotations authored by anyone
- { backup: { $exists: false } }
- ),
- { $set: { backup: true } },
- { multi: true }
- );
- } catch (err) {
- throw new Error(err);
}
- // add new version
- const arrayTobeSaved = annotation.Regions.map((region) => ({
- fileID,
- user, // "user" property in annotation corresponds to "username" everywhere else
- project,
- Hash,
- annotation: region
- }));
- try {
- await req.app.db.insertAnnotations(arrayTobeSaved);
- } catch(err) {
- throw new Error(err);
+ if(!foundInPrevious && !markedForRemoval) {
+ newAnnotations.push(prevAnnot.annotation);
}
-}
+ }
+ annotation.Regions = newAnnotations;
+
+ // mark previous version as backup
+ const query = Object.assign(
+ {},
+ { fileID, /*user,*/ project }, // update annotations authored by anyone
+ { backup: { $exists: false } }
+ );
+ const update = { $set: { backup: true } };
+ const options = { multi: true };
+
+ // add new version
+ const arrayTobeSaved = annotation.Regions.map((region) => ({
+ fileID,
+ user, // "user" property in annotation corresponds to "username" everywhere else
+ project,
+ Hash,
+ annotation: region
+ }));
+ await req.app.db.updateAnnotations({ query, update, options });
+ await req.app.db.insertAnnotations(arrayTobeSaved);
+};
+// eslint-disable-next-line max-statements
const saveFromGUI = async function (req, res) {
- const { Hash, annotation } = req.body;
-
- // current user
- const username = (req.user && req.user.username) || 'anyone';
- console.log(`current user: ${username}`);
+ const { Hash, annotation } = req.body;
- // project name
- const project = (req.body.project) || '';
- console.log(`current project: ${project}`);
+ // current user
+ const username = (req.user && req.user.username) || 'anyone';
+ console.log(`current user: ${username}`);
- // project owner and project users
- if(project) {
- const result = await req.app.db.queryProject({shortname: project});
- const owner = result
+ // project name
+ const project = (req.body.project) || '';
+ console.log(`current project: ${project}`);
+
+ // project owner and project users
+ if(project) {
+ const result = await req.app.db.queryProject({shortname: project});
+ const owner = result
&& result.owner;
- const users = result
+ const users = result
&& result.collaborators
&& result.collaborators.list
&& result.collaborators.list.map((u) => u.username);
- console.log(`project owner: ${owner}, users: ${users}`);
+ console.log(`project owner: ${owner}, users: ${users}`);
- // check if user is among the allowed project's users
- userIndex = [...users, owner].indexOf(username);
- if(userIndex<0) {
- res.status(403).send(`User ${username} not part of project ${project}`);
+ // check if user is among the allowed project's users
+ const userIndex = [...users, owner].indexOf(username);
+ if(userIndex<0) {
+ res.status(403).send(`User ${username} not part of project ${project}`);
- return;
- }
+ return;
}
+ }
- const fileID = buildFileID(req.body);
- updateAnnotation(req, {
- fileID,
- user: username,
- project,
- Hash,
- annotationString: annotation
- })
+ const fileID = buildFileID(req.body);
+ updateAnnotation(req, {
+ fileID,
+ user: username,
+ project,
+ Hash,
+ annotationString: annotation
+ })
.then(() => {
- res.status(200).send({success: true})
+ res.status(200).send({success: true});
})
.catch((e) => {
- res.status(500).send({err:JSON.stringify(e)})
+ res.status(500).send({err:e.message});
});
};
@@ -199,101 +183,102 @@ const saveFromGUI = async function (req, res) {
* @returns {boolean} True if the object is valid
*/
const jsonIsValid = function (obj) {
- if(typeof obj === 'undefined') {
- return false;
- } else if(obj.constructor !== Array) {
- return false;
- } else if(!obj.every(item => item.annotation && item.annotation.path)) {
- return false;
- }
-
- return true;
+ if(typeof obj === 'undefined') {
+ return false;
+ } else if(obj.constructor !== Array) {
+ return false;
+ } else if(!obj.every((item) => item.annotation && item.annotation.path)) {
+ return false;
+ }
+
+ return true;
};
/**
* Loads a json file containing an annotation object.
+ * Not used anymore
* @param {string} annotationPath Path to json file containing an annotation
* @returns {object} A valid annotation object or nothing.
*/
-const loadAnnotationFile = function (annotationPath) {
- const json = JSON.parse(fs.readFileSync(annotationPath).toString());
- if(jsonIsValid(json) === true) {
- return json;
- }
-};
-
+// const loadAnnotationFile = function (annotationPath) {
+// const json = JSON.parse(fs.readFileSync(annotationPath).toString());
+// if(jsonIsValid(json) === true) {
+// return json;
+// }
+// };
+
+// eslint-disable-next-line max-statements
const saveFromAPI = async function (req, res) {
- const fileID = buildFileID(req.query);
- const username = req.user && req.user.username;
- const { project, Hash } = req.query;
- const rawString = TMP_DIR
- ? fs.readFileSync(req.files[0].path).toString()
- : req.files[0].buffer.toString();
- const json = JSON.parse(rawString);
- if (typeof project === 'undefined') {
- res.status(401).json({msg: "Invalid project"});
- } else if(!jsonIsValid(json)) {
- res.status(401).json({msg: "Invalid annotation file"});
- } else {
- const { action } = req.query;
- const annotations = action === 'append'
- ? await req.app.db.findAnnotations({ fileID, user: username, project })
- : { Regions: [] };
-
- /**
+ const fileID = buildFileID(req.query);
+ const username = req.user && req.user.username;
+ const { project, Hash } = req.query;
+ const rawString = TMP_DIR
+ ? await fs.promises.readFile(req.files[0].path).toString()
+ : req.files[0].buffer.toString();
+ const json = JSON.parse(rawString);
+ if (typeof project === 'undefined') {
+ res.status(401).json({msg: "Invalid project"});
+ } else if(!jsonIsValid(json)) {
+ res.status(401).json({msg: "Invalid annotation file"});
+ } else {
+ const { action } = req.query;
+ const annotations = action === 'append'
+ ? await req.app.db.findAnnotations({ fileID, user: username, project })
+ : [];
+
+ /**
* use object destruction to avoid mutation of annotations object
*/
- const { Regions, ...rest } = annotations
- updateAnnotation(req, {
- fileID,
- user: username,
- project,
- Hash,
- annotationString: JSON.stringify({
- ...rest,
- Regions: Regions.concat(json.map(v => v.annotation))
- })
- })
- .then(() => res.status(200).send({msg: "Annotation successfully saved"}))
- .catch((e) => res.status(500).send({err:JSON.stringify(e)}));
- }
+ const { Regions, ...rest } = annotations;
+ updateAnnotation(req, {
+ fileID,
+ user: username,
+ project,
+ Hash,
+ annotationString: JSON.stringify({
+ ...rest,
+ Regions: Regions.concat(json.map((v) => v.annotation))
+ })
+ })
+ .then(() => res.status(200).send({msg: "Annotation successfully saved"}))
+ .catch((e) => res.status(500).send({ err: e.message}));
+ }
};
router.post('/', function (req, res) {
- console.warn("call to POST from GUI");
+ console.warn("call to POST from GUI");
- if(req.body.action === 'save') {
- saveFromGUI(req, res);
- } else {
- res.status(500).send({err:'actions other than save are no longer supported.'});
- }
+ if(req.body.action === 'save') {
+ saveFromGUI(req, res);
+ } else {
+ res.status(500).send({err:'actions other than save are no longer supported.'});
+ }
});
const filterAuthorizedUserOnly = (req, res, next) => {
- const username = req.user && req.user.username;
- if (typeof username === 'undefined') {
- return res.status(401).send({msg:'Invalid user'});
- } else {
- return next()
- }
-}
+ const username = req.user && req.user.username;
+ if (typeof username === 'undefined') {
+ return res.status(401).send({msg:'Invalid user'});
+ }
-router.post('/upload', filterAuthorizedUserOnly, multer({ storage }).array('data'), function (req, res) {
- console.warn("call to POST from API");
+ return next();
- const { action } = req.query
- switch(action) {
- case 'save':
- case 'append':
- saveFromAPI(req, res)
- break;
- default:
- return res.status(500).send({err: `actions other than save and append are no longer supported`})
- }
+};
+
+router.post('/upload', filterAuthorizedUserOnly, multer({ storage }).array('data'), function (req, res) {
+ console.warn("call to POST from API");
+
+ const { action } = req.query;
+ switch(action) {
+ case 'save':
+ case 'append':
+ saveFromAPI(req, res);
+ break;
+ default:
+ return res.status(500).send({err: `actions other than save and append are no longer supported`});
+ }
});
-router.use('', (req, res) => {
- return res.redirect('/')
-})
+router.use('', (req, res) => res.redirect('/'));
-module.exports = router;
\ No newline at end of file
+module.exports = router;
diff --git a/app/public/js/.eslintrc.json b/app/public/js/.eslintrc.json
new file mode 100644
index 0000000..3c4df18
--- /dev/null
+++ b/app/public/js/.eslintrc.json
@@ -0,0 +1,8 @@
+{
+ "env": {
+ "node": false
+ },
+ "parserOptions": {
+ "sourceType": "script"
+ }
+}
diff --git a/app/public/js/microdraw-ws.js b/app/public/js/microdraw-ws.js
index e94f978..6d64a8c 100644
--- a/app/public/js/microdraw-ws.js
+++ b/app/public/js/microdraw-ws.js
@@ -10,42 +10,41 @@ if(Microdraw.secure) {
} else {
wshostname = "ws://" + Microdraw.wshostname;
}
-var ws = new window.WebSocket(wshostname);
+const ws = new window.WebSocket(wshostname);
+
+const randomUuid = Math.floor(Math.random() * 65535);
+
+const _decodeRandomUuidToNickname = (n) => {
+ if (typeof n !== 'number') {
+ throw new Error('argument to _decodeRandomUuidToNickname must be a number.');
+ }
+ if (Number.isNaN(n)) {
+ throw new Error('argument to _decodeRandomUuidToNickname cannot be NaN');
+ }
+ const { HippyHippo } = window.HippyHippo;
+
+ return HippyHippo.getNickName(n);
+};
/**
* @param {object} data Data received
* @returns {void}
*/
const receiveChatMessage = (data) => {
- const {dom} = Microdraw;
+ const { dom } = Microdraw;
let theUsername;
if (data.username !== "Anonymous") {
theUsername = data.username;
+ } else if (typeof data.randomUuid === 'number') {
+ theUsername = _decodeRandomUuidToNickname(data.randomUuid);
} else {
- if (typeof data.randomUuid === 'number') {
- theUsername = _decodeRandomUuidToNickname(data.randomUuid);
- } else {
- theUsername = data.uid;
- }
+ theUsername = data.uid;
}
const msg = "" + theUsername + ": " + data.msg + "
";
dom.querySelector("#logChat .text").innerHTML += msg;
dom.querySelector("#logChat .text").scrollTop = dom.querySelector("#logChat .text").scrollHeight;
};
-const randomUuid = Math.floor(Math.random() * 65535);
-
-const _decodeRandomUuidToNickname = n => {
- if (typeof n !== 'number') {
- throw new Error('argument to _decodeRandomUuidToNickname must be a number.');
- }
- if (Number.isNaN(n)) {
- throw new Error('argument to _decodeRandomUuidToNickname cannot be NaN');
- }
- const { HippyHippo } = window.HippyHippo
- return HippyHippo.getNickName(n)
-};
-
const _getUserName = () => {
let username = document.querySelector("#MyLogin a").innerText;
@@ -71,7 +70,7 @@ const _makeMessageObject = () => {
const _displayOwnMessage = (msg) => {
const {dom} = Microdraw;
- const _username = _getUserName()
+ const _username = _getUserName();
const username = _username === 'Anonymous'
? _decodeRandomUuidToNickname(randomUuid)
: _username;
diff --git a/app/public/js/microdraw.js b/app/public/js/microdraw.js
index bb6197c..3314202 100644
--- a/app/public/js/microdraw.js
+++ b/app/public/js/microdraw.js
@@ -5,206 +5,205 @@
/*eslint no-alert: "off"*/
/* global paper */
/*global OpenSeadragon*/
-/*global Ontology*/
/*global MUI*/
+/* exported Microdraw */
const Microdraw = (function () {
const me = {
- debug: 1,
- hostname: "http://localhost:3000",
- wshostname: "localhost:8080",
- secure: true,
-
- ImageInfo: {}, // regions for each sections, can be accessed by the section name. (e.g. me.ImageInfo[me.imageOrder[viewer.current_page()]])
- // regions contain a paper.js path, a unique ID and a name
- imageOrder: [], // names of sections ordered by their openseadragon page numbers
- currentImage: null, // name of the current image
- prevImage: null, // name of the last image
- currentLabelIndex: 0, // current label to use
- region: null, // currently selected region (one element of Regions[])
- copyRegion: null, // clone of the currently selected region for copy/paste
- handle: null, // currently selected control point or handle (if any)
- selectedTool: null, // currently selected tool
- viewer: null, // open seadragon viewer
- isAnimating: false, // flag indicating whether there an animation going on (zoom, pan)
- navEnabled: true, // flag indicating whether the navigator is enabled (if it's not, the annotation tools are)
- magicV: 1000, // resolution of the annotation canvas - is changed automatically to reflect the size of the tileSource
- params: null, // URL parameters
- source: null, // data source
- section: null, // section index in a multi-section dataset
- UndoStack: [],
- RedoStack: [],
- mouseUndo: null, // tentative undo information.
- shortCuts: [], // List of shortcuts
- newRegionFlag: null, // true when a region is being drawn
- drawingPolygonFlag: false, // true when drawing a polygon
- annotationLoadingFlag: null, // true when an annotation is being loaded
- config: {}, // App configuration object
- isMac: navigator.platform.match(/Mac/i),
- isIOS: navigator.platform.match(/(iPhone|iPod|iPad)/i),
- tolerance: 4, // tolerance for hit test. This value is divided by paper.view.zoom to make it work across resolutions
- counter: 1,
- tap: false,
- currentColorRegion: null,
- tools : {},
-
- /*
+ debug: 1,
+ hostname: window.location.origin,
+ wshostname: `${window.location.hostname}:8080`,
+ secure: true,
+
+ ImageInfo: {}, // regions for each sections, can be accessed by the section name. (e.g. me.ImageInfo[me.imageOrder[viewer.current_page()]])
+ // regions contain a paper.js path, a unique ID and a name
+ imageOrder: [], // names of sections ordered by their openseadragon page numbers
+ currentImage: null, // name of the current image
+ prevImage: null, // name of the last image
+ currentLabelIndex: 0, // current label to use
+ region: null, // currently selected region (one element of Regions[])
+ copyRegion: null, // clone of the currently selected region for copy/paste
+ handle: null, // currently selected control point or handle (if any)
+ selectedTool: null, // currently selected tool
+ viewer: null, // open seadragon viewer
+ isAnimating: false, // flag indicating whether there an animation going on (zoom, pan)
+ navEnabled: true, // flag indicating whether the navigator is enabled (if it's not, the annotation tools are)
+ magicV: 1000, // resolution of the annotation canvas - is changed automatically to reflect the size of the tileSource
+ params: null, // URL parameters
+ source: null, // data source
+ section: null, // section index in a multi-section dataset
+ UndoStack: [],
+ RedoStack: [],
+ mouseUndo: null, // tentative undo information.
+ shortCuts: [], // List of shortcuts
+ newRegionFlag: null, // true when a region is being drawn
+ drawingPolygonFlag: false, // true when drawing a polygon
+ annotationLoadingFlag: null, // true when an annotation is being loaded
+ config: {}, // App configuration object
+ isMac: navigator.platform.match(/Mac/i),
+ isIOS: navigator.platform.match(/(iPhone|iPod|iPad)/i),
+ tolerance: 4, // tolerance for hit test. This value is divided by paper.view.zoom to make it work across resolutions
+ counter: 1,
+ tap: false,
+ currentColorRegion: null,
+ tools : {},
+
+ /*
Region handling functions
*/
- /**
+ /**
* @param {string} msg Message to print to console.
* @param {int} level Minimum debug level to print.
* @returns {void}
*/
- debugPrint: function (msg, level) {
- if (me.debug >= level) {
- console.log(msg);
- }
- },
+ debugPrint: function (msg, level) {
+ if (me.debug >= level) {
+ console.log(msg);
+ }
+ },
- /**
+ /**
* @returns {string} Get a unique random alphanumeric identifier for the region
*/
- regionUID: function () {
- if( me.debug>1 ) { console.log("> regionUID"); }
+ regionUID: function () {
+ if( me.debug>1 ) { console.log("> regionUID"); }
- return Math.random().toString(16).slice(2);
+ return Math.random().toString(16)
+ .slice(2);
// me.counter = me.ImageInfo[me.currentImage].Regions.reduce(
// (a, b) => Math.max(a, Math.floor(b.uid) + 1), me.counter
// );
// return me.counter;
- },
+ },
- /**
+ /**
* @param {string} str String to hash
* @returns {string} A hash
*/
- hash: function (str) {
- const result = str.split("").reduce(function(a, b) {
- a = ((a<<5)-a) + b.charCodeAt(0);
+ hash: function (str) {
+ const result = str.split("").reduce(function(a, b) {
+ a = ((a<<5)-a) + b.charCodeAt(0);
- return a&a;
- }, 0);
+ return a&a;
+ }, 0);
- return result;
- },
+ return result;
+ },
- sectionValueForHashing: function (section) {
- const value = {
- Regions: [],
- RegionsToRemove: []
- };
-
- for(const reg of section.Regions) {
- value.Regions.push({
- path: JSON.parse(reg.path.exportJSON()),
- name: reg.name,
- uid: reg.uid
- });
- }
-
- for( const uid of section.RegionsToRemove ) {
- value.RegionsToRemove.push(uid);
- }
+ sectionValueForHashing: function (section) {
+ const value = {
+ Regions: [],
+ RegionsToRemove: []
+ };
- return value;
- },
+ for(const reg of section.Regions) {
+ value.Regions.push({
+ path: JSON.parse(reg.path.exportJSON()),
+ name: reg.name,
+ uid: reg.uid
+ });
+ }
- sectionHash: function (section) {
- const value = me.sectionValueForHashing(section);
- const hash = me.hash(JSON.stringify(value)).toString(16);
-
- return hash;
- },
+ for( const uid of section.RegionsToRemove ) {
+ value.RegionsToRemove.push(uid);
+ }
- /**
+ return value;
+ },
+
+ sectionHash: function (section) {
+ const value = me.sectionValueForHashing(section);
+ const hash = me.hash(JSON.stringify(value)).toString(16);
+
+ return hash;
+ },
+
+ /**
* @desc Produces a color based on a region name.
* @param {string} name Name of the region.
* @returns {number} color Default color of the region based on its name.
*/
- regionHashColor: function (name) {
- const color = {};
- let h = me.hash(name);
+ regionHashColor: function (name) {
+ const color = {};
+ let h = me.hash(name);
- // add some randomness
- h = Math.sin(h += 1)*10000;
- h = 0xffffff*(h - Math.floor(h));
+ // add some randomness
+ h = Math.sin(h += 1)*10000;
+ h = 0xffffff*(h - Math.floor(h));
- color.red = h & 0xff;
- color.green = (h & 0xff00)>>8;
- color.blue = (h & 0xff0000)>>16;
+ color.red = h & 0xff;
+ color.green = (h & 0xff00)>>8;
+ color.blue = (h & 0xff0000)>>16;
- return color;
- },
+ return color;
+ },
- /**
+ /**
* @desc Gets the color for a region based on its name
* @param {string} name Name of the region.
* @returns {number} color Color of the region based on its name.
*/
- regionColor: function (name) {
- const color = {};
- for(const {name: rname, value, color: rcolor, url} of me.ontology.labels) {
- if(name === rname) {
- color.red = rcolor[0];
- color.green = rcolor[1];
- color.blue = rcolor[2];
+ regionColor: function (name) {
+ const color = {};
+ for(const {name: rname, color: rcolor} of me.ontology.labels) {
+ if(name === rname) {
+ [color.red, color.green, color.blue] = rcolor;
- return color;
- }
+ return color;
}
+ }
- // name not found: assign one based on the name
- return me.regionHashColor(name);
- },
+ // name not found: assign one based on the name
+ return me.regionHashColor(name);
+ },
- updateLabelDisplay: function () {
- const {color} = me.ontology.labels[me.currentLabelIndex];
- me.dom.querySelector("#color").style["background-color"] = `rgb(${color[0]}, ${color[1]}, ${color[2]})`;
- },
+ updateLabelDisplay: function () {
+ const {color} = me.ontology.labels[me.currentLabelIndex];
+ me.dom.querySelector("#color").style["background-color"] = `rgb(${color[0]}, ${color[1]}, ${color[2]})`;
+ },
- /**
+ /**
* @param {number} uid Unique ID of a region.
* @returns {object} The region corresponding to the given ID
*/
- findRegionByUID: function (uid) {
- me.debugPrint("> findRegionByUID", 1);
+ findRegionByUID: function (uid) {
+ me.debugPrint("> findRegionByUID", 1);
- me.debugPrint( "look for uid: " + uid, 2);
- me.debugPrint( "region array lenght: " + me.ImageInfo[me.currentImage].Regions.length, 2 );
- // if( me.debug > 2 ) console.log( me.ImageInfo );
+ me.debugPrint( "look for uid: " + uid, 2);
+ me.debugPrint( "region array lenght: " + me.ImageInfo[me.currentImage].Regions.length, 2 );
+ // if( me.debug > 2 ) console.log( me.ImageInfo );
- for( let i = 0; i < me.ImageInfo[me.currentImage].Regions.length; i += 1 ) {
- // if( parseInt(me.ImageInfo[me.currentImage].Regions[i].uid, 10) === parseInt(uid, 10) ) {
- if( me.ImageInfo[me.currentImage].Regions[i].uid === uid ) {
- me.debugPrint( "region " + me.ImageInfo[me.currentImage].Regions[i].uid + ": ", 2);
- me.debugPrint( me.ImageInfo[me.currentImage].Regions[i], 2 );
+ for( let i = 0; i < me.ImageInfo[me.currentImage].Regions.length; i += 1 ) {
+ // if( parseInt(me.ImageInfo[me.currentImage].Regions[i].uid, 10) === parseInt(uid, 10) ) {
+ if( me.ImageInfo[me.currentImage].Regions[i].uid === uid ) {
+ me.debugPrint( "region " + me.ImageInfo[me.currentImage].Regions[i].uid + ": ", 2);
+ me.debugPrint( me.ImageInfo[me.currentImage].Regions[i], 2 );
- return me.ImageInfo[me.currentImage].Regions[i];
- }
+ return me.ImageInfo[me.currentImage].Regions[i];
}
- console.log(`WARNING: Region with unique ID ${uid} not found`);
- },
+ }
+ console.log(`WARNING: Region with unique ID ${uid} not found`);
+ },
- /**
+ /**
* @param {string} name Name of the region.
* @param {string} uid Unique ID of the region.
* @returns {string} str The color of the region.
*/
- regionTag: function (name, uid) {
- if( me.debug>1 ) console.log("> regionTag");
-
- let str;
- const color = me.regionColor(name);
- if( uid ) {
- const reg = me.findRegionByUID(uid);
- let mult = 1.0;
- if( reg ) {
- mult = 255;
- color = reg.path.fillColor;
- }
- str = `
+ regionTag: function (name, uid) {
+ if( me.debug>1 ) { console.log("> regionTag"); }
+
+ let str;
+ let color = me.regionColor(name);
+ if( uid ) {
+ const reg = me.findRegionByUID(uid);
+ let mult = 1.0;
+ if( reg ) {
+ mult = 255;
+ color = reg.path.fillColor;
+ }
+ str = `
${name}
`;
- } else {
- str = `
+ } else {
+ str = `
`;
- }
+ }
- return str;
- },
+ return str;
+ },
- /**
+ /**
* Create a new path from a Paper json object
* @param {object} json Paper json object
* @returns {object} Paper path (which is automatically added to the active project)
*/
- _pathFromJSON: (json) => {
- let path;
- switch(json[0]) {
- case 'Path': {
- path = new paper.Path();
- path.importJSON(json);
- break;
- }
- case 'CompoundPath': {
- path = new paper.CompoundPath();
- path.importJSON(json);
- break;
- }
- }
+ _pathFromJSON: (json) => {
+ let path;
+ switch(json[0]) {
+ case 'Path': {
+ path = new paper.Path();
+ path.importJSON(json);
+ break;
+ }
+ case 'CompoundPath': {
+ path = new paper.CompoundPath();
+ path.importJSON(json);
+ break;
+ }
+ }
- return path;
- },
+ return path;
+ },
- _selectRegionInList: function (reg) {
- // Select region name in list
- [].forEach.call(me.dom.querySelectorAll("#regionList > .region-tag"), function(r) {
- r.classList.add("deselected");
- r.classList.remove("selected");
- });
+ _selectRegionInList: function (reg) {
+ // Select region name in list
+ [].forEach.call(me.dom.querySelectorAll("#regionList > .region-tag"), function(r) {
+ r.classList.add("deselected");
+ r.classList.remove("selected");
+ });
- if (reg) {
- // Need to use unicode character for ID since CSS3 doesn't support ID selectors that start with a digit:
- // If reg.uid starts with a number, the first number has to be converted to unicode
- // for example, 10a to #\\31 0a
- // var tag = me.dom.querySelector("#regionList > .region-tag#\\3" + (reg.uid.toString().length > 1 ? reg.uid.toString()[0] + ' ' + reg.uid.toString().slice(1) : reg.uid.toString()) );
- let uid = reg.uid.toString();
- if(isNaN(parseInt(uid[0])) === false) {
- uid = "\\3" + (uid.length > 1 ? uid[0] + ' ' + uid.slice(1) : uid)
- }
+ if (reg) {
+ // Need to use unicode character for ID since CSS3 doesn't support ID selectors that start with a digit:
+ // If reg.uid starts with a number, the first number has to be converted to unicode
+ // for example, 10a to #\\31 0a
+ // var tag = me.dom.querySelector("#regionList > .region-tag#\\3" + (reg.uid.toString().length > 1 ? reg.uid.toString()[0] + ' ' + reg.uid.toString().slice(1) : reg.uid.toString()) );
+ let uid = reg.uid.toString();
+ if(isNaN(parseInt(uid[0], 10)) === false) {
+ uid = "\\3" + (uid.length > 1 ? uid[0] + ' ' + uid.slice(1) : uid);
+ }
- const tag = me.dom.querySelector(`#regionList > .region-tag#${uid}`);
- if(tag) {
- tag.classList.remove("deselected");
- tag.classList.add("selected");
- }
+ const tag = me.dom.querySelector(`#regionList > .region-tag#${uid}`);
+ if(tag) {
+ tag.classList.remove("deselected");
+ tag.classList.add("selected");
}
- },
+ }
+ },
- /**
+ /**
* @desc Make the region selected
* @param {object} reg The region to select, or null to deselect allr egions
* @returns {void}
*/
- selectRegion: function (reg) {
- if( me.debug>1 ) { console.log("> selectRegion"); }
+ selectRegion: function (reg) {
+ if( me.debug>1 ) { console.log("> selectRegion"); }
- // Select path
- for( let i = 0; i < me.ImageInfo[me.currentImage].Regions.length; i += 1 ) {
- if( me.ImageInfo[me.currentImage].Regions[i] === reg ) {
- reg.path.selected = true;
- reg.path.fullySelected = true;
- me.region = reg;
- } else {
- me.ImageInfo[me.currentImage].Regions[i].path.selected = false;
- me.ImageInfo[me.currentImage].Regions[i].path.fullySelected = false;
- }
+ // Select path
+ for( let i = 0; i < me.ImageInfo[me.currentImage].Regions.length; i += 1 ) {
+ if( me.ImageInfo[me.currentImage].Regions[i] === reg ) {
+ reg.path.selected = true;
+ reg.path.fullySelected = true;
+ me.region = reg;
+ } else {
+ me.ImageInfo[me.currentImage].Regions[i].path.selected = false;
+ me.ImageInfo[me.currentImage].Regions[i].path.fullySelected = false;
}
- paper.view.draw();
+ }
+ paper.view.draw();
- me._selectRegionInList(reg);
+ me._selectRegionInList(reg);
- if(me.debug>1) { console.log("< selectRegion"); }
- },
+ if(me.debug>1) { console.log("< selectRegion"); }
+ },
- /**
+ /**
* @param {object} reg The entry in the region's array.
* @param {string} name Name of the region.
* @returns {void}
*/
- changeRegionName: function (reg, name) {
- if( me.debug>1 ) { console.log("> changeRegionName"); }
+ changeRegionName: function (reg, name) {
+ if( me.debug>1 ) { console.log("> changeRegionName"); }
- const color = me.regionColor(name);
+ const color = me.regionColor(name);
- // Update path
- reg.name = name;
- reg.path.fillColor = 'rgba(' + color.red + ',' + color.green + ',' + color.blue + ',0.5)';
- paper.view.draw();
+ // Update path
+ reg.name = name;
+ reg.path.fillColor = 'rgba(' + color.red + ',' + color.green + ',' + color.blue + ',0.5)';
+ paper.view.draw();
- // Update region tag
- // me.dom.querySelector(".region-tag#" + reg.uid + ">.region-name").textContent = name;
- // me.dom.querySelector(".region-tag#" + reg.uid + ">.region-color").style['background-color'] = 'rgba(' + color.red + ',' + color.green + ',' + color.blue + ',0.67)';
- },
+ // Update region tag
+ // me.dom.querySelector(".region-tag#" + reg.uid + ">.region-name").textContent = name;
+ // me.dom.querySelector(".region-tag#" + reg.uid + ">.region-color").style['background-color'] = 'rgba(' + color.red + ',' + color.green + ',' + color.blue + ',0.67)';
+ },
- /**
+ /**
* @desc Toggle the visibility of a region
* @param {object} reg The region whose visibility is to toggle
* @returns {void}
*/
- toggleRegion: function (reg) {
- if( me.region !== null ) {
- if( me.debug>1 ) { console.log("> toggle region"); }
+ toggleRegion: function (reg) {
+ if( me.region !== null ) {
+ if( me.debug>1 ) { console.log("> toggle region"); }
- if( reg.path.fillColor !== null ) {
- reg.path.storeColor = reg.path.fillColor;
- reg.path.fillColor = null;
+ if( reg.path.fillColor !== null ) {
+ reg.path.storeColor = reg.path.fillColor;
+ reg.path.fillColor = null;
- reg.path.strokeWidth = 0;
- reg.path.fullySelected = false;
- reg.storeName = reg.name;
- me.dom.querySelector('#eye_' + reg.uid).setAttribute('src', '/img/eyeClosed.svg');
- } else {
- reg.path.fillColor = reg.path.storeColor;
- reg.path.strokeWidth = 1;
- reg.name = reg.storeName;
- me.dom.querySelector('#eye_' + reg.uid).setAttribute('src', '/img/eyeOpened.svg');
- }
- paper.view.draw();
- me.dom.querySelector(".region-tag#" + reg.uid + ">.region-name").textContent = reg.name;
+ reg.path.strokeWidth = 0;
+ reg.path.fullySelected = false;
+ reg.storeName = reg.name;
+ me.dom.querySelector('#eye_' + reg.uid).setAttribute('src', '/img/eyeClosed.svg');
+ } else {
+ reg.path.fillColor = reg.path.storeColor;
+ reg.path.strokeWidth = 1;
+ reg.name = reg.storeName;
+ me.dom.querySelector('#eye_' + reg.uid).setAttribute('src', '/img/eyeOpened.svg');
}
- },
+ paper.view.draw();
+ me.dom.querySelector(".region-tag#" + reg.uid + ">.region-name").textContent = reg.name;
+ }
+ },
- /**
+ /**
* @desc Add leading zeros
* @param {number} number A number
* @param {length} length The desired length for the resulting string
* @returns {string} number string padded with zeroes
*/
- pad: function (number, length) {
- let str = String(number);
- while( str.length < length ) { str = '0' + str; }
+ pad: function (number, length) {
+ let str = String(number);
+ while( str.length < length ) { str = '0' + str; }
- return str;
- },
+ return str;
+ },
- /**
+ /**
* @desc Get current alpha & color values for colorPicker display
* @param {object} reg The selected region.
* @returns {void}
*/
- annotationStyle: function (reg) {
- if( me.debug>1 ) { console.log(reg.path.fillColor); }
+ annotationStyle: function (reg) {
+ if( me.debug>1 ) { console.log(reg.path.fillColor); }
- if( me.region !== null ) {
- if( me.debug>1 ) { console.log("> changing annotation style"); }
+ if( me.region !== null ) {
+ if( me.debug>1 ) { console.log("> changing annotation style"); }
- me.currentColorRegion = reg;
- let {alpha} = reg.path.fillColor.alpha;
- me.dom.querySelector('#alphaSlider').value = alpha*100;
- me.dom.querySelector('#alphaFill').value = parseInt(alpha*100, 10);
+ me.currentColorRegion = reg;
+ let {alpha} = reg.path.fillColor.alpha;
+ me.dom.querySelector('#alphaSlider').value = alpha*100;
+ me.dom.querySelector('#alphaFill').value = parseInt(alpha*100, 10);
- const hexColor = '#'
+ const hexColor = '#'
+ me.pad(( parseInt(reg.path.fillColor.red * 255, 10) ).toString(16), 2)
+ me.pad(( parseInt(reg.path.fillColor.green * 255, 10) ).toString(16), 2)
+ me.pad(( parseInt(reg.path.fillColor.blue * 255, 10) ).toString(16), 2);
- if( me.debug>1 ) { console.log(hexColor); }
+ if( me.debug>1 ) { console.log(hexColor); }
- me.dom.querySelector('#fillColorPicker').value = hexColor;
+ me.dom.querySelector('#fillColorPicker').value = hexColor;
- if( me.dom.querySelector('#colorSelector').style.display === 'none' ) {
+ if( me.dom.querySelector('#colorSelector').style.display === 'none' ) {
- /** @todo On show, populate alpha */
- reg = me.currentColorRegion;
- alpha = reg.path.fillColor.alpha;
- me.dom.querySelector('#alphaSlider').value = alpha * 100;
- me.dom.querySelector('#alphaFill').value = alpha * 100;
+ /** @todo On show, populate alpha */
+ reg = me.currentColorRegion;
+ ({alpha} = reg.path.fillColor);
+ me.dom.querySelector('#alphaSlider').value = alpha * 100;
+ me.dom.querySelector('#alphaFill').value = alpha * 100;
- me.dom.querySelector('#colorSelector').style.display = 'block';
- } else {
- me.dom.querySelector('#colorSelector').style.display = 'none';
- }
+ me.dom.querySelector('#colorSelector').style.display = 'block';
+ } else {
+ me.dom.querySelector('#colorSelector').style.display = 'none';
}
- },
+ }
+ },
- /**
+ /**
* Create a new region, adding it to the ImageInfo structure and the current paper project
* @param {object} arg An object containing the name, uid and path of the region
* @param {number} imageNumber The number of the image section where the region will be created
* @returns {object} Reference to the new region that was added
*/
- newRegion: function (arg, imageNumber) {
- if( me.debug>1 ) { console.log("> newRegion"); }
- const reg = {};
+ newRegion: function (arg, imageNumber) {
+ if( me.debug>1 ) { console.log("> newRegion"); }
+ const reg = {};
- if(arg.uid) {
- reg.uid = arg.uid;
- } else {
- reg.uid = me.regionUID();
- }
+ if(arg.uid) {
+ reg.uid = arg.uid;
+ } else {
+ reg.uid = me.regionUID();
+ }
- if( arg.name ) {
- reg.name = arg.name;
- } else {
- reg.name = me.ontology.labels[me.currentLabelIndex].name;
- }
+ if( arg.name ) {
+ reg.name = arg.name;
+ } else {
+ reg.name = me.ontology.labels[me.currentLabelIndex].name;
+ }
- const color = me.regionColor(reg.name);
+ const color = me.regionColor(reg.name);
- if( arg.path ) {
- reg.path = arg.path;
- reg.path.strokeWidth = arg.path.strokeWidth ? arg.path.strokeWidth : me.config.defaultStrokeWidth;
- reg.path.strokeColor = arg.path.strokeColor ? arg.path.strokeColor : me.config.defaultStrokeColor;
- reg.path.strokeScaling = false;
- reg.path.fillColor = arg.path.fillColor ? arg.path.fillColor :'rgba(' + color.red + ',' + color.green + ',' + color.blue + ',' + me.config.defaultFillAlpha + ')';
- reg.path.selected = false;
- }
+ if( arg.path ) {
+ reg.path = arg.path;
+ reg.path.strokeWidth = arg.path.strokeWidth ? arg.path.strokeWidth : me.config.defaultStrokeWidth;
+ reg.path.strokeColor = arg.path.strokeColor ? arg.path.strokeColor : me.config.defaultStrokeColor;
+ reg.path.strokeScaling = false;
+ reg.path.fillColor = arg.path.fillColor ? arg.path.fillColor :'rgba(' + color.red + ',' + color.green + ',' + color.blue + ',' + me.config.defaultFillAlpha + ')';
+ reg.path.selected = false;
+ }
- if( typeof imageNumber === "undefined" ) {
- imageNumber = me.currentImage;
- }
- // if( imageNumber === me.currentImage ) {
- // // append region tag to regionList
- // const regionTag = me.regionTag(reg.name, reg.uid);
- // var el = me.dom.querySelector(regionTag);
- // me.dom.querySelector("#regionList").appendChild(el);
+ if( typeof imageNumber === "undefined" ) {
+ imageNumber = me.currentImage;
+ }
+ // if( imageNumber === me.currentImage ) {
+ // // append region tag to regionList
+ // const regionTag = me.regionTag(reg.name, reg.uid);
+ // var el = me.dom.querySelector(regionTag);
+ // me.dom.querySelector("#regionList").appendChild(el);
- // // handle single click on computers
- // el.click(me.singlePressOnRegion);
+ // // handle single click on computers
+ // el.click(me.singlePressOnRegion);
- // // handle double click on computers
- // el.dblclick(me.doublePressOnRegion);
+ // // handle double click on computers
+ // el.dblclick(me.doublePressOnRegion);
- // // handle single and double tap on touch devices
- // /**
- // * @todo it seems that a click event is also fired on touch devices, making this one redundant
- // */
+ // // handle single and double tap on touch devices
+ // /**
+ // * @todo it seems that a click event is also fired on touch devices, making this one redundant
+ // */
- // el.on("touchstart", me.handleRegionTap);
- // }
+ // el.on("touchstart", me.handleRegionTap);
+ // }
- // push the new region to the Regions array
- me.ImageInfo[imageNumber].Regions.push(reg);
+ // push the new region to the Regions array
+ me.ImageInfo[imageNumber].Regions.push(reg);
- // Select region name in list
- // me.selectRegion(reg);
+ // Select region name in list
+ // me.selectRegion(reg);
- return reg;
- },
+ return reg;
+ },
- /**
+ /**
* Remove region from current image. The image not directly removed, but marked for removal,
* so that it can be removed from the database.
* @param {object} reg The region to be removed
* @param {number} imageNumber The number of the image where the region will be removed
* @returns {void}
*/
- removeRegion: function (reg, imageNumber) {
- if( me.debug>1 ) { console.log("> removeRegion"); }
+ removeRegion: function (reg, imageNumber) {
+ if( me.debug>1 ) { console.log("> removeRegion"); }
- if( typeof imageNumber === "undefined" ) {
- imageNumber = me.currentImage;
- }
+ if( typeof imageNumber === "undefined" ) {
+ imageNumber = me.currentImage;
+ }
- // mark for removal
- me.ImageInfo[imageNumber].RegionsToRemove.push(reg.uid);
- // remove from Regions array
- me.ImageInfo[imageNumber].Regions.splice(me.ImageInfo[imageNumber].Regions.indexOf(reg), 1);
- // remove from paths
- reg.path.remove();
+ // mark for removal
+ me.ImageInfo[imageNumber].RegionsToRemove.push(reg.uid);
+ // remove from Regions array
+ me.ImageInfo[imageNumber].Regions.splice(me.ImageInfo[imageNumber].Regions.indexOf(reg), 1);
+ // remove from paths
+ reg.path.remove();
- },
+ },
- /**
+ /**
* @desc Find region by its name
* @param {string} name Name of the region from the ontology list
* @returns {object} The region
*/
- findRegionByName: function (name) {
- if( me.debug>1 ) { console.log("> findRegionByName"); }
+ findRegionByName: function (name) {
+ if( me.debug>1 ) { console.log("> findRegionByName"); }
- for( let i = 0; i < me.ImageInfo[me.currentImage].Regions.length; i += 1 ) {
- if( me.ImageInfo[me.currentImage].Regions[i].name === name ) {
- return me.ImageInfo[me.currentImage].Regions[i];
- }
+ for( let i = 0; i < me.ImageInfo[me.currentImage].Regions.length; i += 1 ) {
+ if( me.ImageInfo[me.currentImage].Regions[i].name === name ) {
+ return me.ImageInfo[me.currentImage].Regions[i];
}
- console.log("WARNING: Region with name " + name + " not found");
+ }
+ console.log("WARNING: Region with name " + name + " not found");
- return null;
- },
+ return null;
+ },
- /**
+ /**
* @param {array} o Array with ontology terms
* @returns {void}
*/
- appendRegionTagsFromOntology: function (o) {
- if( me.debug>1 ) { console.log("> appendRegionTagsFromOntology"); }
+ appendRegionTagsFromOntology: function (o) {
+ if( me.debug>1 ) { console.log("> appendRegionTagsFromOntology"); }
- for( let i = 0; i < o.length; i += 1 ) {
- if( o[i].parts ) {
- const el = document.createElement("div");
- el.textContent = o[i].name;
- me.dom.querySelector("#regionPicker").appendChild(el);
- me.appendRegionTagsFromOntology(o[i].parts);
- } else {
- const tag = me.regionTag(o[i].name);
- const el = me.dom.querySelector(tag);
- el.classList.add("ontology");
- me.dom.querySelector("#regionPicker").appendChild(el);
+ for( let i = 0; i < o.length; i += 1 ) {
+ if( o[i].parts ) {
+ const el = document.createElement("div");
+ el.textContent = o[i].name;
+ me.dom.querySelector("#regionPicker").appendChild(el);
+ me.appendRegionTagsFromOntology(o[i].parts);
+ } else {
+ const tag = me.regionTag(o[i].name);
+ const el = me.dom.querySelector(tag);
+ el.classList.add("ontology");
+ me.dom.querySelector("#regionPicker").appendChild(el);
- // handle single click on computers
- el.click(me.singlePressOnRegion);
+ // handle single click on computers
+ el.click(me.singlePressOnRegion);
- // handle double click on computers
- el.dblclick(me.doublePressOnRegion);
+ // handle double click on computers
+ el.dblclick(me.doublePressOnRegion);
- el.on("touchstart", me.handleRegionTap);
- }
+ el.on("touchstart", me.handleRegionTap);
}
- },
+ }
+ },
- /**
+ /**
* @desc Interaction: mouse and tap: If on a computer, it will send click event; if on tablet, it will send touch event.
* @param {object} event Event
* @returns {void}
*/
- clickHandler: function (event) {
- if( me.debug>1 ) { console.log("> clickHandler"); }
- event.stopHandlers = !me.navEnabled;
- },
+ clickHandler: function (event) {
+ if( me.debug>1 ) { console.log("> clickHandler"); }
+ event.stopHandlers = !me.navEnabled;
+ },
- /**
+ /**
* @param {object} event Event
* @returns {void}
*/
- pressHandler: function (event) {
- if( me.debug>1 ) { console.log("> pressHandler"); }
+ pressHandler: function (event) {
+ if( me.debug>1 ) { console.log("> pressHandler"); }
- if( !me.navEnabled ) {
- event.stopHandlers = true;
- me.mouseDown(event.originalEvent.layerX, event.originalEvent.layerY);
- }
- },
+ if( !me.navEnabled ) {
+ event.stopHandlers = true;
+ me.mouseDown(event.originalEvent.layerX, event.originalEvent.layerY);
+ }
+ },
- /**
+ /**
* @param {object} event Event
* @returns {void}
*/
- releaseHandler: function (event) {
- if( me.debug>1 ) { console.log("> releaseHandler"); }
+ releaseHandler: function (event) {
+ if( me.debug>1 ) { console.log("> releaseHandler"); }
- if( !me.navEnabled ) {
- event.stopHandlers = true;
- me.mouseUp();
- }
- },
+ if( !me.navEnabled ) {
+ event.stopHandlers = true;
+ me.mouseUp();
+ }
+ },
- /**
+ /**
* @param {object} event Event
* @returns {void}
*/
- dragHandler: function (event) {
- if( me.debug > 1 ) { console.log("> dragHandler"); }
+ dragHandler: function (event) {
+ if( me.debug > 1 ) { console.log("> dragHandler"); }
- if( !me.navEnabled ) {
- event.stopHandlers = true;
- me.mouseDrag(event.originalEvent.layerX, event.originalEvent.layerY, event.delta.x, event.delta.y);
- }
- },
+ if( !me.navEnabled ) {
+ event.stopHandlers = true;
+ me.mouseDrag(event.originalEvent.layerX, event.originalEvent.layerY, event.delta.x, event.delta.y);
+ }
+ },
- /**
+ /**
* @param {object} event Event
* @returns {void}
*/
- dragEndHandler: function (event) {
- if( me.debug>1 ) { console.log("> dragEndHandler"); }
+ dragEndHandler: function (event) {
+ if( me.debug>1 ) { console.log("> dragEndHandler"); }
- if( !me.navEnabled ) {
- event.stopHandlers = true;
- me.mouseUp();
- }
- },
+ if( !me.navEnabled ) {
+ event.stopHandlers = true;
+ me.mouseUp();
+ }
+ },
- /**
+ /**
* @param {object} ev Scroll event
* @returns {void}
*/
- scrollHandler: function (ev) {
- if( me.debug>1 ) { console.log("> scrollHandler") }
+ scrollHandler: function (ev) {
+ if( me.debug>1 ) { console.log("> scrollHandler"); }
- if( me.tools[me.selectedTool]
+ if( me.tools[me.selectedTool]
&& me.tools[me.selectedTool].scrollHandler ) {
- me.tools[me.selectedTool].scrollHandler(ev);
- }
- paper.view.draw();
- },
+ me.tools[me.selectedTool].scrollHandler(ev);
+ }
+ paper.view.draw();
+ },
- /**
+ /**
* @param {number} x X-coordinate for mouse down
* @param {number} y Y-coordinate for mouse down
* @returns {void}
*/
- mouseDown: function (x, y) {
- me.debugPrint("> mouseDown", 1);
+ mouseDown: function (x, y) {
+ me.debugPrint("> mouseDown", 1);
- me.mouseUndo = me.getUndo();
- const point = paper.view.viewToProject(new paper.Point(x, y));
+ me.mouseUndo = me.getUndo();
+ const point = paper.view.viewToProject(new paper.Point(x, y));
- me.handle = null;
+ me.handle = null;
- if( me.tools[me.selectedTool]
+ if( me.tools[me.selectedTool]
&& me.tools[me.selectedTool].mouseDown ) {
- me.tools[me.selectedTool].mouseDown(point);
- }
- paper.view.draw();
- },
+ me.tools[me.selectedTool].mouseDown(point);
+ }
+ paper.view.draw();
+ },
- /**
+ /**
* @param {number} x X-coordinate where drag event started
* @param {number} y Y-coordinate where drag event started
* @param {number} dx Size of the drag step in the X axis
* @param {number} dy Size of the drag step in the Y axis
* @returns {void}
*/
- mouseDrag: function (x, y, dx, dy) {
- if( me.debug>2 ) console.log("> mouseDrag");
-
- // transform screen coordinate into world coordinate
- const point = paper.view.viewToProject(new paper.Point(x, y));
-
- // transform screen delta into world delta
- const orig = paper.view.viewToProject(new paper.Point(0, 0));
- const dpoint = paper.view.viewToProject(new paper.Point(dx, dy));
- dpoint.x -= orig.x;
- dpoint.y -= orig.y;
- if( me.handle ) {
- me.handle.x += point.x-me.handle.point.x;
- me.handle.y += point.y-me.handle.point.y;
- me.handle.point = point;
- me.commitMouseUndo();
- } else if (me.tools[me.selectedTool] && me.tools[me.selectedTool].mouseDrag) {
- me.tools[me.selectedTool].mouseDrag(point, dpoint);
- }
- paper.view.draw();
- },
+ mouseDrag: function (x, y, dx, dy) {
+ if( me.debug>2 ) { console.log("> mouseDrag"); }
+
+ // transform screen coordinate into world coordinate
+ const point = paper.view.viewToProject(new paper.Point(x, y));
+
+ // transform screen delta into world delta
+ const orig = paper.view.viewToProject(new paper.Point(0, 0));
+ const dpoint = paper.view.viewToProject(new paper.Point(dx, dy));
+ dpoint.x -= orig.x;
+ dpoint.y -= orig.y;
+ if( me.handle ) {
+ me.handle.x += point.x-me.handle.point.x;
+ me.handle.y += point.y-me.handle.point.y;
+ me.handle.point = point;
+ me.commitMouseUndo();
+ } else if (me.tools[me.selectedTool] && me.tools[me.selectedTool].mouseDrag) {
+ me.tools[me.selectedTool].mouseDrag(point, dpoint);
+ }
+ paper.view.draw();
+ },
- /**
+ /**
* @returns {void}
*/
- mouseUp: function () {
- if( me.debug>1 ) { console.log("> mouseUp"); }
- if(me.tools[me.selectedTool] && me.tools[me.selectedTool].mouseUp) {
- me.tools[me.selectedTool].mouseUp();
- }
- },
+ mouseUp: function () {
+ if( me.debug>1 ) { console.log("> mouseUp"); }
+ if(me.tools[me.selectedTool] && me.tools[me.selectedTool].mouseUp) {
+ me.tools[me.selectedTool].mouseUp();
+ }
+ },
- /**
+ /**
* @desc Simplify the region path
* @returns {void}
*/
- simplify: function () {
- if( me.region !== null ) {
- if( me.debug>1 ) { console.log("> simplifying region path"); }
+ simplify: function () {
+ if( me.region !== null ) {
+ if( me.debug>1 ) { console.log("> simplifying region path"); }
- const origSegments = me.region.path.segments.length;
- me.region.path.simplify();
- const finalSegments = me.region.path.segments.length;
- console.log( parseInt(finalSegments/origSegments*100, 10) + "% segments conserved" );
- paper.view.draw();
- }
- },
+ const origSegments = me.region.path.segments.length;
+ me.region.path.simplify();
+ const finalSegments = me.region.path.segments.length;
+ console.log( parseInt(finalSegments/origSegments*100, 10) + "% segments conserved" );
+ paper.view.draw();
+ }
+ },
- /**
+ /**
* @desc Set picked color & alpha
* @returns {void}
*/
- setRegionColor: function () {
- const reg = me.currentColorRegion;
- const hexColor = me.dom.querySelector('#fillColorPicker').value;
- const red = parseInt( hexColor.substring(1, 3), 16 );
- const green = parseInt( hexColor.substring(3, 5), 16 );
- const blue = parseInt( hexColor.substring(5, 7), 16 );
+ setRegionColor: function () {
+ const reg = me.currentColorRegion;
+ const hexColor = me.dom.querySelector('#fillColorPicker').value;
+ const red = parseInt( hexColor.substring(1, 3), 16 );
+ const green = parseInt( hexColor.substring(3, 5), 16 );
+ const blue = parseInt( hexColor.substring(5, 7), 16 );
- reg.path.fillColor.red = red / 255;
- reg.path.fillColor.green = green / 255;
- reg.path.fillColor.blue = blue / 255;
- reg.path.fillColor.alpha = me.dom.querySelector('#alphaSlider').value / 100;
+ reg.path.fillColor.red = red / 255;
+ reg.path.fillColor.green = green / 255;
+ reg.path.fillColor.blue = blue / 255;
+ reg.path.fillColor.alpha = me.dom.querySelector('#alphaSlider').value / 100;
- // update region tag
- me.dom
- .querySelector(".region-tag#" + reg.uid + ">.region-color")
- .style['background-color'] ='rgba(' + red + ',' + green + ',' + blue + ',0.67)'
+ // update region tag
+ me.dom
+ .querySelector(".region-tag#" + reg.uid + ">.region-color")
+ .style['background-color'] ='rgba(' + red + ',' + green + ',' + blue + ',0.67)';
- // update stroke color
- const {selectedIndex} = me.dom.querySelector('#selectStrokeColor');
- reg.path.strokeColor = ["black", "white", "red", "green", "blue", "yellow"][selectedIndex];
+ // update stroke color
+ const {selectedIndex} = me.dom.querySelector('#selectStrokeColor');
+ reg.path.strokeColor = ["black", "white", "red", "green", "blue", "yellow"][selectedIndex];
- me.dom.querySelector('#colorSelector').style.display = 'none';
- },
+ me.dom.querySelector('#colorSelector').style.display = 'none';
+ },
- /**
+ /**
* @desc Update all values on the fly
* @param {number} value The value assigned to the color picker
* @returns {void}
*/
- onFillColorPicker: function (value) {
- me.dom.querySelector('#fillColorPicker').value = value;
- const reg = me.currentColorRegion;
- const hexColor = me.dom.querySelector('#fillColorPicker').value;
- const red = parseInt( hexColor.substring(1, 3), 16 );
- const green = parseInt( hexColor.substring(3, 5), 16);
- const blue = parseInt( hexColor.substring(5, 7), 16);
- reg.path.fillColor.red = red / 255;
- reg.path.fillColor.green = green / 255;
- reg.path.fillColor.blue = blue / 255;
- reg.path.fillColor.alpha = me.dom.querySelector('#alphaSlider').value / 100;
- paper.view.draw();
- },
-
- /**
+ onFillColorPicker: function (value) {
+ me.dom.querySelector('#fillColorPicker').value = value;
+ const reg = me.currentColorRegion;
+ const hexColor = me.dom.querySelector('#fillColorPicker').value;
+ const red = parseInt( hexColor.substring(1, 3), 16 );
+ const green = parseInt( hexColor.substring(3, 5), 16);
+ const blue = parseInt( hexColor.substring(5, 7), 16);
+ reg.path.fillColor.red = red / 255;
+ reg.path.fillColor.green = green / 255;
+ reg.path.fillColor.blue = blue / 255;
+ reg.path.fillColor.alpha = me.dom.querySelector('#alphaSlider').value / 100;
+ paper.view.draw();
+ },
+
+ /**
* @returns {void}
*/
- onSelectStrokeColor: function () {
- const reg = me.currentColorRegion;
- const {selectedIndex} = me.dom.querySelector('#selectStrokeColor');
- reg.path.strokeColor = ["black", "white", "red", "green", "blue", "yellow"][selectedIndex];
- paper.view.draw();
- },
+ onSelectStrokeColor: function () {
+ const reg = me.currentColorRegion;
+ const {selectedIndex} = me.dom.querySelector('#selectStrokeColor');
+ reg.path.strokeColor = ["black", "white", "red", "green", "blue", "yellow"][selectedIndex];
+ paper.view.draw();
+ },
- /**
+ /**
* @param {number} value The value assigned to alpha slider
* @returns {void}
*/
- onAlphaSlider: function (value) {
- me.dom.querySelector('#alphaFill').value = value;
- const reg = me.currentColorRegion;
- reg.path.fillColor.alpha = me.dom.querySelector('#alphaSlider').value / 100;
- paper.view.draw();
- },
+ onAlphaSlider: function (value) {
+ me.dom.querySelector('#alphaFill').value = value;
+ const reg = me.currentColorRegion;
+ reg.path.fillColor.alpha = me.dom.querySelector('#alphaSlider').value / 100;
+ paper.view.draw();
+ },
- /**
+ /**
* @param {number} value The value assigned to alpha input field
* @returns {void}
*/
- onAlphaInput: function (value) {
- me.dom.querySelector('#alphaSlider').value = value;
- const reg = me.currentColorRegion;
- reg.path.fillColor.alpha = me.dom.querySelector('#alphaSlider').value / 100;
- paper.view.draw();
- },
+ onAlphaInput: function (value) {
+ me.dom.querySelector('#alphaSlider').value = value;
+ const reg = me.currentColorRegion;
+ reg.path.fillColor.alpha = me.dom.querySelector('#alphaSlider').value / 100;
+ paper.view.draw();
+ },
- /**
+ /**
* @returns {void}
*/
- onStrokeWidthDec: function () {
- const reg = me.currentColorRegion;
- reg.path.strokeWidth = Math.max(me.region.path.strokeWidth - 1, 1);
- paper.view.draw();
- },
+ onStrokeWidthDec: function () {
+ const reg = me.currentColorRegion;
+ reg.path.strokeWidth = Math.max(me.region.path.strokeWidth - 1, 1);
+ paper.view.draw();
+ },
- /**
+ /**
* @returns {void}
*/
- onStrokeWidthInc: function () {
- const reg = me.currentColorRegion;
- reg.path.strokeWidth = Math.min(me.region.path.strokeWidth + 1, 10);
- paper.view.draw();
- },
+ onStrokeWidthInc: function () {
+ const reg = me.currentColorRegion;
+ reg.path.strokeWidth = Math.min(me.region.path.strokeWidth + 1, 10);
+ paper.view.draw();
+ },
- /*** UNDO ***/
+ /*** UNDO ***/
- /**
+ /**
* @desc Command to actually perform an undo.
* @returns {void}
*/
- cmdUndo: function () {
- if( me.UndoStack.length > 0 ) {
- const redoInfo = me.getUndo();
- const undoInfo = me.UndoStack.pop();
- me.applyUndo(undoInfo);
- me.RedoStack.push(redoInfo);
- paper.view.draw();
- }
- },
+ cmdUndo: function () {
+ if( me.UndoStack.length > 0 ) {
+ const redoInfo = me.getUndo();
+ const undoInfo = me.UndoStack.pop();
+ me.applyUndo(undoInfo);
+ me.RedoStack.push(redoInfo);
+ paper.view.draw();
+ }
+ },
- /**
+ /**
* @desc Command to actually perform a redo.
* @returns {void}
*/
- cmdRedo: function () {
- if( me.RedoStack.length > 0 ) {
- const undoInfo = me.getUndo();
- const redoInfo = me.RedoStack.pop();
- me.applyUndo(redoInfo);
- me.UndoStack.push(undoInfo);
- paper.view.draw();
- }
- },
+ cmdRedo: function () {
+ if( me.RedoStack.length > 0 ) {
+ const undoInfo = me.getUndo();
+ const redoInfo = me.RedoStack.pop();
+ me.applyUndo(redoInfo);
+ me.UndoStack.push(undoInfo);
+ paper.view.draw();
+ }
+ },
- /**
+ /**
* @desc Return a complete copy of the current state as an undo object.
* @returns {Object} The undo object
*/
- getUndo: function () {
- const undo = {
- imageNumber: me.currentImage,
- regions: [],
- drawingPolygonFlag: me.drawingPolygonFlag
+ getUndo: function () {
+ const undo = {
+ imageNumber: me.currentImage,
+ regions: [],
+ drawingPolygonFlag: me.drawingPolygonFlag
+ };
+ const info = me.ImageInfo[me.currentImage].Regions;
+
+ for( let i = 0; i < info.length; i += 1 ) {
+ const el = {
+ json: JSON.parse(info[i].path.exportJSON()),
+ name: info[i].name,
+ uid: info[i].uid,
+ selected: info[i].path.selected,
+ fullySelected: info[i].path.fullySelected
};
- const info = me.ImageInfo[me.currentImage].Regions;
-
- for( let i = 0; i < info.length; i += 1 ) {
- const el = {
- json: JSON.parse(info[i].path.exportJSON()),
- name: info[i].name,
- uid: info[i].uid,
- selected: info[i].path.selected,
- fullySelected: info[i].path.fullySelected
- };
- undo.regions.push(el);
- }
+ undo.regions.push(el);
+ }
- return undo;
- },
+ return undo;
+ },
- /**
+ /**
* @desc Save an undo object. This has the side-effect of initializing the redo stack.
* @param {object} undoInfo The undo info object
* @returns {void}
*/
- saveUndo: function (undoInfo) {
- me.UndoStack.push(undoInfo);
- me.RedoStack = [];
- },
+ saveUndo: function (undoInfo) {
+ me.UndoStack.push(undoInfo);
+ me.RedoStack = [];
+ },
- /**
+ /**
* @param {number} imageNumber The image number
* @returns {void}
*/
- setImage: function (imageNumber) {
- if( me.debug>1 ) { console.log("> setImage"); }
- const index = me.imageOrder.indexOf(imageNumber);
+ setImage: function (imageNumber) {
+ if( me.debug>1 ) { console.log("> setImage"); }
+ const index = me.imageOrder.indexOf(imageNumber);
- // update image slider
- me.updateSliderValue(index);
+ // update image slider
+ me.updateSliderValue(index);
- //update url
- me.updateURL(index);
+ //update url
+ me.updateURL(index);
- me.loadImage(me.imageOrder[index]);
- },
+ me.loadImage(me.imageOrder[index]);
+ },
- /**
+ /**
* @desc Restore the current state from an undo object.
* @param {object} undo The undo object to apply
* @returns {void}
*/
- applyUndo: function (undo) {
- // go to the image involved
- if( undo.imageNumber !== me.currentImage ) {
- me.setImage(undo.imageNumber);
- }
+ applyUndo: function (undo) {
+ // go to the image involved
+ if( undo.imageNumber !== me.currentImage ) {
+ me.setImage(undo.imageNumber);
+ }
- // remove its current contents
- const {Regions: regions} = me.ImageInfo[undo.imageNumber];
- while( regions.length > 0 ) {
- me.removeRegion(regions[0], undo.imageNumber);
- }
+ // remove its current contents
+ const {Regions: regions} = me.ImageInfo[undo.imageNumber];
+ while( regions.length > 0 ) {
+ me.removeRegion(regions[0], undo.imageNumber);
+ }
- // add contents from the undo object
- me.region = null;
- for( let i = 0; i < undo.regions.length; i += 1 ) {
- const el = undo.regions[i];
- const path = me._pathFromJSON(el.json);
- // add to the correct project activeLayer, which may not be the current one
- paper.project.activeLayer.addChild(path);
-
- const reg = me.newRegion({
- name: el.name,
- uid: el.uid,
- path: path
- }, undo.imageNumber);
-
- // here order matters: if fully selected is set after selected, partially selected paths will be incorrect
- reg.path.fullySelected = el.fullySelected;
- reg.path.selected = el.selected;
- if( el.selected ) {
- if( me.region === null ) {
- me.region = reg;
- } else {
- console.log("WARNING: This should not happen. Are two regions selected?");
- }
+ // add contents from the undo object
+ me.region = null;
+ for( let i = 0; i < undo.regions.length; i += 1 ) {
+ const el = undo.regions[i];
+ const path = me._pathFromJSON(el.json);
+ // add to the correct project activeLayer, which may not be the current one
+ paper.project.activeLayer.addChild(path);
+
+ const reg = me.newRegion({
+ name: el.name,
+ uid: el.uid,
+ path: path
+ }, undo.imageNumber);
+
+ // here order matters: if fully selected is set after selected, partially selected paths will be incorrect
+ reg.path.fullySelected = el.fullySelected;
+ reg.path.selected = el.selected;
+ if( el.selected ) {
+ if( me.region === null ) {
+ me.region = reg;
+ } else {
+ console.log("WARNING: This should not happen. Are two regions selected?");
}
}
+ }
- if(undo.callback && typeof undo.callback === 'function') {
- undo.callback();
- }
+ if(undo.callback && typeof undo.callback === 'function') {
+ undo.callback();
+ }
- /**
+ /**
* @todo This line produces an error when the undo object is undefined. However, the code seems to work fine without this line. Check what the line was supposed to do
*/
- // me.drawingPolygonFlag = me.undo.drawingPolygonFlag;
- },
+ // me.drawingPolygonFlag = me.undo.drawingPolygonFlag;
+ },
- /**
+ /**
* @desc If we have actually made a change with a mouse operation, commit the undo information.
* @returns {void}
*/
- commitMouseUndo: function () {
- if( me.mouseUndo !== null ) {
- me.saveUndo(me.mouseUndo);
- me.mouseUndo = null;
- }
- },
+ commitMouseUndo: function () {
+ if( me.mouseUndo !== null ) {
+ me.saveUndo(me.mouseUndo);
+ me.mouseUndo = null;
+ }
+ },
- /**
+ /**
* @param {string} prevTool Name of the previously selected tool
* @returns {void}
*/
- backToPreviousTool: function (prevTool) {
- setTimeout(function() {
- me.selectedTool = prevTool;
- // me.selectTool();
- }, 500);
- },
+ backToPreviousTool: function (prevTool) {
+ setTimeout(function() {
+ me.selectedTool = prevTool;
+ // me.selectTool();
+ }, 500);
+ },
- /**
+ /**
* @returns {void}
*/
- backToSelect: function () {
- setTimeout(function() {
- me.selectedTool = "select";
- // me.selectTool();
- }, 500);
- },
+ backToSelect: function () {
+ setTimeout(function() {
+ me.selectedTool = "select";
+ // me.selectTool();
+ }, 500);
+ },
- /**
+ /**
* @desc This function deletes the currently selected object.
* @returns {void}
*/
- cmdDeleteSelected: function () {
- const undoInfo = me.getUndo();
- for( const region of me.ImageInfo[me.currentImage].Regions ) {
- if( region.path.selected ) {
- me.removeRegion(region);
- me.saveUndo(undoInfo);
- paper.view.draw();
- break;
- }
+ cmdDeleteSelected: function () {
+ const undoInfo = me.getUndo();
+ for( const region of me.ImageInfo[me.currentImage].Regions ) {
+ if( region.path.selected ) {
+ me.removeRegion(region);
+ me.saveUndo(undoInfo);
+ paper.view.draw();
+ break;
}
- },
+ }
+ },
- /**
+ /**
* @returns {void}
*/
- cmdPaste: function () {
- if( me.copyRegion !== null ) {
- const undoInfo = me.getUndo();
- me.saveUndo(undoInfo);
- if(me.debug) { console.log( "paste " + me.copyRegion.name ); }
- if( me.findRegionByName(me.copyRegion.name) ) {
- // me.copyRegion.name += " Copy";
- }
+ cmdPaste: function () {
+ if( me.copyRegion !== null ) {
+ const undoInfo = me.getUndo();
+ me.saveUndo(undoInfo);
+ if(me.debug) { console.log( "paste " + me.copyRegion.name ); }
+ if( me.findRegionByName(me.copyRegion.name) ) {
+ // me.copyRegion.name += " Copy";
+ }
- const reg = JSON.parse(JSON.stringify(me.copyRegion));
- reg.path = me._pathFromJSON(JSON.parse(me.copyRegion.path));
- reg.path.fullySelected = true;
+ const reg = JSON.parse(JSON.stringify(me.copyRegion));
+ reg.path = me._pathFromJSON(JSON.parse(me.copyRegion.path));
+ reg.path.fullySelected = true;
- const color = me.regionColor(reg.name);
- reg.path.fillColor = 'rgba(' + color.red + ',' + color.green + ',' + color.blue + ',0.5)';
+ const color = me.regionColor(reg.name);
+ reg.path.fillColor = 'rgba(' + color.red + ',' + color.green + ',' + color.blue + ',0.5)';
- me.newRegion({
- name: me.copyRegion.name,
- uid: me.regionUID(),
- path: reg.path
- });
- }
- paper.view.draw();
- },
+ me.newRegion({
+ name: me.copyRegion.name,
+ uid: me.regionUID(),
+ path: reg.path
+ });
+ }
+ paper.view.draw();
+ },
- /**
+ /**
* @returns {void}
*/
- cmdCopy: function () {
- if(me.debug>1) { console.log( "< copy " + me.copyRegion.name ); }
- if( me.region !== null ) {
- const json = me.region.path.exportJSON();
- me.copyRegion = JSON.parse(JSON.stringify(me.region));
- me.copyRegion.path = json;
- }
- },
+ cmdCopy: function () {
+ if(me.debug>1) { console.log( "< copy " + me.copyRegion.name ); }
+ if( me.region !== null ) {
+ const json = me.region.path.exportJSON();
+ me.copyRegion = JSON.parse(JSON.stringify(me.region));
+ me.copyRegion.path = json;
+ }
+ },
- // /**
- // * @returns {void}
- // */
- // selectTool: function () {
- // if( me.debug>1 ) { console.log("> selectTool"); }
+ // /**
+ // * @returns {void}
+ // */
+ // selectTool: function () {
+ // if( me.debug>1 ) { console.log("> selectTool"); }
- // me.dom.querySelector("img.button1").classList.remove("selected");
- // me.dom.querySelector("img.button1#" + me.selectedTool).classList.add("selected");
- // },
+ // me.dom.querySelector("img.button1").classList.remove("selected");
+ // me.dom.querySelector("img.button1#" + me.selectedTool).classList.add("selected");
+ // },
- clickTool: function (tool) {
- const prevTool = me.selectedTool;
+ clickTool: function (tool) {
+ const prevTool = me.selectedTool;
- if( me.tools[prevTool] && me.tools[prevTool].onDeselect ) {
- me.tools[prevTool].onDeselect();
- }
+ if( me.tools[prevTool] && me.tools[prevTool].onDeselect ) {
+ me.tools[prevTool].onDeselect();
+ }
- me.selectedTool = tool;
- // me.selectTool();
+ me.selectedTool = tool;
+ // me.selectTool();
- if( me.tools[me.selectedTool] && me.tools[me.selectedTool].click ) {
- me.tools[me.selectedTool].click(prevTool);
- }
- },
+ if( me.tools[me.selectedTool] && me.tools[me.selectedTool].click ) {
+ me.tools[me.selectedTool].click(prevTool);
+ }
+ },
- /**
+ /**
* @returns {void}
*/
- toolSelection: function () {
- if( me.debug>1 ) { console.log("> toolSelection"); }
- const tool = this.id;
- me.clickTool(tool);
- },
+ toolSelection: function () {
+ if( me.debug>1 ) { console.log("> toolSelection"); }
+ const tool = this.id;
+ me.clickTool(tool);
+ },
- /*
+ /*
Annotation storage
*/
- /**
+ /**
* @desc Load SVG overlay from microdrawDB
* @returns {Promise} A promise to return an array of paths of the current section.
* @default returns an empty array. Can/should be overwritten in save.js. Users can use their own save.js for different backend.
*/
- microdrawDBLoad: function () {
- return new Promise(function(resolve) {
- if( me.debug>1 ) { console.log("> default microdrawDBLoad promise, returning an empty array. Overwrite Microdraw.microdrawDBLoad() to load annotations."); }
- resolve([]);
- });
- },
+ microdrawDBLoad: function () {
+ return new Promise(function(resolve) {
+ if( me.debug>1 ) { console.log("> default microdrawDBLoad promise, returning an empty array. Overwrite Microdraw.microdrawDBLoad() to load annotations."); }
+ resolve([]);
+ });
+ },
- /**
+ /**
* @returns {void}
*/
- save: function () {
- if( me.debug ) { console.log("> save"); }
-
- const obj = {};
- obj.Regions = [];
- for( let i = 0; i < me.ImageInfo[me.currentImage].Regions.length; i += 1 ) {
- const el = {};
- el.path = me.ImageInfo[me.currentImage].Regions[i].path.exportJSON();
- el.name = me.ImageInfo[me.currentImage].Regions[i].name;
- el.uid = me.ImageInfo[me.currentImage].Regions[i].uid;
- obj.Regions.push(el);
- }
- localStorage.Microdraw = JSON.stringify(obj);
+ save: function () {
+ if( me.debug ) { console.log("> save"); }
+
+ const obj = {};
+ obj.Regions = [];
+ for( let i = 0; i < me.ImageInfo[me.currentImage].Regions.length; i += 1 ) {
+ const el = {};
+ el.path = me.ImageInfo[me.currentImage].Regions[i].path.exportJSON();
+ el.name = me.ImageInfo[me.currentImage].Regions[i].name;
+ el.uid = me.ImageInfo[me.currentImage].Regions[i].uid;
+ obj.Regions.push(el);
+ }
+ localStorage.Microdraw = JSON.stringify(obj);
- if( me.debug>1 ) {
- console.log("+ saved regions:", me.ImageInfo[me.currentImage].Regions.length);
- }
- },
+ if( me.debug>1 ) {
+ console.log("+ saved regions:", me.ImageInfo[me.currentImage].Regions.length);
+ }
+ },
- /**
+ /**
* @returns {void}
*/
- load: function () {
- if( me.debug>1 ) { console.log("> load"); }
-
- if( localStorage.Microdraw ) {
- console.log("Loading data from localStorage");
- const obj = JSON.parse(localStorage.Microdraw);
- for( let i = 0; i < obj.Regions.length; i += 1 ) {
- me.newRegion({
- name: obj.Regions[i].name,
- uid: obj.Regions[i].uid,
- path: me._pathFromJSON(obj.Regions[i].path)
- });
- }
- paper.view.draw();
+ load: function () {
+ if( me.debug>1 ) { console.log("> load"); }
+
+ if( localStorage.Microdraw ) {
+ console.log("Loading data from localStorage");
+ const obj = JSON.parse(localStorage.Microdraw);
+ for( let i = 0; i < obj.Regions.length; i += 1 ) {
+ me.newRegion({
+ name: obj.Regions[i].name,
+ uid: obj.Regions[i].uid,
+ path: me._pathFromJSON(obj.Regions[i].path)
+ });
}
- },
+ paper.view.draw();
+ }
+ },
- /***5
+ /***5
Initialisation
*/
- /**
+ /**
* @param {number} imageNumber The image number
* @returns {void}
*/
- loadImage: function (imageNumber) {
- if( me.debug>1 ) { console.log("> loadImage(" + imageNumber + ")"); }
+ loadImage: function (imageNumber) {
+ if( me.debug>1 ) { console.log("> loadImage(" + imageNumber + ")"); }
- // when load a new image, deselect any currently selecting regions
- // n.b. this needs to be called before me.currentImage is set
- me.selectRegion(null);
+ // when load a new image, deselect any currently selecting regions
+ // n.b. this needs to be called before me.currentImage is set
+ me.selectRegion(null);
- // save previous image for some (later) cleanup
- me.prevImage = me.currentImage;
+ // save previous image for some (later) cleanup
+ me.prevImage = me.currentImage;
- // set current image to new image
- me.currentImage = imageNumber;
+ // set current image to new image
+ me.currentImage = imageNumber;
- // display slice number
- me.dom.querySelector("#slice-number").innerHTML = `Slice ${imageNumber}`;
+ // display slice number
+ me.dom.querySelector("#slice-number").innerHTML = `Slice ${imageNumber}`;
- me.viewer.open(me.ImageInfo[me.currentImage].source);
- },
+ me.viewer.open(me.ImageInfo[me.currentImage].source);
+ },
- /**
+ /**
* @returns {void}
*/
- loadNextImage: function () {
- if( me.debug>1 ) { console.log("> loadNextImage"); }
- const index = me.imageOrder.indexOf(me.currentImage);
- const nextIndex = (index + 1) % me.imageOrder.length;
+ loadNextImage: function () {
+ if( me.debug>1 ) { console.log("> loadNextImage"); }
+ const index = me.imageOrder.indexOf(me.currentImage);
+ const nextIndex = (index + 1) % me.imageOrder.length;
- // update image slider
- me.updateSliderValue(nextIndex);
+ // update image slider
+ me.updateSliderValue(nextIndex);
- // update URL
- me.updateURL(nextIndex);
+ // update URL
+ me.updateURL(nextIndex);
- me.loadImage(me.imageOrder[nextIndex]);
- },
+ me.loadImage(me.imageOrder[nextIndex]);
+ },
- /**
+ /**
* @returns {void}
*/
- loadPreviousImage: function () {
- if(me.debug>1) { console.log("> loadPrevImage"); }
- const index = me.imageOrder.indexOf(me.currentImage);
- const previousIndex = ((index - 1 >= 0)? index - 1 : me.imageOrder.length - 1 );
+ loadPreviousImage: function () {
+ if(me.debug>1) { console.log("> loadPrevImage"); }
+ const index = me.imageOrder.indexOf(me.currentImage);
+ const previousIndex = ((index - 1 >= 0)? index - 1 : me.imageOrder.length - 1 );
- // update image slider
- me.updateSliderValue(previousIndex);
+ // update image slider
+ me.updateSliderValue(previousIndex);
- // update URL
- me.updateURL(previousIndex);
+ // update URL
+ me.updateURL(previousIndex);
- me.loadImage(me.imageOrder[previousIndex]);
- },
+ me.loadImage(me.imageOrder[previousIndex]);
+ },
- /**
+ /**
* @returns {void}
*/
- resizeAnnotationOverlay: function () {
- if( me.debug>1 ) { console.log("> resizeAnnotationOverlay"); }
-
- const width = me.dom.querySelector("#paperjs-container").offsetWidth;
- const height = me.dom.querySelector("#paperjs-container").offsetHeight;
- me.dom.querySelector("canvas.overlay").offsetWidth = width;
- me.dom.querySelector("canvas.overlay").offsetHeight = height;
- paper.view.viewSize = [
- width,
- height
- ];
- me.transform();
- },
-
- _convertDBAnnotationsToRegions: (data) => {
- const regions = [];
- let path;
- for( let i = 0; i < data.length; i += 1 ) {
- const json = data[i].annotation.path;
- const [type] = json;
- const reg = {
- name: data[i].annotation.name,
- uid: data[i].annotation.uid
- };
- switch(type) {
- case 'Path': {
- path = me._pathFromJSON(json);
- path.remove();
- break;
- }
- case 'CompoundPath': {
- path = new paper.CompoundPath();
- path.importJSON(json);
- path.remove();
- break;
- }
- default:
- // catch future path types
- path = me._pathFromJSON(json);
- path.remove();
- }
- reg.path = path;
- regions.push(reg);
+ resizeAnnotationOverlay: function () {
+ if( me.debug>1 ) { console.log("> resizeAnnotationOverlay"); }
+
+ const width = me.dom.querySelector("#paperjs-container").offsetWidth;
+ const height = me.dom.querySelector("#paperjs-container").offsetHeight;
+ me.dom.querySelector("canvas.overlay").offsetWidth = width;
+ me.dom.querySelector("canvas.overlay").offsetHeight = height;
+ paper.view.viewSize = [
+ width,
+ height
+ ];
+ me.transform();
+ },
+
+ _convertDBAnnotationsToRegions: (data) => {
+ const regions = [];
+ let path;
+ for( let i = 0; i < data.length; i += 1 ) {
+ const json = data[i].annotation.path;
+ const [type] = json;
+ const reg = {
+ name: data[i].annotation.name,
+ uid: data[i].annotation.uid
+ };
+ switch(type) {
+ case 'Path': {
+ path = me._pathFromJSON(json);
+ path.remove();
+ break;
}
- return regions;
- },
-
- _addRegionsToCurrentImage: function(regions) {
- for(const region of regions) {
- // regions are added to the ImageInfo[currentImage];
- me.newRegion(region);
+ case 'CompoundPath': {
+ path = new paper.CompoundPath();
+ path.importJSON(json);
+ path.remove();
+ break;
}
-
- // if image has no hash, save one
- me.ImageInfo[me.currentImage].Hash = regions.Hash ? regions.Hash : me.sectionHash(me.ImageInfo[me.currentImage]);
- },
-
- _drawRegionsInPaper: function(regions) {
- for(const region of regions) {
- paper.project.activeLayer.addChild(region.path);
+ default:
+ // catch future path types
+ path = me._pathFromJSON(json);
+ path.remove();
}
- me._resizePaperViewToMatchContainer();
- me.transform();
- paper.view.draw();
- },
-
- _resizePaperViewToMatchContainer: () => {
- const width = me.dom.querySelector("#paperjs-container").offsetWidth;
- const height = me.dom.querySelector("#paperjs-container").offsetHeight;
- paper.view.viewSize = [
- width,
- height
- ];
- paper.settings.handleSize = 10;
- // me.updateRegionList();
- paper.view.draw();
- },
+ reg.path = path;
+ regions.push(reg);
+ }
- _createCanvasAndAddToPaper: () => {
- const canvas = document.createElement("canvas");
- canvas.classList.add("overlay");
- canvas.id = me.currentImage;
- me.dom.querySelector("#paperjs-container").appendChild(canvas);
+ return regions;
+ },
- // create project
- paper.setup(canvas);
- },
+ _addRegionsToCurrentImage: function(regions) {
+ for(const region of regions) {
+ // regions are added to the ImageInfo[currentImage];
+ me.newRegion(region);
+ }
- /**
+ // if image has no hash, save one
+ me.ImageInfo[me.currentImage].Hash = regions.Hash ? regions.Hash : me.sectionHash(me.ImageInfo[me.currentImage]);
+ },
+
+ _drawRegionsInPaper: function(regions) {
+ for(const region of regions) {
+ paper.project.activeLayer.addChild(region.path);
+ }
+ me._resizePaperViewToMatchContainer();
+ me.transform();
+ paper.view.draw();
+ },
+
+ _resizePaperViewToMatchContainer: () => {
+ const width = me.dom.querySelector("#paperjs-container").offsetWidth;
+ const height = me.dom.querySelector("#paperjs-container").offsetHeight;
+ paper.view.viewSize = [
+ width,
+ height
+ ];
+ paper.settings.handleSize = 10;
+ // me.updateRegionList();
+ paper.view.draw();
+ },
+
+ _createCanvasAndAddToPaper: () => {
+ const canvas = document.createElement("canvas");
+ canvas.classList.add("overlay");
+ canvas.id = me.currentImage;
+ me.dom.querySelector("#paperjs-container").appendChild(canvas);
+
+ // create project
+ paper.setup(canvas);
+ },
+
+ /**
* @returns {void}
*/
- initAnnotationOverlay: async () => {
- if( me.debug>1 ) { console.log("> initAnnotationOverlay"); }
+ initAnnotationOverlay: async () => {
+ if( me.debug>1 ) { console.log("> initAnnotationOverlay"); }
- // do not start loading a new annotation if a previous one is still being loaded
- if(me.annotationLoadingFlag === true) {
- return;
- }
+ // do not start loading a new annotation if a previous one is still being loaded
+ if(me.annotationLoadingFlag === true) {
+ return;
+ }
- // change current section index (for loading and saving)
- me.section = me.currentImage;
- me.fileID = `${me.source}`;
+ // change current section index (for loading and saving)
+ me.section = me.currentImage;
+ me.fileID = `${me.source}`;
- paper.project.activeLayer.removeChildren();
+ paper.project.activeLayer.removeChildren();
- let regions;
- if(me.ImageInfo[me.currentImage].Regions.length > 0) {
- ({Regions: regions} = me.ImageInfo[me.currentImage]);
- } else {
- // first time this section is accessed: create its canvas, project,
- // and load its regions from the database
- // me._createCanvasAndAddToPaper();
-
- // load regions from database
- if( me.config.useDatabase ) {
- let data;
- try {
- data = await me.microdrawDBLoad();
- } catch(err) {
- throw new Error(err);
- }
- regions = me._convertDBAnnotationsToRegions(data);
- me._addRegionsToCurrentImage(regions);
- }
+ let regions;
+ if(me.ImageInfo[me.currentImage].Regions.length > 0) {
+ ({Regions: regions} = me.ImageInfo[me.currentImage]);
+ } else {
+ // first time this section is accessed: create its canvas, project,
+ // and load its regions from the database
+ // me._createCanvasAndAddToPaper();
+
+ // load regions from database
+ // eslint-disable-next-line no-lonely-if
+ if( me.config.useDatabase ) {
+ const data = await me.microdrawDBLoad();
+ regions = me._convertDBAnnotationsToRegions(data);
+ me._addRegionsToCurrentImage(regions);
}
+ }
- me._drawRegionsInPaper(regions);
- },
+ me._drawRegionsInPaper(regions);
+ },
- /**
+ /**
* @returns {void}
*/
- transform: function () {
- const z = me.viewer.viewport.viewportToImageZoom(me.viewer.viewport.getZoom(true));
- const sw = me.viewer.source.width;
- const bounds = me.viewer.viewport.getBounds(true);
- const [x, y, w, h] = [
- me.magicV * bounds.x,
- me.magicV * bounds.y,
- me.magicV * bounds.width,
- me.magicV * bounds.height
- ];
- paper.view.setCenter(x + (w/2), y + (h/2));
- paper.view.zoom = (sw * z) / me.magicV;
- },
-
- /**
+ transform: function () {
+ const z = me.viewer.viewport.viewportToImageZoom(me.viewer.viewport.getZoom(true));
+ const sw = me.viewer.source.width;
+ const bounds = me.viewer.viewport.getBounds(true);
+ const [x, y, w, h] = [
+ me.magicV * bounds.x,
+ me.magicV * bounds.y,
+ me.magicV * bounds.width,
+ me.magicV * bounds.height
+ ];
+ paper.view.setCenter(x + (w/2), y + (h/2));
+ paper.view.zoom = (sw * z) / me.magicV;
+ },
+
+ /**
* @returns {Object} Returns an object containing URL parametres
*/
- deparam: function () {
- if( me.debug>1 ) { console.log("> deparam"); }
-
- /** @todo Use URLSearchParams instead */
- const search = location.search.substring(1);
- const result = search?
- JSON.parse('{"' + search.replace(/[&]/g, '","').replace(/[=]/g, '":"') + '"}',
- function(key, value) { return key === "" ? value : decodeURIComponent(value); }) :
- {};
- if( me.debug>1 ) {
- console.log("url parametres:", result);
- }
+ deparam: function () {
+ if( me.debug>1 ) { console.log("> deparam"); }
+
+ /** @todo Use URLSearchParams instead */
+ const search = location.search.substring(1);
+ const result = search?
+ JSON.parse('{"' + search.replace(/[&]/g, '","').replace(/[=]/g, '":"') + '"}',
+ function(key, value) { return key === "" ? value : decodeURIComponent(value); }) :
+ {};
+ if( me.debug>1 ) {
+ console.log("url parametres:", result);
+ }
- return result;
- },
+ return result;
+ },
- /**
+ /**
* @returns {void} Returns a promise that is fulfilled when the user is loged in
*/
- loginChanged: function () {
- if( me.debug ) { console.log("> loginChanged"); }
+ loginChanged: function () {
+ if( me.debug ) { console.log("> loginChanged"); }
- // updateUser();
+ // updateUser();
- /** @todo Maybe log to db?? */
+ /** @todo Maybe log to db?? */
- // remove all annotations and paper projects from old user
- paper.project.activeLayer.removeChildren();
+ // remove all annotations and paper projects from old user
+ paper.project.activeLayer.removeChildren();
- // load new users data
- me.viewer.open(me.ImageInfo[me.currentImage].source);
- },
+ // load new users data
+ me.viewer.open(me.ImageInfo[me.currentImage].source);
+ },
- /**
+ /**
* @returns {void}
*/
- initShortCutHandler: function () {
- window.addEventListener("keydown", e => {
- if (e.isComposing || e.keyCode === 229) {
- return;
- }
- const key = [];
- if( e.ctrlKey ) { key.push("^"); }
- if( e.altKey ) { key.push("alt"); }
- if( e.shiftKey ) { key.push("shift"); }
- if( e.metaKey ) { key.push("cmd"); }
- key.push(String.fromCharCode(e.keyCode));
- const code = key.join(" ");
- if( me.shortCuts[code] ) {
- const shortcut = me.shortCuts[code];
- shortcut();
- e.stopPropagation();
- }
- });
- },
-
- /**
+ initShortCutHandler: function () {
+ window.addEventListener("keydown", (e) => {
+ if (e.isComposing || e.keyCode === 229) {
+ return;
+ }
+ const key = [];
+ if( e.ctrlKey ) { key.push("^"); }
+ if( e.altKey ) { key.push("alt"); }
+ if( e.shiftKey ) { key.push("shift"); }
+ if( e.metaKey ) { key.push("cmd"); }
+ key.push(String.fromCharCode(e.keyCode));
+ const code = key.join(" ");
+ if( me.shortCuts[code] ) {
+ const shortcut = me.shortCuts[code];
+ shortcut();
+ e.stopPropagation();
+ }
+ });
+ },
+
+ /**
* @param {string} theKey Key used for the shortcut
* @param {function} callback Function called for the specific key shortcut
* @returns {void}
*/
- shortCutHandler: function (theKey, callback) {
- let key = me.isMac?theKey.mac:theKey.pc;
- const arr = key.split(" ");
- for( let i = 0; i < arr.length; i += 1 ) {
- if( arr[i].charAt(0) === "#" ) {
- arr[i] = String.fromCharCode(parseInt(arr[i].substring(1), 10));
- } else
- if( arr[i].length === 1 ) {
- arr[i] = arr[i].toUpperCase();
- }
+ shortCutHandler: function (theKey, callback) {
+ let key = me.isMac?theKey.mac:theKey.pc;
+ const arr = key.split(" ");
+ for( let i = 0; i < arr.length; i += 1 ) {
+ if( arr[i].charAt(0) === "#" ) {
+ arr[i] = String.fromCharCode(parseInt(arr[i].substring(1), 10));
+ } else
+ if( arr[i].length === 1 ) {
+ arr[i] = arr[i].toUpperCase();
}
- key = arr.join(" ");
- me.shortCuts[key] = callback;
- },
+ }
+ key = arr.join(" ");
+ me.shortCuts[key] = callback;
+ },
- /**
+ /**
* @desc Initialises a slider to change between sections
* @param {number} minVal Minimum value
* @param {number} maxVal Maximum value
@@ -1398,607 +1394,620 @@ const Microdraw = (function () {
* @param {number} defaultValue Value at which the slider is initialised
* @returns {void}
*/
- initSlider: function (minVal, maxVal, step, defaultValue) {
- if( me.debug>1 ) { console.log("> initSlider promise"); }
- const slider = me.dom.querySelector("#slice");
- if( slider ) { // only if slider could be found
- slider.dataset.min = minVal;
- slider.dataset.max = maxVal - 1;
- slider.dataset.step = step;
- slider.dataset.val = defaultValue;
-
- me.updateSliderDisplay();
-
- // slider.on("change", function() {
- // me.sliderOnChange(this.value);
- // });
-
- // Input event can only be used when not using database, otherwise the annotations will be loaded several times
- /** @todo Fix the issue with the annotations for real */
-
- // if (me.config.useDatabase === false) {
- // slider.on("input", function () {
- // me.sliderOnChange(this.value);
- // });
- // }
- }
- },
+ initSlider: function (minVal, maxVal, step, defaultValue) {
+ if( me.debug>1 ) { console.log("> initSlider promise"); }
+ const slider = me.dom.querySelector("#slice");
+ if( slider ) { // only if slider could be found
+ slider.dataset.min = minVal;
+ slider.dataset.max = maxVal - 1;
+ slider.dataset.step = step;
+ slider.dataset.val = defaultValue;
- /**
+ me.updateSliderDisplay();
+
+ // slider.on("change", function() {
+ // me.sliderOnChange(this.value);
+ // });
+
+ // Input event can only be used when not using database, otherwise the annotations will be loaded several times
+ /** @todo Fix the issue with the annotations for real */
+
+ // if (me.config.useDatabase === false) {
+ // slider.on("input", function () {
+ // me.sliderOnChange(this.value);
+ // });
+ // }
+ }
+ },
+
+ /**
* @desc Called when the slider value is changed to load a new section
* @param {number} newImageNumber Index of the image selected using the slider
* @returns {void}
*/
- sliderOnChange: function (newImageNumber) {
- if( me.debug>1 ) { console.log("> sliderOnChange promise"); }
- const imageNumber = me.imageOrder[newImageNumber];
- me.loadImage(imageNumber);
- me.updateURL(imageNumber);
- },
+ sliderOnChange: function (newImageNumber) {
+ if( me.debug>1 ) { console.log("> sliderOnChange promise"); }
+ const imageNumber = me.imageOrder[newImageNumber];
+ me.loadImage(imageNumber);
+ me.updateURL(imageNumber);
+ },
- /**
+ /**
* @desc Used to update the slider value if the section was changed by another control
* @param {number} newIndex section number to which the slider will be set
* @returns {void}
*/
- updateSliderValue: function (newIndex) {
- if( me.debug>1 ) { console.log("> updateSliderValue promise"); }
- const slider = me.dom.querySelector("#slice");
- if( slider ) { // only if slider could be found
- slider.dataset.val = newIndex;
- me.updateSliderDisplay();
- }
- },
+ updateSliderValue: function (newIndex) {
+ if( me.debug>1 ) { console.log("> updateSliderValue promise"); }
+ const slider = me.dom.querySelector("#slice");
+ if( slider ) { // only if slider could be found
+ slider.dataset.val = newIndex;
+ me.updateSliderDisplay();
+ }
+ },
- updateSliderDisplay: () => {
- let {val, max} = me.dom.querySelector("#slice").dataset;
- const thumb = me.dom.querySelector("#slice .mui-thumb");
- val = Number(val);
- max = Number(max);
- thumb.style.left = (val*100/max) + "%";
- },
+ updateSliderDisplay: () => {
+ let {val, max} = me.dom.querySelector("#slice").dataset;
+ const thumb = me.dom.querySelector("#slice .mui-thumb");
+ val = Number(val);
+ max = Number(max);
+ thumb.style.left = (val*100/max) + "%";
+ },
- /**
+ /**
* Searches for the given section-number.
* If the number could be found its index will be returned. Otherwise -1
* @param {String} numberStr Section number
* @returns {void}
*/
- findSectionNumber: function (numberStr) {
- const number = parseInt(numberStr, 10); // number = NaN if cast to int failed!
- if( !isNaN(number) ) {
- for( let i = 0; i < me.imageOrder.length; i += 1 ) {
- const sectionNumber = parseInt(me.imageOrder[i], 10);
- // Compare the int values because the string values might be different (e.g. "0001" != "1")
- if( number === sectionNumber ) {
- return i;
- }
+ findSectionNumber: function (numberStr) {
+ const number = parseInt(numberStr, 10); // number = NaN if cast to int failed!
+ if( !isNaN(number) ) {
+ for( let i = 0; i < me.imageOrder.length; i += 1 ) {
+ const sectionNumber = parseInt(me.imageOrder[i], 10);
+ // Compare the int values because the string values might be different (e.g. "0001" != "1")
+ if( number === sectionNumber ) {
+ return i;
}
}
+ }
- return -1;
- },
+ return -1;
+ },
- /**
+ /**
* @param {object} event Event produced by the enter key
* @returns {void}
*/
- sectionNameOnEnter: function (event) {
- if( me.debug>1 ) { console.log("> sectionNameOnEnter promise"); }
- if( event.keyCode === 13 ) { // enter key
- const sectionNumber = this.value;
- const index = me.findSectionNumber(sectionNumber);
- if( index > -1 ) { // if section number exists
- me.updateSliderValue(index);
- me.loadImage(me.imageOrder[index]);
- me.updateURL(index);
- }
+ sectionNameOnEnter: function (event) {
+ if( me.debug>1 ) { console.log("> sectionNameOnEnter promise"); }
+ if( event.keyCode === 13 ) { // enter key
+ const sectionNumber = this.value;
+ const index = me.findSectionNumber(sectionNumber);
+ if( index > -1 ) { // if section number exists
+ me.updateSliderValue(index);
+ me.loadImage(me.imageOrder[index]);
+ me.updateURL(index);
}
- event.stopPropagation(); // prevent the default action (scroll / move caret)
- },
+ }
+ event.stopPropagation(); // prevent the default action (scroll / move caret)
+ },
- /**
+ /**
* @desc Used to update the URL with the slice value if the section was changed by another control
* @param {number} newIndex section number to which the URL will be set
* @returns {void}
*/
- updateURL : function (newIndex) {
- if( me.debug>1 ) { console.log('> updateURL'); }
- const urlParams = new URLSearchParams(window.location.search);
- urlParams.set('slice', newIndex);
- const newURL = [
- window.location.protocol,
- '//',
- window.location.host,
- window.location.pathname,
- '?',
- urlParams.toString()
- ].join('');
- const stateObj = {
- oldURL: newURL
- };
- history.pushState(stateObj, "", newURL);
- },
-
- /**
+ updateURL : function (newIndex) {
+ if( me.debug>1 ) { console.log('> updateURL'); }
+ const urlParams = new URLSearchParams(window.location.search);
+ urlParams.set('slice', newIndex);
+ const newURL = [
+ window.location.protocol,
+ '//',
+ window.location.host,
+ window.location.pathname,
+ '?',
+ urlParams.toString()
+ ].join('');
+ const stateObj = {
+ oldURL: newURL
+ };
+ history.pushState(stateObj, "", newURL);
+ },
+
+ /**
* @desc Used to update the URL with the slice value if none is given by user
* @param {number} newIndex section number to which the URL will be set
* @returns {void}
*/
- addSliceToURL : function (newIndex) {
- if( me.debug>1 ) { console.log('> addSliceToURL'); }
- const urlParams = new URLSearchParams(window.location.search);
- urlParams.set('slice', newIndex);
- const newURL = [
- window.location.protocol,
- '//',
- window.location.host,
- window.location.pathname,
- '?',
- urlParams.toString()
- ].join('');
- const stateObj = {
- oldURL: newURL
- };
- history.pushState(stateObj, "", newURL);
- },
-
- /**
+ addSliceToURL : function (newIndex) {
+ if( me.debug>1 ) { console.log('> addSliceToURL'); }
+ const urlParams = new URLSearchParams(window.location.search);
+ urlParams.set('slice', newIndex);
+ const newURL = [
+ window.location.protocol,
+ '//',
+ window.location.host,
+ window.location.pathname,
+ '?',
+ urlParams.toString()
+ ].join('');
+ const stateObj = {
+ oldURL: newURL
+ };
+ history.pushState(stateObj, "", newURL);
+ },
+
+ /**
* @desc Load source json (from server)
* @returns {promise} returns a promise, resolving as a microdraw compatible object
*/
- loadSourceJson : function () {
- if( me.debug ) { console.log('> loadSourceJson'); }
-
- return new Promise((resolve, reject) => {
- const directFetch = new Promise((rs, rj) => {
- // decide between json (local) and jsonp (cross-origin)
- let ext = me.params.source.split(".");
- ext = ext[ext.length - 1];
- if( ext === "jsonp" ) {
- if( me.debug ) { console.log("Reading cross-origin jsonp file"); }
- $.ajax({
- type: 'GET',
- url: me.params.source + "?callback=?",
- jsonpCallback: 'f',
- dataType: 'jsonp',
- contentType: "application/json",
- success: function(obj) {
- rs(obj);
- },
- error: function(err) {
- rj(err);
- }
- });
- } else
- if( ext === "json" ) {
- if( me.debug ) { console.log("Reading local json file"); }
- $.ajax({
- type: 'GET',
- url: me.params.source,
- dataType: "json",
- contentType: "application/json",
- success: function(obj) {
- rs(obj);
- },
- error: function(err) {
- rj(err);
- }
- });
- } else {
- fetch(me.params.source)
- .then((data) => data.json())
- .then((json) => {
- rs(json);
- })
- .catch((e) => rj(e));
- }
- });
+ loadSourceJson : function () {
+ if( me.debug ) { console.log('> loadSourceJson'); }
+
+ return new Promise((resolve, reject) => {
+ const directFetch = new Promise((rs, rj) => {
+ // decide between json (local) and jsonp (cross-origin)
+ let ext = me.params.source.split(".");
+ ext = ext[ext.length - 1];
+ if( ext === "jsonp" ) {
+ if( me.debug ) { console.log("Reading cross-origin jsonp file"); }
+ $.ajax({
+ type: 'GET',
+ url: me.params.source + "?callback=?",
+ jsonpCallback: 'f',
+ dataType: 'jsonp',
+ contentType: "application/json",
+ success: function(obj) {
+ rs(obj);
+ },
+ error: function(err) {
+ rj(err);
+ }
+ });
+ } else
+ if( ext === "json" ) {
+ if( me.debug ) { console.log("Reading local json file"); }
+ $.ajax({
+ type: 'GET',
+ url: me.params.source,
+ dataType: "json",
+ contentType: "application/json",
+ success: function(obj) {
+ rs(obj);
+ },
+ error: function(err) {
+ rj(err);
+ }
+ });
+ } else {
+ fetch(me.params.source)
+ .then((data) => data.json())
+ .then((json) => {
+ rs(json);
+ })
+ .catch((e) => rj(e));
+ }
+ });
- directFetch
- .then( function (json) {
- resolve(json);
- })
- .catch( (err) => {
- console.warn('> loadSourceJson : direct fetching of source failed ... ', err, 'attempting to fetch via microdraw server');
-
- fetch('/getJson?source='+me.params.source)
- .then((data) => data.json())
- .then((json) => {
- resolve(json);
- })
- .catch( (err2) => {
- reject(err2);
- });
- });
+ directFetch
+ .then( function (json) {
+ resolve(json);
+ })
+ .catch( (err) => {
+ console.warn('> loadSourceJson : direct fetching of source failed ... ', err, 'attempting to fetch via microdraw server');
+
+ fetch('/getJson?source='+me.params.source)
+ .then((data) => data.json())
+ .then((json) => {
+ resolve(json);
+ })
+ .catch( (err2) => {
+ reject(err2);
+ });
});
- },
+ });
+ },
- /**
+ /**
* @desc Load general microdraw configuration
* @returns {Promise} returns a promise that resolves when the configuration is loaded
*/
- loadConfiguration: async () => {
- await Promise.all([
- me.loadScript("/lib/jquery-1.11.0.min.js"),
- me.loadScript("/lib/paper-full-0.12.11.min.js"),
- me.loadScript("/lib/openseadragon/openseadragon.js"),
- me.loadScript("https://unpkg.com/hippy-hippo@0.0.1/dist/hippy-hippo-umd.js"),
- me.loadScript("/js/microdraw-ws.js")
- ]);
-
- await me.loadScript("/lib/openseadragon-viewerinputhook.min.js");
-
- await Promise.all([
- me.loadScript("/lib/OpenSeadragonScalebar/openseadragon-scalebar.js"),
- // me.loadScript("/lib/openseadragon-screenshot/openseadragonScreenshot.min.js"),
- me.loadScript("https://cdn.jsdelivr.net/gh/r03ert0/Openseadragon-screenshot@v0.0.1/openseadragonScreenshot.js"),
- me.loadScript("/lib/FileSaver.js/FileSaver.min.js"),
- me.loadScript("/js/neurolex-ontology.js"),
- me.loadScript("https://cdn.jsdelivr.net/gh/r03ert0/muijs@v0.1.2/mui.js"),
- me.loadScript("https://unpkg.com/codeflask/build/codeflask.min.js"),
- me.loadScript("https://cdn.jsdelivr.net/gh/r03ert0/consolita.js@0.2.1/consolita.js"),
-
- me.loadScript('/js/tools/fullscreen.js'),
- me.loadScript('/js/tools/home.js'),
- me.loadScript('/js/tools/navigate.js'),
- me.loadScript('/js/tools/zoomIn.js'),
- me.loadScript('/js/tools/zoomOut.js'),
- me.loadScript('/js/tools/previous.js'),
- me.loadScript('/js/tools/next.js'),
- me.loadScript('/js/tools/closeMenu.js'),
- me.loadScript('/js/tools/openMenu.js')
- ]);
-
- $.extend(me.tools, ToolFullscreen);
- $.extend(me.tools, ToolHome);
- $.extend(me.tools, ToolNavigate);
- $.extend(me.tools, ToolZoomIn);
- $.extend(me.tools, ToolZoomOut);
- $.extend(me.tools, ToolPrevious);
- $.extend(me.tools, ToolNext);
- $.extend(me.tools, ToolCloseMenu);
- $.extend(me.tools, ToolOpenMenu);
-
- // load configuration file, then load the tools accordingly
- const r = await fetch("/js/configuration.json")
- const data = await r.json();
- me.config = data;
-
- // tools loaded dynamically, based on user configuration, server configuration etc.
- data.presets.default.map( async (item) => {
- // load script + extend me.tools
- await me.loadScript(item.scriptPath);
-
- // there maybe multiple exported variables
- item.exportedVar.forEach( (variable) => {
- /** @todo use ES 6 for proper module import. eval should be avoided when possible */
- eval(`$.extend(me.tools,${variable})`);
- });
+ loadConfiguration: async () => {
+ await Promise.all([
+ me.loadScript("/lib/jquery-1.11.0.min.js"),
+ me.loadScript("/lib/paper-full-0.12.11.min.js"),
+ me.loadScript("/lib/openseadragon/openseadragon.js"),
+ me.loadScript("https://unpkg.com/hippy-hippo@0.0.1/dist/hippy-hippo-umd.js"),
+ me.loadScript("/js/microdraw-ws.js")
+ ]);
+
+ await me.loadScript("/lib/openseadragon-viewerinputhook.min.js");
+
+ await Promise.all([
+ me.loadScript("/lib/OpenSeadragonScalebar/openseadragon-scalebar.js"),
+ // me.loadScript("/lib/openseadragon-screenshot/openseadragonScreenshot.min.js"),
+ me.loadScript("https://cdn.jsdelivr.net/gh/r03ert0/Openseadragon-screenshot@v0.0.1/openseadragonScreenshot.js"),
+ me.loadScript("/lib/FileSaver.js/FileSaver.min.js"),
+ me.loadScript("/js/neurolex-ontology.js"),
+ me.loadScript("https://cdn.jsdelivr.net/gh/r03ert0/muijs@v0.1.2/mui.js"),
+ me.loadScript("https://unpkg.com/codeflask/build/codeflask.min.js"),
+ me.loadScript("https://cdn.jsdelivr.net/gh/r03ert0/consolita.js@0.2.1/consolita.js"),
+ /* global Consolita */
+
+ me.loadScript('/js/tools/fullscreen.js'),
+ me.loadScript('/js/tools/home.js'),
+ me.loadScript('/js/tools/navigate.js'),
+ me.loadScript('/js/tools/zoomIn.js'),
+ me.loadScript('/js/tools/zoomOut.js'),
+ me.loadScript('/js/tools/previous.js'),
+ me.loadScript('/js/tools/next.js'),
+ me.loadScript('/js/tools/closeMenu.js'),
+ me.loadScript('/js/tools/openMenu.js')
+ ]);
+
+ /* global ToolFullscreen */
+ /* global ToolHome */
+ /* global ToolNavigate */
+ /* global ToolZoomIn */
+ /* global ToolZoomOut */
+ /* global ToolPrevious */
+ /* global ToolNext */
+ /* global ToolCloseMenu */
+ /* global ToolOpenMenu */
+
+ $.extend(me.tools, ToolFullscreen);
+ $.extend(me.tools, ToolHome);
+ $.extend(me.tools, ToolNavigate);
+ $.extend(me.tools, ToolZoomIn);
+ $.extend(me.tools, ToolZoomOut);
+ $.extend(me.tools, ToolPrevious);
+ $.extend(me.tools, ToolNext);
+ $.extend(me.tools, ToolCloseMenu);
+ $.extend(me.tools, ToolOpenMenu);
+
+ // load configuration file, then load the tools accordingly
+ const r = await fetch("/js/configuration.json");
+ const data = await r.json();
+ me.config = data;
+
+ // tools loaded dynamically, based on user configuration, server configuration etc.
+ data.presets.default.map( async (item) => {
+ // load script + extend me.tools
+ await me.loadScript(item.scriptPath);
+
+ // there maybe multiple exported variables
+ item.exportedVar.forEach( (variable) => {
+
+ /** @todo use ES 6 for proper module import. eval should be avoided when possible */
+ $.extend(me.tools, this[variable]);
+ // eval(`$.extend(me.tools,${variable})`);
});
- },
+ });
+ },
- /**
+ /**
* @desc Loads script from path if test is not fulfilled
* @param {string} path Path to script, either a local path or a url
* @param {function} testScriptPresent Function to test if the script is already present. If undefined, the script will be loaded.
* @returns {promise} A promise fulfilled when the script is loaded
*/
- loadScript: function (path, testScriptPresent) {
- return new Promise(function (resolve, reject) {
- if(testScriptPresent && testScriptPresent()) {
- console.log("[loadScript] Script", path, "already present, not loading it again");
- resolve();
- }
- const s = document.createElement("script");
- s.src = path;
- s.onload=function () {
- console.log("Loaded", path);
- resolve();
- };
- s.onerror=function() {
- console.log("Error", path);
- reject(new Error("something bad happened"));
- };
- document.body.appendChild(s);
- });
- },
+ loadScript: function (path, testScriptPresent) {
+ return new Promise(function (resolve, reject) {
+ if(testScriptPresent && testScriptPresent()) {
+ console.log("[loadScript] Script", path, "already present, not loading it again");
+ resolve();
+ }
+ const s = document.createElement("script");
+ s.src = path;
+ s.onload=function () {
+ console.log("Loaded", path);
+ resolve();
+ };
+ s.onerror=function() {
+ console.log("Error", path);
+ reject(new Error("something bad happened"));
+ };
+ document.body.appendChild(s);
+ });
+ },
- /**
+ /**
* @desc Changes the way in which the toolbar is displayed
* @param {string} display Position where the toolbar is displayed
* @returns {void}
*/
- changeToolbarDisplay: function (display) {
- switch(display) {
- case "minimize":
- me.dom.querySelector("#tools-maximized").style.display = "none";
- me.dom.querySelector("#tools-minimized").style.display = "block";
- break;
- case "maximize":
- me.dom.querySelector("#tools-maximized").style.display = "block";
- me.dom.querySelector("#tools-minimized").style.display = "none";
- break;
- case "left":
- me.dom.querySelector("body").setAttribute("data-toolbarDisplay", "left");
- break;
- case "right":
- me.dom.querySelector("body").setAttribute("data-toolbarDisplay", "right");
- break;
- }
- },
+ changeToolbarDisplay: function (display) {
+ switch(display) {
+ case "minimize":
+ me.dom.querySelector("#tools-maximized").style.display = "none";
+ me.dom.querySelector("#tools-minimized").style.display = "block";
+ break;
+ case "maximize":
+ me.dom.querySelector("#tools-maximized").style.display = "block";
+ me.dom.querySelector("#tools-minimized").style.display = "none";
+ break;
+ case "left":
+ me.dom.querySelector("body").setAttribute("data-toolbarDisplay", "left");
+ break;
+ case "right":
+ me.dom.querySelector("body").setAttribute("data-toolbarDisplay", "right");
+ break;
+ }
+ },
- /**
+ /**
* @param {string} mode One from Chat or Script
* @returns {void}
*/
- toggleTextInput: function (mode) {
- switch(mode) {
- case "Chat":
- me.dom.querySelector("#textInputBlock").style.display = "block";
- me.dom.getElementById("logScript").classList.add("hidden");
- me.dom.getElementById("logChat").classList.remove("hidden");
- me.dom.querySelector("#logChat #msg").focus();
- break;
- case "Script":
- me.dom.querySelector("#textInputBlock").style.display = "block";
- me.dom.getElementById("logScript").classList.remove("hidden");
- me.dom.getElementById("logChat").classList.add("hidden");
- me.dom.querySelector("#logScript textarea").focus();
- break;
- default:
- me.dom.querySelector("#textInputBlock").style.display = "none";
- }
- },
+ toggleTextInput: function (mode) {
+ switch(mode) {
+ case "Chat":
+ me.dom.querySelector("#textInputBlock").style.display = "block";
+ me.dom.getElementById("logScript").classList.add("hidden");
+ me.dom.getElementById("logChat").classList.remove("hidden");
+ me.dom.querySelector("#logChat #msg").focus();
+ break;
+ case "Script":
+ me.dom.querySelector("#textInputBlock").style.display = "block";
+ me.dom.getElementById("logScript").classList.remove("hidden");
+ me.dom.getElementById("logChat").classList.add("hidden");
+ me.dom.querySelector("#logScript textarea").focus();
+ break;
+ default:
+ me.dom.querySelector("#textInputBlock").style.display = "none";
+ }
+ },
- /**
+ /**
* @returns {void}
*/
- initMicrodraw: async () => {
- if( me.debug>1 ) { console.log("> initMicrodraw promise"); }
-
- // Enable click on toolbar buttons
- Array.prototype.forEach.call(me.dom.querySelectorAll('#buttonsBlock div.mui.push'), (el) => {
- el.addEventListener('click', me.toolSelection);
- });
- MUI.toggle(me.dom.querySelector("#fullscreen"), () => { me.clickTool("fullscreen"); });
- MUI.push(me.dom.querySelector("#sliderBlock #previous"), () => { me.clickTool("previous"); });
- MUI.push(me.dom.querySelector("#sliderBlock #next"), () => { me.clickTool("next"); });
- MUI.slider(me.dom.querySelector("#sliderBlock #slice"), (x) => {
- const newImageNumber = Math.round((me.imageOrder.length-1)*x/100);
- me.sliderOnChange(newImageNumber);
+ initMicrodraw: async () => {
+ if( me.debug>1 ) { console.log("> initMicrodraw promise"); }
+
+ // Enable click on toolbar buttons
+ Array.prototype.forEach.call(me.dom.querySelectorAll('#buttonsBlock div.mui.push'), (el) => {
+ el.addEventListener('click', me.toolSelection);
+ });
+ MUI.toggle(me.dom.querySelector("#fullscreen"), () => { me.clickTool("fullscreen"); });
+ MUI.push(me.dom.querySelector("#sliderBlock #previous"), () => { me.clickTool("previous"); });
+ MUI.push(me.dom.querySelector("#sliderBlock #next"), () => { me.clickTool("next"); });
+ MUI.slider(me.dom.querySelector("#sliderBlock #slice"), (x) => {
+ const newImageNumber = Math.round((me.imageOrder.length-1)*x/100);
+ me.sliderOnChange(newImageNumber);
+ });
+ MUI.chose(me.dom.querySelector("#clickTool.mui-chose"), (title) => {
+ const el = me.dom.querySelector(`[title="${title}"]`);
+ const tool = el.id;
+ me.clickTool(tool);
+ });
+
+ // set annotation loading flag to false
+ me.annotationLoadingFlag = false;
+
+ // Initialize the control key handler and set shortcuts
+ me.initShortCutHandler();
+ me.shortCutHandler({pc:'^ z', mac:'cmd z'}, me.cmdUndo);
+ me.shortCutHandler({pc:'shift ^ z', mac:'shift cmd z'}, me.cmdRedo);
+ if( me.config.drawingEnabled ) {
+ me.shortCutHandler({pc:'^ x', mac:'cmd x'}, function () {
+ console.log("cut!");
});
- MUI.chose(me.dom.querySelector("#clickTool.mui-chose"), (title) => {
- const el = me.dom.querySelector(`[title="${title}"]`);
- const tool = el.id;
- me.clickTool(tool);
+ me.shortCutHandler({pc:'^ v', mac:'cmd v'}, me.cmdPaste);
+ me.shortCutHandler({pc:'^ a', mac:'cmd a'}, function () {
+ console.log("select all!");
});
+ me.shortCutHandler({pc:'^ c', mac:'cmd c'}, me.cmdCopy);
+ me.shortCutHandler({pc:'#46', mac:'#8'}, me.cmdDeleteSelected); // delete key
+ }
+ me.shortCutHandler({pc:'#37', mac:'#37'}, me.loadPreviousImage); // left-arrow key
+ me.shortCutHandler({pc:'#39', mac:'#39'}, me.loadNextImage); // right-arrow key
- // set annotation loading flag to false
- me.annotationLoadingFlag = false;
+ // Configure currently selected tool
+ me.selectedTool = "navigate";
- // Initialize the control key handler and set shortcuts
- me.initShortCutHandler();
- me.shortCutHandler({pc:'^ z', mac:'cmd z'}, me.cmdUndo);
- me.shortCutHandler({pc:'shift ^ z', mac:'shift cmd z'}, me.cmdRedo);
- if( me.config.drawingEnabled ) {
- me.shortCutHandler({pc:'^ x', mac:'cmd x'}, function () {
- console.log("cut!");
- });
- me.shortCutHandler({pc:'^ v', mac:'cmd v'}, me.cmdPaste);
- me.shortCutHandler({pc:'^ a', mac:'cmd a'}, function () {
- console.log("select all!");
- });
- me.shortCutHandler({pc:'^ c', mac:'cmd c'}, me.cmdCopy);
- me.shortCutHandler({pc:'#46', mac:'#8'}, me.cmdDeleteSelected); // delete key
- }
- me.shortCutHandler({pc:'#37', mac:'#37'}, me.loadPreviousImage); // left-arrow key
- me.shortCutHandler({pc:'#39', mac:'#39'}, me.loadNextImage); // right-arrow key
+ document.body.dataset.toolbardisplay = "left";
+ me.dom.querySelector("#tools-minimized").style.display = "none";
+ me.dom.querySelector("#tools-minimized").addEventListener("click", () => { me.changeToolbarDisplay("maximize"); });
+ MUI.push(me.dom.querySelector(".push#display-minimize"), () => { me.changeToolbarDisplay("minimize"); });
+ MUI.push(me.dom.querySelector(".push#display-left"), () => { me.changeToolbarDisplay("left"); });
+ MUI.push(me.dom.querySelector(".push#display-right"), () => { me.changeToolbarDisplay("right"); });
- // Configure currently selected tool
- me.selectedTool = "navigate";
+ MUI.chose3state(me.dom.querySelector("#text.mui-chose"), me.toggleTextInput);
- document.body.dataset.toolbardisplay = "left";
- me.dom.querySelector("#tools-minimized").style.display = "none";
- me.dom.querySelector("#tools-minimized").addEventListener("click", () => { me.changeToolbarDisplay("maximize"); });
- MUI.push(me.dom.querySelector(".push#display-minimize"), () => { me.changeToolbarDisplay("minimize"); });
- MUI.push(me.dom.querySelector(".push#display-left"), () => { me.changeToolbarDisplay("left"); });
- MUI.push(me.dom.querySelector(".push#display-right"), () => { me.changeToolbarDisplay("right"); });
-
- MUI.chose3state(me.dom.querySelector("#text.mui-chose"), me.toggleTextInput);
+ Consolita.init(me.dom.querySelector("#logScript"), me.dom);
- Consolita.init(me.dom.querySelector("#logScript"), me.dom);
-
- $(window).resize(function() {
- me.resizeAnnotationOverlay();
- });
+ $(window).resize(function() {
+ me.resizeAnnotationOverlay();
+ });
- // Load regions label set
- const res = await fetch("/js/10regions.json");
- const labels = await res.json();
- me.ontology = labels;
- me.updateLabelDisplay();
- },
+ // Load regions label set
+ const res = await fetch("/js/10regions.json");
+ const labels = await res.json();
+ me.ontology = labels;
+ me.updateLabelDisplay();
+ },
- /**
+ /**
* @param {Object} obj DZI json configuration object
* @returns {void}
*/
- initOpenSeadragon: function (obj) {
- if( me.debug>1 ) { console.log("json file:", obj); }
+ initOpenSeadragon: function (obj) {
+ if( me.debug>1 ) { console.log("json file:", obj); }
- // for loading the bigbrain
- if( obj.tileCodeY ) {
- obj.tileSources = obj.tileCodeY;
- }
+ // for loading the bigbrain
+ if( obj.tileCodeY ) {
+ obj.tileSources = obj.tileCodeY;
+ }
- // set up the ImageInfo array and me.imageOrder array
- for( let i = 0; i < obj.tileSources.length; i += 1 ) {
- // name is either the index of the tileSource or a named specified in the json file
- const name = ((obj.names && obj.names[i]) ? String(obj.names[i]) : String(i));
- me.imageOrder.push(name);
- me.ImageInfo[name] = {
- source: obj.tileSources[i],
- Regions: [],
- RegionsToRemove: []
- };
- // if getTileUrl is specified, we might need to eval it to get the function
- if( obj.tileSources[i].getTileUrl && typeof obj.tileSources[i].getTileUrl === 'string' ) {
- eval(`me.ImageInfo[name].source.getTileUrl = ${obj.tileSources[i].getTileUrl}`)
- }
- }
+ // set up the ImageInfo array and me.imageOrder array
+ for( let i = 0; i < obj.tileSources.length; i += 1 ) {
+ // name is either the index of the tileSource or a named specified in the json file
+ const name = ((obj.names && obj.names[i]) ? String(obj.names[i]) : String(i));
+ me.imageOrder.push(name);
+ me.ImageInfo[name] = {
+ source: obj.tileSources[i],
+ Regions: [],
+ RegionsToRemove: []
+ };
+ // if getTileUrl is specified, we might need to eval it to get the function
+ // if( obj.tileSources[i].getTileUrl && typeof obj.tileSources[i].getTileUrl === 'string' ) {
+ // eval(`me.ImageInfo[name].source.getTileUrl = ${obj.tileSources[i].getTileUrl}`);
+ // }
+ }
- // set default values for new regions (general configuration)
- if (typeof me.config.defaultStrokeColor === "undefined") {
- me.config.defaultStrokeColor = 'black';
- }
- if (typeof me.config.defaultStrokeWidth === "undefined") {
- me.config.defaultStrokeWidth = 1;
- }
- if (typeof me.config.defaultFillAlpha === "undefined") {
- me.config.defaultFillAlpha = 0.5;
- }
- // set default values for new regions (per-brain configuration)
- if (obj.configuration) {
- if (typeof obj.configuration.defaultStrokeColor !== "undefined") {
- me.config.defaultStrokeColor = obj.configuration.defaultStrokeColor;
- }
- if (typeof obj.configuration.defaultStrokeWidth !== "undefined") {
- me.config.defaultStrokeWidth = obj.configuration.defaultStrokeWidth;
- }
- if (typeof obj.configuration.defaultFillAlpha !== "undefined") {
- me.config.defaultFillAlpha = obj.configuration.defaultFillAlpha;
- }
+ // set default values for new regions (general configuration)
+ if (typeof me.config.defaultStrokeColor === "undefined") {
+ me.config.defaultStrokeColor = 'black';
+ }
+ if (typeof me.config.defaultStrokeWidth === "undefined") {
+ me.config.defaultStrokeWidth = 1;
+ }
+ if (typeof me.config.defaultFillAlpha === "undefined") {
+ me.config.defaultFillAlpha = 0.5;
+ }
+ // set default values for new regions (per-brain configuration)
+ if (obj.configuration) {
+ if (typeof obj.configuration.defaultStrokeColor !== "undefined") {
+ me.config.defaultStrokeColor = obj.configuration.defaultStrokeColor;
}
-
- // init slider that can be used to change between slides
-
- if(me.params.slice === "undefined" || typeof me.params.slice === "undefined") { // this is correct: the string "undefined", or the type
- me.initSlider(0, obj.tileSources.length, 1, Math.round(obj.tileSources.length / 2));
- const newIndex = Math.floor(obj.tileSources.length / 2)
- me.currentImage = me.imageOrder[newIndex];
- me.addSliceToURL(newIndex);
- } else {
- me.initSlider(0, obj.tileSources.length, 1, me.params.slice);
- me.currentImage = me.imageOrder[[parseInt(me.params.slice, 10)]];
+ if (typeof obj.configuration.defaultStrokeWidth !== "undefined") {
+ me.config.defaultStrokeWidth = obj.configuration.defaultStrokeWidth;
}
-
- // display slice number
- me.dom.querySelector("#slice-number").innerHTML = `Slice ${me.currentImage}`;
-
- me.params.tileSources = obj.tileSources;
- if (typeof obj.fileID !== 'undefined') {
- me.fileID = obj.fileID;
- } else {
- me.fileID = me.source + '_' + me.section;
+ if (typeof obj.configuration.defaultFillAlpha !== "undefined") {
+ me.config.defaultFillAlpha = obj.configuration.defaultFillAlpha;
}
- me.viewer = new OpenSeadragon({
- // id: "openseadragon1",
- element: me.dom.querySelector("#openseadragon1"),
- prefixUrl: "/lib/openseadragon/images/",
- tileSources: [],
- showReferenceStrip: false,
- referenceStripSizeRatio: 0.2,
- showNavigator: true,
- sequenceMode: false,
- // navigatorId: "myNavigator",
- navigatorPosition: "BOTTOM_RIGHT",
- homeButton:"homee",
- maxZoomPixelRatio:10,
- preserveViewport: true
- });
-
- // open the currentImage
- me.viewer.open(me.ImageInfo[me.currentImage].source);
-
- // add the scalebar
- me.viewer.scalebar({
- type: OpenSeadragon.ScalebarType.MICROSCOPE,
- minWidth:'150px',
- pixelsPerMeter:obj.pixelsPerMeter,
- color:'black',
- fontColor:'black',
- backgroundColor:"rgba(255, 255, 255, 0.5)",
- barThickness:4,
- location: OpenSeadragon.ScalebarLocation.TOP_RIGHT,
- xOffset:5,
- yOffset:5
- });
-
- /* fixes https://github.com/r03ert0/microdraw/issues/142 */
- me.viewer.scalebarInstance.divElt.style.pointerEvents = `none`;
+ }
- // add screenshot
- me.viewer.screenshot({
- showOptions: false, // Default is false
- // keyboardShortcut: 'p', // Default is null
- // showScreenshotControl: true // Default is true
- });
+ // init slider that can be used to change between slides
- // initialise paperjs
- me._createCanvasAndAddToPaper();
+ if(me.params.slice === "undefined" || typeof me.params.slice === "undefined") { // this is correct: the string "undefined", or the type
+ me.initSlider(0, obj.tileSources.length, 1, Math.round(obj.tileSources.length / 2));
+ const newIndex = Math.floor(obj.tileSources.length / 2);
+ me.currentImage = me.imageOrder[newIndex];
+ me.addSliceToURL(newIndex);
+ } else {
+ me.initSlider(0, obj.tileSources.length, 1, me.params.slice);
+ me.currentImage = me.imageOrder[[parseInt(me.params.slice, 10)]];
+ }
- // add handlers: update section name, animation, page change, mouse actions
- me.viewer.addHandler('open', function () {
- me.initAnnotationOverlay();
- });
- me.viewer.addHandler('animation', me.transform);
- me.viewer.addHandler("animation-start", function () {
- me.isAnimating = true;
- });
- me.viewer.addHandler("animation-finish", function () {
- me.isAnimating = false;
- });
- me.viewer.addHandler("page", function (data) {
- console.log("page", data.page, me.params.tileSources[data.page]);
- });
- me.viewer.addViewerInputHook({hooks: [
- {tracker: 'viewer', handler: 'clickHandler', hookHandler: me.clickHandler},
- {tracker: 'viewer', handler: 'pressHandler', hookHandler: me.pressHandler},
- {tracker: 'viewer', handler: 'releaseHandler', hookHandler: me.releaseHandler},
- {tracker: 'viewer', handler: 'dragHandler', hookHandler: me.dragHandler},
- // {tracker: 'viewer', handler: 'dragEndHandler', hookHandler: me.dragEndHandler},
- {tracker: 'viewer', handler: 'scrollHandler', hookHandler: me.scrollHandler}
- ]});
-
- if( me.debug>1 ) { console.log("< initOpenSeadragon resolve: success"); }
- },
+ // display slice number
+ me.dom.querySelector("#slice-number").innerHTML = `Slice ${me.currentImage}`;
- /**
+ me.params.tileSources = obj.tileSources;
+ if (typeof obj.fileID !== 'undefined') {
+ me.fileID = obj.fileID;
+ } else {
+ me.fileID = me.source + '_' + me.section;
+ }
+ me.viewer = new OpenSeadragon({
+ // id: "openseadragon1",
+ element: me.dom.querySelector("#openseadragon1"),
+ prefixUrl: "/lib/openseadragon/images/",
+ tileSources: [],
+ showReferenceStrip: false,
+ referenceStripSizeRatio: 0.2,
+ showNavigator: true,
+ sequenceMode: false,
+ // navigatorId: "myNavigator",
+ navigatorPosition: "BOTTOM_RIGHT",
+ homeButton:"homee",
+ maxZoomPixelRatio:10,
+ preserveViewport: true
+ });
+
+ // open the currentImage
+ me.viewer.open(me.ImageInfo[me.currentImage].source);
+
+ // add the scalebar
+ me.viewer.scalebar({
+ type: OpenSeadragon.ScalebarType.MICROSCOPE,
+ minWidth:'150px',
+ pixelsPerMeter:obj.pixelsPerMeter,
+ color:'black',
+ fontColor:'black',
+ backgroundColor:"rgba(255, 255, 255, 0.5)",
+ barThickness:4,
+ location: OpenSeadragon.ScalebarLocation.TOP_RIGHT,
+ xOffset:5,
+ yOffset:5
+ });
+
+ /* fixes https://github.com/r03ert0/microdraw/issues/142 */
+ me.viewer.scalebarInstance.divElt.style.pointerEvents = `none`;
+
+ // add screenshot
+ me.viewer.screenshot({
+ showOptions: false // Default is false
+ // keyboardShortcut: 'p', // Default is null
+ // showScreenshotControl: true // Default is true
+ });
+
+ // initialise paperjs
+ me._createCanvasAndAddToPaper();
+
+ // add handlers: update section name, animation, page change, mouse actions
+ me.viewer.addHandler('open', function () {
+ me.initAnnotationOverlay();
+ });
+ me.viewer.addHandler('animation', me.transform);
+ me.viewer.addHandler("animation-start", function () {
+ me.isAnimating = true;
+ });
+ me.viewer.addHandler("animation-finish", function () {
+ me.isAnimating = false;
+ });
+ me.viewer.addHandler("page", function (data) {
+ console.log("page", data.page, me.params.tileSources[data.page]);
+ });
+ me.viewer.addViewerInputHook({hooks: [
+ {tracker: 'viewer', handler: 'clickHandler', hookHandler: me.clickHandler},
+ {tracker: 'viewer', handler: 'pressHandler', hookHandler: me.pressHandler},
+ {tracker: 'viewer', handler: 'releaseHandler', hookHandler: me.releaseHandler},
+ {tracker: 'viewer', handler: 'dragHandler', hookHandler: me.dragHandler},
+ // {tracker: 'viewer', handler: 'dragEndHandler', hookHandler: me.dragEndHandler},
+ {tracker: 'viewer', handler: 'scrollHandler', hookHandler: me.scrollHandler}
+ ]});
+
+ if( me.debug>1 ) { console.log("< initOpenSeadragon resolve: success"); }
+ },
+
+ /**
* @return {void}
*/
- toggleMenu: function () {
- if( me.dom.querySelector('#menuBar').style.display === 'none' ) {
- me.dom.querySelector('#menuBar').style.display = 'block';
- me.dom.querySelector('#menuButton').style.display = 'none';
- } else {
- me.dom.querySelector('#menuBar').style.display = 'none';
- me.dom.querySelector('#menuButton').style.display = 'block';
- }
- },
+ toggleMenu: function () {
+ if( me.dom.querySelector('#menuBar').style.display === 'none' ) {
+ me.dom.querySelector('#menuBar').style.display = 'block';
+ me.dom.querySelector('#menuButton').style.display = 'none';
+ } else {
+ me.dom.querySelector('#menuBar').style.display = 'none';
+ me.dom.querySelector('#menuButton').style.display = 'block';
+ }
+ },
- init: async function (dom) {
- me.dom = dom;
+ init: async function (dom) {
+ me.dom = dom;
- await me.loadConfiguration();
+ await me.loadConfiguration();
- me.params = me.deparam();
+ me.params = me.deparam();
- if( me.config.useDatabase ) {
- me.section = me.currentImage;
- me.source = me.params.source;
- if(typeof me.params.project !== 'undefined') {
- me.project = me.params.project;
- }
+ if( me.config.useDatabase ) {
+ me.section = me.currentImage;
+ me.source = me.params.source;
+ if(typeof me.params.project !== 'undefined') {
+ me.project = me.params.project;
}
+ }
- me.initMicrodraw();
+ me.initMicrodraw();
- const json = await me.loadSourceJson();
- me.initOpenSeadragon(json);
- }
+ const json = await me.loadSourceJson();
+ me.initOpenSeadragon(json);
+ }
};
-
+
return me;
}());
diff --git a/app/public/js/neurolex-ontology.js b/app/public/js/neurolex-ontology.js
index 3070be9..1a335cc 100755
--- a/app/public/js/neurolex-ontology.js
+++ b/app/public/js/neurolex-ontology.js
@@ -1,4 +1,4 @@
-/* eslint-disable no-unused-vars */
+/* exported Ontology */
const Ontology = [
{
name:"Telencephalon",
diff --git a/app/public/js/tools/save.js b/app/public/js/tools/save.js
index 8827f36..6be0d89 100644
--- a/app/public/js/tools/save.js
+++ b/app/public/js/tools/save.js
@@ -40,13 +40,14 @@ const _dialog = async ({el, message, doFadeOut=true, delay=2000, background="#33
fadeOut(el);
}
resolve();
- }, delay);
+ }, delay);
});
};
-var ToolSave = { save: (function() {
+window.ToolSave = { save: (function() {
- const _processOneSection = function (sl) {
+ // eslint-disable-next-line max-statements
+ const _processOneSection = async function (sl) {
if ((Microdraw.config.multiImageSave === false) && (sl !== Microdraw.currentImage)) {
return;
@@ -67,33 +68,27 @@ var ToolSave = { save: (function() {
value.Hash = h;
- const pr = new Promise(async (resolve, reject) => {
- let res, req;
- try {
- req = await fetch('/api', {
- method: "POST",
- headers: {'Content-Type': 'application/json'},
- body: JSON.stringify({
- action: 'save',
- source: Microdraw.source,
- slice: sl,
- project: Microdraw.project,
- Hash: h,
- annotation: JSON.stringify(value)
- })
- });
- res = await req.json();
+ const res = await fetch('/api', {
+ method: "POST",
+ headers: {'Content-Type': 'application/json'},
+ body: JSON.stringify({
+ action: 'save',
+ source: Microdraw.source,
+ slice: sl,
+ project: Microdraw.project,
+ Hash: h,
+ annotation: JSON.stringify(value)
+ })
+ });
- // update hash
- section.Hash = h;
+ if (!res.ok) {
+ throw await res.json();
+ }
- resolve(sl);
- } catch(err) {
- reject(err);
- }
- });
+ // update hash
+ section.Hash = h;
- return pr;
+ return sl;
};
const _savingFeedback = async function (savedSections) {
@@ -108,8 +103,8 @@ var ToolSave = { save: (function() {
await _dialog({el, message, doFadeOut: false});
}
};
-
- const _successFeedback = function (savedSections) {
+
+ const _successFeedback = function () {
const el = Microdraw.dom.querySelector('#saveDialog');
_dialog({el, message: "Successfully saved", delay: 1000, background: "#2a3"});
};
diff --git a/package-lock.json b/package-lock.json
index 6213dfc..8031534 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -22,7 +22,7 @@
"monk": "^7.1.1",
"multer": "^1.4.1",
"mustache-express": "latest",
- "neuroweblab": "github:neuroanatomy/neuroweblab#43eab1a99c9fffe4ff258e31733c600fe447959f",
+ "neuroweblab": "github:neuroanatomy/neuroweblab",
"passport": "^0.4.0",
"passport-github": "latest",
"passport-local": "^1.0.0",
@@ -3399,9 +3399,9 @@
}
},
"node_modules/mongodb-connection-string-url": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.4.2.tgz",
- "integrity": "sha512-mZUXF6nUzRWk5J3h41MsPv13ukWlH4jOMSk6astVeoZ1EbdTJyF5I3wxKkvqBAOoVtzLgyEYUvDjrGdcPlKjAw==",
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.1.tgz",
+ "integrity": "sha512-0GAJKc1LBXzlWPhtj9uGawIlYSkTXkgpW9wZ97b4ySEuKbE5j9a0OdLGM31AWMhRS2ut49Z0kufSYsamGEIb8Q==",
"peer": true,
"dependencies": {
"@types/whatwg-url": "^8.2.1",
@@ -3600,8 +3600,7 @@
},
"node_modules/neuroweblab": {
"version": "0.0.1",
- "resolved": "git+ssh://git@github.com/neuroanatomy/neuroweblab.git#43eab1a99c9fffe4ff258e31733c600fe447959f",
- "integrity": "sha512-VwXSn9yU7jOKKna2A8hIuBGYKXyixwLquwDfT+P2kJMW8UQJhfGjDNsPusl0xMU6JWT7YCLIbAF/W/RqJNN0eA==",
+ "resolved": "git+ssh://git@github.com/neuroanatomy/neuroweblab.git#38bc9cad27d890c75d0ff32317bb6a3e0f570fb1",
"license": "ISC",
"dependencies": {
"bcrypt": "^5.0.1",
@@ -7767,9 +7766,9 @@
}
},
"mongodb-connection-string-url": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.4.2.tgz",
- "integrity": "sha512-mZUXF6nUzRWk5J3h41MsPv13ukWlH4jOMSk6astVeoZ1EbdTJyF5I3wxKkvqBAOoVtzLgyEYUvDjrGdcPlKjAw==",
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.1.tgz",
+ "integrity": "sha512-0GAJKc1LBXzlWPhtj9uGawIlYSkTXkgpW9wZ97b4ySEuKbE5j9a0OdLGM31AWMhRS2ut49Z0kufSYsamGEIb8Q==",
"peer": true,
"requires": {
"@types/whatwg-url": "^8.2.1",
@@ -7920,9 +7919,8 @@
"version": "0.6.2"
},
"neuroweblab": {
- "version": "git+ssh://git@github.com/neuroanatomy/neuroweblab.git#43eab1a99c9fffe4ff258e31733c600fe447959f",
- "integrity": "sha512-VwXSn9yU7jOKKna2A8hIuBGYKXyixwLquwDfT+P2kJMW8UQJhfGjDNsPusl0xMU6JWT7YCLIbAF/W/RqJNN0eA==",
- "from": "neuroweblab@github:neuroanatomy/neuroweblab#43eab1a99c9fffe4ff258e31733c600fe447959f",
+ "version": "git+ssh://git@github.com/neuroanatomy/neuroweblab.git#38bc9cad27d890c75d0ff32317bb6a3e0f570fb1",
+ "from": "neuroweblab@neuroanatomy/neuroweblab",
"requires": {
"bcrypt": "^5.0.1",
"connect-mongo": "^4.4.1",
diff --git a/package.json b/package.json
index 2c81ef7..8d47542 100644
--- a/package.json
+++ b/package.json
@@ -26,7 +26,7 @@
"monk": "^7.1.1",
"multer": "^1.4.1",
"mustache-express": "latest",
- "neuroweblab": "github:neuroanatomy/neuroweblab#43eab1a99c9fffe4ff258e31733c600fe447959f",
+ "neuroweblab": "github:neuroanatomy/neuroweblab",
"passport": "^0.4.0",
"passport-github": "latest",
"passport-local": "^1.0.0",
diff --git a/test/e2e/microdraw.addRegion.spec.js b/test/e2e/microdraw.addRegion.spec.js
index 7037d77..31fe6d9 100644
--- a/test/e2e/microdraw.addRegion.spec.js
+++ b/test/e2e/microdraw.addRegion.spec.js
@@ -37,6 +37,7 @@ describe('Editing tools: Add regions', () => {
assert(diff<1000, `${diff} pixels were different`);
}).timeout(0);
+ // eslint-disable-next-line max-statements
it('draws a square', async () => {
// select the polygon tool
await shadowclick(UI.DRAWPOLYGON);
@@ -47,6 +48,7 @@ describe('Editing tools: Add regions', () => {
await page.mouse.click(400, 500);
await page.mouse.click(400, 400);
+ await U.waitUntilHTMLRendered(page);
const filename = "addRegion.02.cat-square-A.png";
await page.screenshot({path: U.newPath + filename});
const diff = await U.compareImages(U.newPath + filename, U.refPath + filename);
@@ -61,6 +63,7 @@ describe('Editing tools: Add regions', () => {
await page.mouse.click(450, 550);
await page.mouse.click(450, 450);
+ await U.waitUntilHTMLRendered(page);
const filename = "addRegion.03.cat-square-B.png";
await page.screenshot({path: U.newPath + filename});
const diff = await U.compareImages(U.newPath + filename, U.refPath + filename);
@@ -77,6 +80,7 @@ describe('Editing tools: Add regions', () => {
// click on square B (square A is already selected)
await page.mouse.click(540, 540);
+ await U.waitUntilHTMLRendered(page);
const filename = "addRegion.04.cat-union.png";
await page.screenshot({path: U.newPath + filename});
const diff = await U.compareImages(U.newPath + filename, U.refPath + filename);
diff --git a/test/e2e/microdraw.multiple.spec.js b/test/e2e/microdraw.multiple.spec.js
index 2129373..6013846 100644
--- a/test/e2e/microdraw.multiple.spec.js
+++ b/test/e2e/microdraw.multiple.spec.js
@@ -54,6 +54,7 @@ describe('Editing tools: draw polygons and curves', () => {
await page2.mouse.click(400, 200);
await page2.mouse.click(400, 100);
await shadowclick(UI.SAVE, page2);
+ await U.waitUntilHTMLRendered(page2);
const filename = "multiple.04.page2-square.png";
await page2.screenshot({path: U.newPath + filename});
@@ -61,6 +62,7 @@ describe('Editing tools: draw polygons and curves', () => {
assert(diff {
await shadowclick(UI.DRAWPOLYGON, page1);
await page1.mouse.click(300, 100);
@@ -68,6 +70,7 @@ describe('Editing tools: draw polygons and curves', () => {
await page1.mouse.click(350, 200);
await page1.mouse.click(300, 100);
await shadowclick(UI.SAVE, page1);
+ await U.waitUntilHTMLRendered(page1);
const filename = "multiple.03.page1-triangle.png";
await page1.screenshot({path: U.newPath + filename});
@@ -96,6 +99,7 @@ describe('Editing tools: draw polygons and curves', () => {
await shadowclick(UI.DELETE, page1);
await shadowclick(UI.SAVE, page1);
+ await U.waitUntilHTMLRendered(page1);
const filename = "multiple.06.page1-cleanup.png";
await page1.screenshot({path: U.newPath + filename});
diff --git a/test/e2e/microdraw.order.spec.js b/test/e2e/microdraw.order.spec.js
index 0306d9c..bbbf772 100644
--- a/test/e2e/microdraw.order.spec.js
+++ b/test/e2e/microdraw.order.spec.js
@@ -53,12 +53,14 @@ describe('Editing tools: order', () => {
await shadowclick(UI.SELECT);
await page.mouse.click(500, 100);
+ await U.waitUntilHTMLRendered(page);
const filename = "order.02.triangles.png";
await page.screenshot({path: U.newPath + filename});
const diff = await U.compareImages(U.newPath + filename, U.refPath + filename);
assert(diff<1000, `${diff} pixels were different`);
}).timeout(0);
+ // eslint-disable-next-line max-statements
it('invert the order by sending front', async () => {
for(let i=2; i>=0; i--) {
/* eslint-disable no-await-in-loop */
@@ -71,12 +73,14 @@ describe('Editing tools: order', () => {
await shadowclick(UI.SELECT);
await page.mouse.click(500, 100);
+ await U.waitUntilHTMLRendered(page);
const filename = "order.03.invert.png";
await page.screenshot({path: U.newPath + filename});
const diff = await U.compareImages(U.newPath + filename, U.refPath + filename);
assert(diff {
for (let i = 2; i >= 0; i--) {
/* eslint-disable no-await-in-loop */
@@ -89,6 +93,7 @@ describe('Editing tools: order', () => {
await shadowclick(UI.SELECT);
await page.mouse.click(500, 100);
+ await U.waitUntilHTMLRendered(page);
const filename = "order.04.invert-again.png";
await page.screenshot({path: U.newPath + filename});
const diff = await U.compareImages(U.newPath + filename, U.refPath + filename);
diff --git a/test/e2e/microdraw.splitRegion.spec.js b/test/e2e/microdraw.splitRegion.spec.js
index 2b96be8..bd5e593 100644
--- a/test/e2e/microdraw.splitRegion.spec.js
+++ b/test/e2e/microdraw.splitRegion.spec.js
@@ -35,6 +35,7 @@ describe('Editing tools: split regions', () => {
assert(diff {
await shadowclick(UI.DRAWPOLYGON);
await page.mouse.click(400, 400);
@@ -43,6 +44,7 @@ describe('Editing tools: split regions', () => {
await page.mouse.click(400, 500);
await page.mouse.click(400, 400);
+ await U.waitUntilHTMLRendered(page);
const filename = "split.02.cat-square-E.png";
await page.screenshot({path: U.newPath + filename});
const diff = await U.compareImages(U.newPath + filename, U.refPath + filename);
@@ -56,6 +58,7 @@ describe('Editing tools: split regions', () => {
await page.mouse.click(450, 550);
await page.mouse.click(450, 450);
+ await U.waitUntilHTMLRendered(page);
const filename = "split.03.cat-square-F.png";
await page.screenshot({path: U.newPath + filename});
const diff = await U.compareImages(U.newPath + filename, U.refPath + filename);
@@ -68,6 +71,7 @@ describe('Editing tools: split regions', () => {
// click on square E (square F is already selected)
await page.mouse.click(405, 405);
+ await U.waitUntilHTMLRendered(page);
const filename = "split.04.cat-split.png";
await page.screenshot({path: U.newPath + filename});
const diff = await U.compareImages(U.newPath + filename, U.refPath + filename);
diff --git a/test/e2e/microdraw.subtractRegion.spec.js b/test/e2e/microdraw.subtractRegion.spec.js
index 471667d..381ec13 100644
--- a/test/e2e/microdraw.subtractRegion.spec.js
+++ b/test/e2e/microdraw.subtractRegion.spec.js
@@ -37,6 +37,7 @@ describe('Editing tools: subtract regions', () => {
assert(diff<1000, `${diff} pixels were different`);
}).timeout(0);
+ // eslint-disable-next-line max-statements
it('draws a square', async () => {
// select the polygon tool
await shadowclick(UI.DRAWPOLYGON);
@@ -47,6 +48,7 @@ describe('Editing tools: subtract regions', () => {
await page.mouse.click(400, 500);
await page.mouse.click(400, 400);
+ await U.waitUntilHTMLRendered(page);
const filename = "subtractRegion.02.cat-square-C.png";
await page.screenshot({path: U.newPath + filename});
const diff = await U.compareImages(U.newPath + filename, U.refPath + filename);
@@ -61,6 +63,7 @@ describe('Editing tools: subtract regions', () => {
await page.mouse.click(450, 550);
await page.mouse.click(450, 450);
+ await U.waitUntilHTMLRendered(page);
const filename = "subtractRegion.03.cat-square-D.png";
await page.screenshot({path: U.newPath + filename});
const diff = await U.compareImages(U.newPath + filename, U.refPath + filename);
@@ -73,6 +76,7 @@ describe('Editing tools: subtract regions', () => {
// click on square C (square D is already selected)
await page.mouse.click(405, 405);
+ await U.waitUntilHTMLRendered(page);
const filename = "subtractRegion.04.cat-subtraction.png";
await page.screenshot({path: U.newPath + filename});
const diff = await U.compareImages(U.newPath + filename, U.refPath + filename);
diff --git a/test/e2e/microdraw.toBezier-toPolygon.spec.js b/test/e2e/microdraw.toBezier-toPolygon.spec.js
index 48a7fc8..7ffb6b7 100644
--- a/test/e2e/microdraw.toBezier-toPolygon.spec.js
+++ b/test/e2e/microdraw.toBezier-toPolygon.spec.js
@@ -50,6 +50,7 @@ describe('Editing tools: convert polygons to bézier and vice-versa', () => {
await page.mouse.click(x, y);
}
+ await U.waitUntilHTMLRendered(page);
const filename = "toBezierPolygon.02.cat-star.png";
await page.screenshot({path: U.newPath + filename});
const diff = await U.compareImages(U.newPath + filename, U.refPath + filename);
@@ -59,6 +60,7 @@ describe('Editing tools: convert polygons to bézier and vice-versa', () => {
it('converts the star polygon to a bézier curve', async () => {
await shadowclick(UI.TOBEZIER);
+ await U.waitUntilHTMLRendered(page);
const filename = "toBezierPolygon.03.cat-star-toBezier.png";
await page.screenshot({path: U.newPath + filename});
const diff = await U.compareImages(U.newPath + filename, U.refPath + filename);
@@ -73,6 +75,7 @@ describe('Editing tools: convert polygons to bézier and vice-versa', () => {
await shadowclick(UI.TOPOLYGON);
+ await U.waitUntilHTMLRendered(page);
const filename = "toBezierPolygon.04.cat-star-toPolygon.png";
await page.screenshot({path: U.newPath + filename});
const diff = await U.compareImages(U.newPath + filename, U.refPath + filename);
diff --git a/test/e2e/microdraw.translate-rotate-flip.spec.js b/test/e2e/microdraw.translate-rotate-flip.spec.js
index a3fb369..498e1c2 100644
--- a/test/e2e/microdraw.translate-rotate-flip.spec.js
+++ b/test/e2e/microdraw.translate-rotate-flip.spec.js
@@ -37,6 +37,7 @@ describe('Editing tools: Translate, rotate, flip', () => {
assert(diff<1000, `${diff} pixels were different`);
}).timeout(0);
+ // eslint-disable-next-line max-statements
it('draws a square', async () => {
// select the polygon tool
await shadowclick(UI.DRAWPOLYGON);
@@ -47,12 +48,14 @@ describe('Editing tools: Translate, rotate, flip', () => {
await page.mouse.click(400, 500);
await page.mouse.click(400, 400);
+ await U.waitUntilHTMLRendered(page);
const filename = "transform.02.cat-square.png";
await page.screenshot({path: U.newPath + filename});
const diff = await U.compareImages(U.newPath + filename, U.refPath + filename);
assert(diff {
await shadowclick(UI.SELECT);
await page.mouse.click(405, 405);
@@ -62,6 +65,7 @@ describe('Editing tools: Translate, rotate, flip', () => {
await page.mouse.move(255, 255, {steps: 10});
await page.mouse.up();
+ await U.waitUntilHTMLRendered(page);
const filename = "transform.03.cat-translate.png";
await page.screenshot({path: U.newPath + filename});
const diff = await U.compareImages(U.newPath + filename, U.refPath + filename);
@@ -80,6 +84,7 @@ describe('Editing tools: Translate, rotate, flip', () => {
await page.mouse.move(450, 300, {steps: 10});
await page.mouse.up();
+ await U.waitUntilHTMLRendered(page);
const filename = "transform.04.cat-rotate.png";
await page.screenshot({path: U.newPath + filename});
const diff = await U.compareImages(U.newPath + filename, U.refPath + filename);
@@ -89,6 +94,7 @@ describe('Editing tools: Translate, rotate, flip', () => {
it('flip', async () => {
await shadowclick(UI.FLIPREGION);
+ await U.waitUntilHTMLRendered(page);
const filename = "transform.05.cat-flip.png";
await page.screenshot({path: U.newPath + filename});
const diff = await U.compareImages(U.newPath + filename, U.refPath + filename);
diff --git a/test/e2e/microdraw.view.spec.js b/test/e2e/microdraw.view.spec.js
index dc5c1fb..9d2ccf3 100644
--- a/test/e2e/microdraw.view.spec.js
+++ b/test/e2e/microdraw.view.spec.js
@@ -49,31 +49,35 @@ describe('View pages and data', () => {
it('can go to the next page', async () => {
await shadowclick(UI.NEXT);
+ await page.waitForFunction('Microdraw.isAnimating === false');
+ await U.waitUntilHTMLRendered(page);
const filename = "view.03.cat-next.png";
await page.screenshot({path: U.newPath + filename});
- await page.waitForFunction('Microdraw.isAnimating === false');
const diff = await U.compareImages(U.newPath + filename, U.refPath + filename);
assert(diff {
await shadowclick(UI.PREVIOUS);
+ await page.waitForFunction('Microdraw.isAnimating === false');
+ await U.waitUntilHTMLRendered(page);
const filename = "view.04.cat-prev.png";
await page.screenshot({path: U.newPath + filename});
- await page.waitForFunction('Microdraw.isAnimating === false');
const diff = await U.compareImages(U.newPath + filename, U.refPath + filename);
assert(diff {
await shadowclick(UI.ZOOMIN);
+ await page.waitForFunction('Microdraw.isAnimating === false');
+ await U.waitUntilHTMLRendered(page);
const filename = "view.05.cat-zoom-in.png";
await page.screenshot({path: U.newPath + filename});
- await page.waitForFunction('Microdraw.isAnimating === false');
const diff = await U.compareImages(U.newPath + filename, U.refPath + filename);
assert(diff {
await shadowclick(UI.NAVIGATE);
@@ -82,6 +86,7 @@ describe('View pages and data', () => {
await page.mouse.move(U.width*2/3, U.height/2, {steps:50});
await page.mouse.up();
await page.waitForFunction('Microdraw.isAnimating === false');
+ await U.waitUntilHTMLRendered(page);
const filename = "view.06.cat-zoom-in-translate.png";
await page.screenshot({path: U.newPath + filename});
const diff = await U.compareImages(U.newPath + filename, U.refPath + filename);
@@ -90,6 +95,7 @@ describe('View pages and data', () => {
it('can zoom out', async () => {
await shadowclick(UI.ZOOMOUT);
+ await U.waitUntilHTMLRendered(page);
const filename = "view.07.cat-zoom-out.png";
await page.screenshot({path: U.newPath + filename});
const diff = await U.compareImages(U.newPath + filename, U.refPath + filename);