From 2b6c57ba4c95adece357da7db79220dc46f3eca8 Mon Sep 17 00:00:00 2001 From: AxelRagobert Date: Thu, 16 May 2019 17:25:09 +0200 Subject: [PATCH] FEATURE : Duplicate portfolio (closes #190) --- etapes | 2 + package.json | 5 +- reverse-proxy/package.json | 18 ++ reverse-proxy/server.js | 21 ++ .../Authenticated/Authenticated.jsx | 37 ++- src/components/Duplication/Duplicator.jsx | 213 ++++++++++++++++++ src/components/Item/Item.jsx | 10 +- src/components/Outliner/Outliner.jsx | 8 +- src/components/Portfolio/Portfolio.jsx | 14 +- src/config/config.json | 3 +- src/images/icons8-plus-64.png | Bin 0 -> 1198 bytes src/styles/App.css | 102 ++++++++- 12 files changed, 405 insertions(+), 28 deletions(-) create mode 100644 etapes create mode 100644 reverse-proxy/package.json create mode 100644 reverse-proxy/server.js create mode 100644 src/components/Duplication/Duplicator.jsx create mode 100644 src/images/icons8-plus-64.png diff --git a/etapes b/etapes new file mode 100644 index 00000000..7b129309 --- /dev/null +++ b/etapes @@ -0,0 +1,2 @@ +modifier etc host +ajouter reverse proxy en local diff --git a/package.json b/package.json index 2d515f3f..41fb8bf0 100644 --- a/package.json +++ b/package.json @@ -5,15 +5,18 @@ "dependencies": { "bootstrap": "^4.1.1", "hypertopic": "^3.1.4", + "jquery": "^3.4.1", "js-tree": "^2.0.1", "json-groupby": "^1.0.2", "open-iconic": "^1.1.1", "query-string": "^4.3.4", "react": "^16.4.0", "react-autosuggest": "^9.3.4", + "react-bootstrap": "^1.0.0-beta.8", "react-dom": "^16.4.0", "react-router-dom": "^4.1.1", - "sort-by": "^1.2.0" + "sort-by": "^1.2.0", + "styled-components": "^4.2.0" }, "devDependencies": { "react-scripts": "1.1.5", diff --git a/reverse-proxy/package.json b/reverse-proxy/package.json new file mode 100644 index 00000000..bc62d589 --- /dev/null +++ b/reverse-proxy/package.json @@ -0,0 +1,18 @@ +{ + "name": "reverse-proxy", + "version": "1.0.0", + "description": "", + "main": "server.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "node server.js" + }, + "author": "", + "license": "ISC", + "dependencies": { + "cors": "^2.8.5", + "express": "^4.17.1", + "express-http-proxy": "^1.5.1", + "http-proxy": "^1.17.0" + } +} diff --git a/reverse-proxy/server.js b/reverse-proxy/server.js new file mode 100644 index 00000000..eeac98e6 --- /dev/null +++ b/reverse-proxy/server.js @@ -0,0 +1,21 @@ +var express = require('express') +var cors = require('cors') +const proxy = require('express-http-proxy'); + +var app = express() + +var corsOptions = { + origin: true, + optionsSuccessStatus: 200, + credentials: true +} + +app.use(cors(corsOptions)); +app.use(proxy('argos2.test.hypertopic.org')); + + + + +app.listen(80, function () { + console.log('CORS-enabled web server listening on port 80') +}) \ No newline at end of file diff --git a/src/components/Authenticated/Authenticated.jsx b/src/components/Authenticated/Authenticated.jsx index 0394c93a..deaddcfc 100644 --- a/src/components/Authenticated/Authenticated.jsx +++ b/src/components/Authenticated/Authenticated.jsx @@ -1,5 +1,7 @@ import React, { Component } from 'react'; import conf from '../../config/config.json'; +import Duplicator from '../Duplication/Duplicator' +import { Dropdown, DropdownButton, ButtonGroup, Button, Form } from 'react-bootstrap'; const SESSION_URI = conf.services[0] + '/_session'; @@ -18,24 +20,39 @@ class Authenticated extends Component { render() { if (this.state.user) { + if(this.props.portfolio){ + return ( +
+ + Se déconnecter + + +
+ ); + } + return ( -
{this.state.user} - Se déconnecter +
+ + Se déconnecter +
); } if (this.state.ask) { return( -
- this.login = x} /> - this.password = x} type="password" /> - -
+
+ this.login = x} placeholder="Nom d'utilisateur" /> + this.password = x} placeholder="Mot de passe" /> + + ); } return (
- Se connecter... +
); } @@ -82,7 +99,9 @@ class Authenticated extends Component { _closeSession() { fetch(SESSION_URI, {method:'DELETE', credentials:'include'}) - .then(() => this.setState({user: ''})); + .then(() => { + this.setState({user: ''}) + }); } componentDidMount() { diff --git a/src/components/Duplication/Duplicator.jsx b/src/components/Duplication/Duplicator.jsx new file mode 100644 index 00000000..df9e021e --- /dev/null +++ b/src/components/Duplication/Duplicator.jsx @@ -0,0 +1,213 @@ +import React, { Component } from 'react'; +import { Modal, Button, Dropdown } from 'react-bootstrap'; +import $ from 'jquery' +var hypertopic = require("hypertopic") + +class Duplicator extends Component { + + constructor(props) { + super(props); + + this.state = { + showModal: false, + showModalConfirmation: false, + showToast: false, + corpora: [], + viewpoints: [] + }; + this.handleShow = this.handleShow.bind(this); + this.handleClose = this.handleClose.bind(this); + this.handleValidate = this.handleValidate.bind(this); + this.handleConfirm = this.handleConfirm.bind(this); + this.onChangeCheckBox = this.onChangeCheckBox.bind(this); + this.closeToast = this.closeToast.bind(this) + this.addUserToEntity = this.addUserToEntity.bind(this) + } + + handleClose() { + this.setState({ showModal: false, showModalConfirmation: false }); + } + + handleShow() { + this.setState({ showModal: true }); + } + + handleValidate() { + if(this.state.viewpoints.length > 0 || this.state.corpora.length > 0) { + console.log(this.state.viewpoints) + console.log(this.state.corpora) + this.nameDuplicatedPortfolio = $('#portfolioDuplicatedName').val() + this.handleClose(); + this.setState({ showModalConfirmation: true}) + } + } + + async handleConfirm() { + this.setState({ showModalConfirmation: false, showToast: true }); + + var ids = this.state.viewpoints.concat(this.state.corpora) + let that = this + + await Promise.all(ids.map(id => that.addUserToEntity(id))) + //refresh page + this.setState({showToast: false }); + window.location.replace("http://" + this.nameDuplicatedPortfolio + ".local:3000"); + + } + + addUserToEntity(user){ + let db = hypertopic([ + "http://localhost", + "http://steatite.hypertopic.org" + ]); + + const _error = (x) => { + console.error(x.message) + return x + }; + + return db.get({_id:user}) + .then( (x) => { + if(!x.users.includes(this.nameDuplicatedPortfolio)) + x.users.push(this.nameDuplicatedPortfolio) + return x + }) + .then(db.post) + .then((x) => { + console.log(x) + }) + .catch(_error); + } + + closeToast() { + this.setState(() => { + return { + showToast: false + }; + }); + } + onChangeCheckBox(e) { + $('.checkCorpora').each(function() { + if ($(this).is(":checked")) { + $('.Corpus').each(function() { + $(this).prop('checked', true); + $(this).prop('disabled', true); + }); + }else{ + $('.Corpus').each(function() { + $(this).prop('disabled', false); + }); + } + }); + + $('.checkViewPoints').each(function() { + if ($(this).is(":checked")) { + $('.ViewPoint').each(function() { + $(this).prop('checked', true); + $(this).prop('disabled', true); + }); + }else{ + $('.ViewPoint').each(function() { + $(this).prop('disabled', false); + }); + } + }); + + var selectedViewPoints = []; + $('.ViewPoint').each(function() { + if ($(this).is(":checked")) { + selectedViewPoints.push($(this).attr('value')); + } + }); + + var selectedCorpora = []; + $('.Corpus').each(function() { + if ($(this).is(":checked")) { + selectedCorpora.push($(this).attr('value')); + } + }); + + this.setState({ + corpora: selectedCorpora, + viewpoints: selectedViewPoints + }) + } + + render() { + let name = this.props.userConnected + '-' + this.props.portfolio + let corpora = this.props.corpora.map((v, i) => +
+ + {v.id} +
+ ); + + let viewpoints = this.props.viewpoints.map((v, i) => +
+ + {v.name} +
+ ); + + + return ( +
+ + +

Redirection à la nouvelle page...

+
+
+ + + +

Créer le nouveau portfolio

+
+ + + + +
+ + + +

Nom du portfolio

+ +
+

Corpus

+
Tout
+
+
+
+ {corpora} +
+
+
+

Points de vue

+
Tout
+
+
+
+ {viewpoints} +
+
+ + + + +
+ Dupliquer +
+ + ) + } +} + +export default Duplicator; diff --git a/src/components/Item/Item.jsx b/src/components/Item/Item.jsx index 35afe1b6..528de112 100644 --- a/src/components/Item/Item.jsx +++ b/src/components/Item/Item.jsx @@ -55,10 +55,12 @@ class Item extends Component {
- - - Retour à l'accueil - +
    +
  • + Retour à l'accueil +
  • +
  • +
diff --git a/src/components/Outliner/Outliner.jsx b/src/components/Outliner/Outliner.jsx index af646c0e..877ac22e 100644 --- a/src/components/Outliner/Outliner.jsx +++ b/src/components/Outliner/Outliner.jsx @@ -30,10 +30,12 @@ class Outliner extends React.Component {
- - +
    +
  • Retour à l'accueil - +
  • +
  • +
diff --git a/src/components/Portfolio/Portfolio.jsx b/src/components/Portfolio/Portfolio.jsx index 5bca632d..67b7a1f7 100644 --- a/src/components/Portfolio/Portfolio.jsx +++ b/src/components/Portfolio/Portfolio.jsx @@ -20,8 +20,9 @@ class Portfolio extends Component { corpora: [], items: [], selectedItems: [], - topicsItems: new Map() + topicsItems: new Map(), }; + this.user = conf.user || window.location.hostname.split('.', 1)[0]; this._updateSelection(); } @@ -30,12 +31,15 @@ class Portfolio extends Component { let viewpoints = this._getViewpoints(); let corpora = this._getCorpora(); let status = this._getStatus(); + return (
- - {status} +
    + {status} +
  • +
@@ -101,9 +105,9 @@ class Portfolio extends Component { let uri = '?' + queryString.stringify({ t: this._toggleTopic(this.selection, t) }); - return + return
  • {topic.name} - ; +
  • ; }); return topics.length ? topics : 'Tous les items'; } diff --git a/src/config/config.json b/src/config/config.json index a0134e68..c2f102e0 100644 --- a/src/config/config.json +++ b/src/config/config.json @@ -1,7 +1,6 @@ { - "user": "vitraux", "services": [ - "http://argos2.test.hypertopic.org", + "http://localhost", "http://steatite.hypertopic.org" ] } diff --git a/src/images/icons8-plus-64.png b/src/images/icons8-plus-64.png new file mode 100644 index 0000000000000000000000000000000000000000..ac7126c0c9c0b3a5c5e5f9b6024ae6860b60a494 GIT binary patch literal 1198 zcmV;f1X25mP)Xc8E+jt)LDGtrp%BCGhCAc~;k zLUg6zW8q2=3@Er1e1W)8a4W_KDsBXE5ho8t#TP-%LnXQil0+sGltgDf7u~szr>pz6 zT~&SWIQ_$2)TQqK|L5H5>f5Kzl{$3j(4oW9g=<3un3OanX@jJ3Nf${PG1E>-IwePI!-Tu&A?X) z0=M`AxF(kG;5`6XF6kLb4@)Zhc4j2)lr$}AucZBwj+r^5lGaIDFKJ8uT2;a~FX=@| zk2&W~hWHNxFa~^H@=eVFZv(dhcEhW~JwmqR-^sar_!KxlMQn8pUJd-}F*Ogo3XDh8 zngF(!B;hb{Wh||_hNgg7kC_9&^@(*4dUR)j8&m086E^`TJtkfN&Tc_}1@N**|99Zp z)Z*j7Z=NN;Evc~I%M-cwLL3DyP9?Y;_|g;n{YeDtd3w;3gwJTO>DCc@*0by$i7xct zeLd>&^;B$;%@l7%FGUpz0Pu?O_7|`z7P$lV7=eAjIZ*^609Z-!?(k78@|!)^eekW$ zuGt8DTRoS;R4A{2orZLKFsb4vAm@C`(C!E&+++ynfOV0~rbxg9#l31n)o1!WTHa&E z?>p!Ah1pDpynyn7dHe3hguo$_%{zk}{+m1c3dniyGSojdCA!2A&5%9%#xj~J0V{wL zhW^5``Bj&|4Q9M+pwHel;hg(J(r)v%jiraG1lY}Ex~ZuF!2Sj(-3Y4^Fk!}f8{26g z)~k)BhpGhFO3Q)9cG`#adO_L5Gb>0|oAHr4%1t-4x}S5-)v04zuQrw*s#d_cW_+TC z@UY;x8LupxSd{>q>1emAVM0{`X3e-Xi$e%oPPei2P_+jfS5~V(jWsIP@(ZYWjTWx? zx}U3wHD)|hHnD02{9wjW9)ene^=f13p(+9U&3Ll0o%Uh9+E{w1O29W}yrr?7_F>E8 zHkSTaDi1^b$o}__!~F^(Z0k!HT0^!SQA?LU>7rQ0UAz}TX1l#JCX`?RuF*)vL} z`C4U62F(DBs< zi{uXzZU1FvQqq{DwUW-uUy^h*e;tzalcaC8*#9!qp+kob9Toxp20wb+ow;nzg#Z8m M07*qoM6N<$g6GgOZU6uP literal 0 HcmV?d00001 diff --git a/src/styles/App.css b/src/styles/App.css index 22c9cce1..5388ce2f 100644 --- a/src/styles/App.css +++ b/src/styles/App.css @@ -6,6 +6,7 @@ background-color: #8b0000; color: ivory; margin-bottom: 0; + position: relative; } .App header .logo { @@ -27,16 +28,39 @@ h1 a, h1 a:hover { padding: 4px; } -.Authenticated { - position: absolute; - right: 0; +.Status ul::before { + content:"D"; + margin: 1px auto 1px 1px; + visibility: hidden; + padding: 5px; +} +.Status li:last-child { + margin-left: auto; +} +.Status ul { + padding: 0; + margin: 0; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + flex-wrap: wrap; +} +.Status li { + display: flex; + margin: 1px; + padding: 5px; } -.Authenticated a { +.SeConnecterButton { color: lightgrey; padding: 0 8px; } +.UserAuth { + margin-right: 10px; +} + .Authenticated input:not([type='submit']) { background: dimgrey; color: white; @@ -48,6 +72,10 @@ h1 a, h1 a:hover { color: lightgrey; } +.ProfilActions { + background-color: red; +} + .App-content { min-height: 100vh; } @@ -375,3 +403,69 @@ ul.Outliner, .btn:not(.btn-xs) .oi { vertical-align: middle; } + +.Duplicator { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + align-items: center; + justify-content: flex-end; + color: ivory; + font-size: 1.15rem; + font-weight: 500; +} + +.Modal-Group { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + align-items: center; +} + +.Modal-Title { + justify-content: space-between; +} + + +.Modal-Input { + width: 100%; + margin-bottom: 30px; +} + +.Modal-CheckBox { + margin-right: 10px; +} +.toast-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.25rem 0rem; + color: #6c757d; + background-color: hsla(0, 0%, 100%, 0.85); + background-clip: padding-box; + border-bottom: 1px solid rgba(0, 0, 0, 0.05); +} +.toast-body { + padding: 0.75rem; +} +button.close { + padding: 0; + background-color: transparent; + border: 0; + appearance: none; + font-size: 1rem; + font-weight: 700; + line-height: 1; + color: #000; + text-shadow: 0 1px 0 #fff; + opacity: 0.5; +} + +.FormConnect { + display: flex; + flex-direction: row; +} + +.form-control { + margin: 0 5px 0 5px; +} \ No newline at end of file