diff --git a/public/css/main.css b/public/css/main.css index 1e7ae1c..9d9a8fe 100644 --- a/public/css/main.css +++ b/public/css/main.css @@ -167,6 +167,15 @@ div.matrix table td > div.group { height: 100%; box-sizing: border-box; } +div.add-row, +div.add-col { + opacity: 0; +} +div.add-row:hover, +div.add-col:hover { + opacity: 1; +} + div.add-col { width: 4rem; } @@ -186,6 +195,8 @@ div.add-row button { }*/ div.group { position: absolute; +/* border: 1px solid #000;*/ +/* background-color: rgba(255, 255, 255, .75);*/ border: 1px solid rgba(51,51,51,.25); background-color: rgba(225,225,225,.25); padding: 0 0 2rem 2rem; @@ -301,6 +312,11 @@ div.card.focus { div.locked { pointer-events: none !important; } +div.locked div.note, +div.locked div.card, +div.locked div.group { + pointer-events: all; +} div.locked::before { content: attr(data-locked-by); position: absolute; diff --git a/public/js/canvas/zoom.mjs b/public/js/canvas/zoom.mjs index f01e551..68e8d48 100644 --- a/public/js/canvas/zoom.mjs +++ b/public/js/canvas/zoom.mjs @@ -1,9 +1,10 @@ -import { Note, Group, Card } from '../elements/index.mjs'; +import { Note, Group, Card, Matrix } from '../elements/index.mjs'; function zoomstart () { Note.releaseAll(true); - Group.releaseAll(true); Card.releaseAll(true); + Group.releaseAll(true); + Matrix.releaseAll(true); } function zooming () { if (d3.select(this).classed('adding-text') || d3.select(this).classed('changing-text')) return diff --git a/public/js/elements/drag.mjs b/public/js/elements/drag.mjs index 46295c8..1af7552 100644 --- a/public/js/elements/drag.mjs +++ b/public/js/elements/drag.mjs @@ -107,16 +107,17 @@ function dragging (d) { }); } async function dragEnd (d) { + const sel = d3.select(this) + .classed('dragging', false); + if (computeDistance([0, 0], [d.dx, d.dy]) <= 10) { // Note.releaseAll(true); Card.releaseAll(true); - Group.releaseAll(true); + // if (!(sel.classed('group') && d.persistent)) Group.releaseAll(true); // Matrix.releaseAll(true); return console.log('has not moved'); } - const sel = d3.select(this) - .classed('dragging', false); // REACTIVATE ALL textareas AND inputs d3.select('div.canvas').selectAll('textarea, input') .classed('deactivate', false); @@ -140,7 +141,7 @@ async function dragEnd (d) { const { id: gid, tree: gtree, pipe_to } = hit.datum(); d.tree = tree.build(gtree, gid); if (Array.isArray(pipe_to) && pipe_to?.length) pipes = [ ...pipes, ...pipe_to ]; - + } else if (hit.classed('note') || hit.classed('card')) { // IF THE HIT IS A NOTE OR CARD, CREATE A GROUP const { x, y, tree: ntree, piped_from } = hit.datum(); @@ -203,7 +204,7 @@ async function dragEnd (d) { datum: d, bcast: true, }); - Group.release({ group: sel, id: d.id, bcast: true }); + // Group.release({ group: sel, id: d.id, bcast: true }); } else if (sel.classed('matrix')) { await Matrix.update({ matrix: sel, diff --git a/public/js/elements/groups.mjs b/public/js/elements/groups.mjs index 8e91ea2..acd0d69 100644 --- a/public/js/elements/groups.mjs +++ b/public/js/elements/groups.mjs @@ -11,13 +11,13 @@ export const Group = { const constructorRef = this; let { parent, datum, children, focus, bcast, client, immutable } = _kwargs; if (!datum) datum = {}; - let { id, label, x, y, tree: gtree, matrix_index } = datum; + let { id, label, x, y, tree: gtree, matrix_index, persistent } = datum; if (!label) label = ''; if (x === undefined) x = 0; if (y === undefined) y = 0; if (matrix_index) immutable = true; // CHECK IF THIS IS A NEW GROUP - if (!id) datum = await POST('/addGroup', { data: { label, x, y, tree: gtree, matrix_index }, project: wallId }); + if (!id) datum = await POST('/addGroup', { data: { label, x, y, tree: gtree, matrix_index, persistent }, project: wallId }); // REMOVE FOCUS FROM ALL OBJECTS constructorRef.releaseAll(bcast); Note.releaseAll(bcast); @@ -27,8 +27,19 @@ export const Group = { const child = tree.getDepth(gtree) > 1; if (!parent) { if (child) { - const parentNode = d3.selectAll('div.group, div.matrix').filter(d => d.tree === tree.moveUp(gtree) && d.id === +tree.getLeaf(gtree)).node(); + const parentNode = d3.selectAll('div.group, div.matrix table tr.row td') + .filter(function (d) { + const sel = d3.select(this); + if (sel.classed('group')) { + return d.tree === tree.moveUp(gtree) + && d.id === +tree.getLeaf(gtree); + } else { + return d.tree === gtree + && d.matrix_index === matrix_index; + } + }).node(); if (parentNode) parent = d3.select(parentNode); + else parent = d3.select('div.canvas'); } else parent = d3.select('div.canvas'); } // ADD A GROUP @@ -134,8 +145,14 @@ export const Group = { let immutable = false; if (!group) { const { id } = datum; - group = d3.selectAll('div.group') - .filter(d => d.id === id); + if (id) { + group = d3.selectAll('div.group') + .filter(d => d.id === id); + } + // IF NO GROUP IS FOUND, CREATE THE GROUP + if (!group.node()) { // THE GROUP DOES NOT YET EXIST + return constructorRef.add({ datum, bcast }); + } } if (datum) { group.each(d => { @@ -144,7 +161,7 @@ export const Group = { } }); }; - const { tree: gtree, id: gid, matrix_index } = group.datum(); + const { tree: gtree, id: gid, matrix_index, persistent } = group.datum(); if (matrix_index) immutable = true; const child = tree.getDepth(gtree) > 1; @@ -169,7 +186,7 @@ export const Group = { .filter(function () { return this.parentNode === group.node(); }); - const rmGroup = children.size() <= 1 && !immutable; + const rmGroup = children.size() <= 1 && !immutable && !persistent; const childNodes = children.nodes(); for (let i = 0; i < childNodes.length; i ++) { diff --git a/public/js/elements/matrixes.mjs b/public/js/elements/matrixes.mjs index d97436f..4a74422 100644 --- a/public/js/elements/matrixes.mjs +++ b/public/js/elements/matrixes.mjs @@ -24,7 +24,6 @@ export const Matrix = { if (!cols) cols = new Array(2).fill('header');; // CHECK IF THIS IS A NEW MATRIX if (!id) datum = await POST('/addMatrix', { data: { label, x, y, tree: mtree, rows, cols }, project: wallId }); - // REMOVE FOCUS FROM ALL OBJECTS // constructorRef.releaseAll(bcast); Note.releaseAll(bcast); @@ -37,6 +36,7 @@ export const Matrix = { if (child) { const parentNode = d3.selectAll('div.matrix').filter(d => d.tree === tree.moveUp(mtree) && d.id === +tree.getLeaf(mtree)).node(); if (parentNode) parent = d3.select(parentNode); + else parent = d3.select('div.canvas'); } else parent = d3.select('div.canvas'); } // ADD A MATRIX @@ -73,7 +73,6 @@ export const Matrix = { // SAVE AND BROADCAST if (bcast) { await constructorRef.save(matrix.datum()); - constructorRef.broadcast({ operation: 'add', data: matrix.datum() }); } // THE SAVE AND BROADCAST NEEDS TO COME BEFORE HANDLING THE CHILDREN HERE // BECAUSE OTHERWISE, THE CHILDREN GET UPDATED BEFORE THE MATRIXES IS CREATED @@ -85,7 +84,7 @@ export const Matrix = { for (let i = 0; i < mrows.length; i ++) { const row = mrows[i]; - const tr = table.addElems('tr', `row-${i}`) + const tr = table.addElems('tr', `row row-${i}`, d => { return [{ ...d, rowId: i }] }); tr.addElems('th', 'sticky-area immutable row-header', [{ id: mid, label: row, cidx: i }]) .call(constructorRef.addLabel, { constructorRef, axis: 'rows' }); @@ -96,6 +95,7 @@ export const Matrix = { && tree.getLeaf(c.matrix_index) === j.toString(); }); let group; + console.log('cell', cell) if (cell) { const td = tr.addElems('td', `cell-${j}`, [cell]); @@ -103,20 +103,20 @@ export const Matrix = { parent: td, datum: cell, immutable: true, - bcast, // THIS OPERATION MAY BE REDUNDANT + bcast: false, // THIS OPERATION MAY BE REDUNDANT }); } else { // CREATE A NEW CELL const ctree = tree.build(mdtree, `m${mid}`); const cidx = tree.build(i, j); - cell = { tree: ctree, matrix_index: cidx, x: null, y: null }; + cell = { tree: ctree, matrix_index: cidx, x: null, y: null, persistent: true }; const td = tr.addElems('td', `cell-${j}`, [cell]); group = await Group.add({ parent: td, datum: cell, immutable: true, - bcast, // THIS OPERATION MAY BE REDUNDANT + bcast: false, // THIS OPERATION MAY BE REDUNDANT }); // ADD A MINIMUM OF TWO NOTES @@ -124,8 +124,9 @@ export const Matrix = { const ntree = tree.build(gtree, gid); for (let n = 0; n < 2; n ++) { await Note.add({ + parent: group, datum: { tree: ntree, x: null, y: null }, - bcast, // THIS OPERATION MAY BE REDUNDANT + bcast: false, // HERE BROADCAST NEEDS TO BE FALSE TO AVOID DUPLICATE BROADCAST }); } } @@ -145,12 +146,23 @@ export const Matrix = { .addElems('button') .on('click', function () { constructorRef.addAxis({ matrix, axis: 'row' }); - }).html('+') + }).html('+'); + + if (bcast) { + // NEED TO SEPARATE OUT THE BROADCAST HERE IN ORDER TO HAVE note IDs TO BROADCAST + // OTHERWISE THE NOTES GET CREATED AS MANY TIMES AS THERE ARE USERS CONNECTED TO THE ROOM + constructorRef.broadcast({ operation: 'add', data: matrix.datum() }); + // BROADCAST ALL NOTES + matrix.selectAll('div.note') + .each(function (d) { + Note.broadcast({ operation: 'add', data: d }); + }); + } matrix.call(drag); return matrix; }, - update: async function (_kwargs) { // TO DO: IMPROVE THIS + update: async function (_kwargs) { const constructorRef = this; let { matrix, datum, bcast, rows, ncols } = _kwargs; @@ -183,11 +195,28 @@ export const Matrix = { return headers; }).call(constructorRef.addLabel, { constructorRef, axis: 'cols' }); + // SAVE AND BROADCAST + let rmMatrix = false; + if (rmMatrix) { + return await constructorRef.remove({ matrix, gid, bcast }); + } else { + // SAVE AND BROADCAST + if (bcast) { + await constructorRef.save(matrix.datum()); + // constructorRef.broadcast({ operation: 'update', data: matrix.datum() }); + } + } + // THE SAVE AND BROADCAST NEEDS TO COME BEFORE HANDLING THE CHILDREN HERE + // BECAUSE OTHERWISE, THE CHILDREN GET UPDATED BEFORE THE MATRIXES IS CREATED + // IN THE DISPATCHED INSTANCES + // UPDATE THE CONTENT OF THE MATRIX + console.log(mcells) + for (let i = 0; i < mrows.length; i ++) { const row = mrows[i]; - const tr = table.addElems('tr', `row-${i}`) + const tr = table.addElems('tr', `row row-${i}`, d => { return [{ ...d, rowId: i }] }); tr.addElems('th', 'sticky-area immutable row-header', [{ id: mid, label: row, cidx: i }]) .call(constructorRef.addLabel, { constructorRef, axis: 'rows' }); @@ -197,20 +226,24 @@ export const Matrix = { return tree.getRoot(c.matrix_index) === i.toString() && tree.getLeaf(c.matrix_index) === j.toString(); }); - + + // IT IS VERY IMPORTANT TO SET THE td OUTSIDE THE CONDITION + // TO BE ABLE TO FIND IT FOR DISPATCHED GROUPS + const td = tr.addElems('td', `cell-${j}`, [cell || {}]); + if (!cell) { // ONLY ADD CELLS THAT DO NOT EXIST // IF A CELL ALREADY EXISTS, DO NOTHING // ALL TRANSFORMATIONS SHOULD BE HANDLED BY THE Group IN THE CELL // CREATE A NEW CELL const ctree = tree.build(mtree, `m${mid}`); const cidx = tree.build(i, j); - cell = { tree: ctree, matrix_index: cidx, x: null, y: null }; + cell = { tree: ctree, matrix_index: cidx, x: null, y: null, persistent: true }; - const td = tr.addElems('td', `cell-${j}`, [cell]); + td.datum(cell); const group = await Group.add({ parent: td, datum: cell, immutable: true, - bcast, // THIS OPERATION MAY BE REDUNDANT + bcast: false, }); // ADD A MINIMUM OF TWO NOTES @@ -218,8 +251,9 @@ export const Matrix = { const ntree = tree.build(gtree, gid); for (let n = 0; n < 2; n ++) { await Note.add({ + parent: group, datum: { tree: ntree, x: null, y: null }, - bcast, // THIS OPERATION MAY BE REDUNDANT + bcast: false, }); } @@ -231,24 +265,31 @@ export const Matrix = { if (!d.cells) d.cells = []; d.cells.push(group.datum()); }); - // TO DO: INVESTIGATE THIS - // LIKELY WHERE THE DISPATCH ISSUE IS COMING FROM } } } - // SAVE AND BROADCAST - let rmMatrix = false; - if (rmMatrix) { - return await constructorRef.remove({ matrix, gid, bcast }); - } else { - // SAVE AND BROADCAST - if (bcast) { - await constructorRef.save(matrix.datum()); - constructorRef.broadcast({ operation: 'update', data: matrix.datum() }); - } - return matrix; + if (bcast) { + // NEED TO SEPARATE OUT THE BROADCAST HERE IN ORDER TO HAVE note IDs TO BROADCAST + // OTHERWISE THE NOTES GET CREATED AS MANY TIMES AS THERE ARE USERS CONNECTED TO THE ROOM + constructorRef.broadcast({ operation: 'update', data: matrix.datum() }); + // BROADCAST ALL GROUPS + matrix.selectAll('div.group') + .each(function (d) { + Group.broadcast({ operation: 'update', data: d }); + }); + // BROADCAST ALL NOTES + matrix.selectAll('div.note') + .each(function (d) { + Note.broadcast({ operation: 'update', data: d }); + }); + constructorRef.release({ + matrix, + id: mid, + bcast: true, + }) } + return matrix; }, remove: async function (_kwargs) { const constructorRef = this; @@ -316,6 +357,7 @@ export const Matrix = { }); }, save: async function (data) { + console.trace() if (wallId) await POST('/updateMatrix', { data, project: wallId }); else console.log('error: no project to save to'); }, diff --git a/public/js/elements/notes.mjs b/public/js/elements/notes.mjs index 926273d..bc098db 100644 --- a/public/js/elements/notes.mjs +++ b/public/js/elements/notes.mjs @@ -12,7 +12,7 @@ const colors = d3.scaleOrdinal(d3.schemePastel1) export const Note = { add: async function (_kwargs) { const constructorRef = this; - let { datum, focus, bcast, client } = _kwargs; + let { parent, datum, focus, bcast, client } = _kwargs; if (!datum) datum = {}; // MAKE SURE THERE IS A datum OBJECT TO DESTRUCTURE BELOW let { content, color, x, y, id, tree: ntree, pipe_from } = datum; if (!content) content = ''; @@ -30,11 +30,13 @@ export const Note = { Group.releaseAll(bcast); Matrix.releaseAll(bcast); // CHECK IF THIS IS TO BE A CHILD GROUP - let parent = d3.select('div.canvas'); const child = tree.getDepth(ntree) > 1; - if (child) { - const parentNode = d3.selectAll('div.group').filter(d => d.tree === tree.moveUp(ntree) && d.id === +tree.getLeaf(ntree)).node(); - if (parentNode) parent = d3.select(parentNode); + if (!parent) { + if (child) { + const parentNode = d3.selectAll('div.group').filter(d => d.tree === tree.moveUp(ntree) && d.id === +tree.getLeaf(ntree)).node(); + if (parentNode) parent = d3.select(parentNode); + else parent = d3.select('div.canvas'); + } else parent = d3.select('div.canvas'); } // ADD A NOTE const note = parent diff --git a/public/js/load.mjs b/public/js/load.mjs index 5912d45..c1384a8 100644 --- a/public/js/load.mjs +++ b/public/js/load.mjs @@ -56,7 +56,6 @@ async function onLoad () { const content = { title, img, txt, source }; await Card.add({ datum: { content }, i, bcast: true }); } - addDatasource(sourceinfo); }) @@ -66,7 +65,7 @@ async function onLoad () { }); d3.select('button#addGroup') .on('click', async _ => { - await Group.add({ focus: true, bcast: true }); + await Group.add({ datum: { persistent: true }, focus: true, bcast: true }); }); d3.select('button#addMatrix') .on('click', async _ => { @@ -134,6 +133,9 @@ async function onLoad () { if (focus.classed('note')) { const { id } = focus.datum(); await Note.remove({ note: focus, id, bcast: true }); + } else if (focus.classed('group')) { + const { id, persistent } = focus.datum(); + if (persistent) await Group.remove({ group: focus, id, bcast: true }); } else if (focus.classed('matrix')) { const { id } = focus.datum(); await Matrix.remove({ matrix: focus, id, bcast: true }); diff --git a/public/js/websocket/connection.mjs b/public/js/websocket/connection.mjs index 50b484f..7fdfff5 100644 --- a/public/js/websocket/connection.mjs +++ b/public/js/websocket/connection.mjs @@ -28,70 +28,70 @@ export const connectToSocket = function () { if (object === "note") { if (operation === "add") { await Note.add({ datum: data, client }); - console.log("added new simple note"); + // console.log("added new simple note"); } else if (operation === "update") { await Note.update({ datum: data, client }); - console.log("updated simple note"); + // console.log("updated simple note"); } else if (operation === "delete") { await Note.remove({ id: data?.id, client }); - console.log("removed note"); + // console.log("removed note"); } else if (operation === "lock") { await Note.lock({ id: data?.id, client }); - console.log("locked note"); + // console.log("locked note"); } else if (operation === "release") { await Note.release({ id: data?.id, client }); - console.log("locked note"); + // console.log("locked note"); } } else if (object === "group") { if (operation === "add") { await Group.add({ datum: data, client }); - console.log("added new group"); + // console.log("added new group"); } else if (operation === "update") { await Group.update({ datum: data, client }); - console.log("updated group"); + // console.log("updated group"); } else if (operation === "delete") { await Group.remove({ id: data?.id, client }); - console.log("removed group"); + // console.log("removed group"); } else if (operation === "lock") { await Group.lock({ id: data?.id, client }); - console.log("locked group"); + // console.log("locked group"); } else if (operation === "release") { await Group.release({ id: data?.id, client }); - console.log("locked group"); + // console.log("locked group"); } } else if (object === "card") { if (operation === "add") { await Card.add({ datum: data, client }); - console.log("added new card"); + // console.log("added new card"); } else if (operation === "update") { await Card.update({ datum: data, client }); - console.log("updated card"); + // console.log("updated card"); } else if (operation === "delete") { await Card.remove({ id: data?.id, client }); - console.log("removed card"); + // console.log("removed card"); } else if (operation === "lock") { await Card.lock({ id: data?.id, client }); - console.log("locked card"); + // console.log("locked card"); } else if (operation === "release") { await Card.release({ id: data?.id, client }); - console.log("locked card"); + // console.log("locked card"); } } else if (object === "matrix") { if (operation === "add") { await Matrix.add({ datum: data, client }); - console.log("added new matrix"); + // console.log("added new matrix"); } else if (operation === "update") { await Matrix.update({ datum: data, client }); - console.log("updated matrix"); + // console.log("updated matrix"); } else if (operation === "delete") { await Matrix.remove({ id: data?.id, client }); - console.log("removed matrix"); + // console.log("removed matrix"); } else if (operation === "lock") { await Matrix.lock({ id: data?.id, client }); - console.log("locked matrix"); + // console.log("locked matrix"); } else if (operation === "release") { await Matrix.release({ id: data?.id, client }); - console.log("locked matrix"); + // console.log("locked matrix"); } } }; diff --git a/routes/groups.js b/routes/groups.js index 8d1528b..fd03a93 100644 --- a/routes/groups.js +++ b/routes/groups.js @@ -16,13 +16,13 @@ exports.get = (req, res) => { .catch(err => console.log(err)); } exports.add = (req, res) => { - const { label, x, y, project, tree, matrix_index } = req.body.data; + const { label, x, y, project, tree, matrix_index, persistent } = req.body.data; const wallId = req.body.project; DB.conn.one(` - INSERT INTO groups (label, x, y, project, tree, matrix_index) - VALUES ($1, $2, $3, $4::INT, text2ltree($5), text2ltree($6)) + INSERT INTO groups (label, x, y, project, tree, matrix_index, persistent) + VALUES ($1, $2, $3, $4::INT, text2ltree($5), text2ltree($6), $7) RETURNING * - ;`, [label, x, y, wallId, tree || '0', matrix_index]) + ;`, [label, x, y, wallId, tree || '0', matrix_index, persistent || false]) .then(data => res.status(200).json(data)) .catch(err => console.log(err)); }