Salle de conférence expérimentale
This commit is contained in:
parent
54e8cf23f7
commit
18dc4135c4
|
@ -4035,6 +4035,11 @@
|
||||||
"randomfill": "^1.0.3"
|
"randomfill": "^1.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"crypto-js": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-bzHZN8Pn+gS7DQA6n+iUmBfl0hO5DJq++QP3U6uTucDtk/0iGpXd/Gg7CGR0p8tJhofJyaKoWBuJI4eAO00BBg=="
|
||||||
|
},
|
||||||
"css": {
|
"css": {
|
||||||
"version": "2.2.4",
|
"version": "2.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz",
|
||||||
|
@ -5419,6 +5424,11 @@
|
||||||
"integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==",
|
"integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"get-browser-rtc": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-browser-rtc/-/get-browser-rtc-1.0.2.tgz",
|
||||||
|
"integrity": "sha1-u81AyEUaftTvXDc7gWmkCd0dEdk="
|
||||||
|
},
|
||||||
"get-caller-file": {
|
"get-caller-file": {
|
||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||||
|
@ -6055,8 +6065,7 @@
|
||||||
"inherits": {
|
"inherits": {
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"ini": {
|
"ini": {
|
||||||
"version": "1.3.5",
|
"version": "1.3.5",
|
||||||
|
@ -6376,6 +6385,11 @@
|
||||||
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
|
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"isomorphic.js": {
|
||||||
|
"version": "0.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.1.5.tgz",
|
||||||
|
"integrity": "sha512-MkX5lLQApx/8IAIU31PKvpAZosnu2Jqcj1rM8TzxyA4CR96tv3SgMKQNTCxL58G7696Q57zd7ubHV/hTg+5fNA=="
|
||||||
|
},
|
||||||
"isstream": {
|
"isstream": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
||||||
|
@ -6496,6 +6510,14 @@
|
||||||
"leven": "^3.1.0"
|
"leven": "^3.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"lib0": {
|
||||||
|
"version": "0.2.34",
|
||||||
|
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.34.tgz",
|
||||||
|
"integrity": "sha512-cqsVIMPgFlDtgQcpkt7HOY6W3sbYPIe3qxMnbRSwHTgiQancgm+TRDPx28mC6GUZ6lG6Nr0bIWf4Nog6dWUNUg==",
|
||||||
|
"requires": {
|
||||||
|
"isomorphic.js": "^0.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"load-json-file": {
|
"load-json-file": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
|
||||||
|
@ -8006,11 +8028,15 @@
|
||||||
"integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==",
|
"integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"queue-microtask": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-eY/4Obve9cE5FK8YvC1cJsm5cr7XvAurul8UtBDJ2PR1p5NmAwHtvAt5ftcLtwYRCUKNhxCneZZlxmUDFoSeKA=="
|
||||||
|
},
|
||||||
"randombytes": {
|
"randombytes": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||||
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
|
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"safe-buffer": "^5.1.0"
|
"safe-buffer": "^5.1.0"
|
||||||
}
|
}
|
||||||
|
@ -8968,6 +8994,30 @@
|
||||||
"integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==",
|
"integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"simple-peer": {
|
||||||
|
"version": "9.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/simple-peer/-/simple-peer-9.7.2.tgz",
|
||||||
|
"integrity": "sha512-xeMyxa9B4V0eA6mf17fVr8nm2QhAYFu+ZZv8zkSFFTjJETGF227CshwobrIYZuspJglMD63egcevQXGOrTIsuA==",
|
||||||
|
"requires": {
|
||||||
|
"debug": "^4.0.1",
|
||||||
|
"get-browser-rtc": "^1.0.0",
|
||||||
|
"queue-microtask": "^1.1.0",
|
||||||
|
"randombytes": "^2.0.3",
|
||||||
|
"readable-stream": "^3.4.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"readable-stream": {
|
||||||
|
"version": "3.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||||
|
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||||
|
"requires": {
|
||||||
|
"inherits": "^2.0.3",
|
||||||
|
"string_decoder": "^1.1.1",
|
||||||
|
"util-deprecate": "^1.0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"slash": {
|
"slash": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz",
|
||||||
|
@ -9397,7 +9447,6 @@
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"safe-buffer": "~5.1.0"
|
"safe-buffer": "~5.1.0"
|
||||||
}
|
}
|
||||||
|
@ -10114,8 +10163,7 @@
|
||||||
"util-deprecate": {
|
"util-deprecate": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
|
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"util.promisify": {
|
"util.promisify": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
@ -10811,6 +10859,33 @@
|
||||||
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"y-protocols": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-QP3fCM7c2gGfUi2nqf8gspyO4VW23zv3kNqPNdD3wNxMbuNQenMyoDVZYEo12jzR4RQ3aaDfPK62Sf31SVOmfg==",
|
||||||
|
"requires": {
|
||||||
|
"lib0": "^0.2.28"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"y-webrtc": {
|
||||||
|
"version": "10.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/y-webrtc/-/y-webrtc-10.1.6.tgz",
|
||||||
|
"integrity": "sha512-b3pTIv9LcPuMb4nbDT3/kkgmcuQoTrBmaPbBqPH1LJMzI8HwYnMK8p5r0fBQJBI0YRor+i8BT15Evv1nQBP0zg==",
|
||||||
|
"requires": {
|
||||||
|
"lib0": "^0.2.32",
|
||||||
|
"simple-peer": "^9.7.2",
|
||||||
|
"ws": "^7.2.0",
|
||||||
|
"y-protocols": "^1.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ws": {
|
||||||
|
"version": "7.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz",
|
||||||
|
"integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==",
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"y18n": {
|
"y18n": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
|
||||||
|
@ -10936,6 +11011,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"yjs": {
|
||||||
|
"version": "13.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/yjs/-/yjs-13.4.1.tgz",
|
||||||
|
"integrity": "sha512-kIh0sprCTzIm2qyr1VsovkvjKzD2GR4WcU/McJpLAEvImCJHA78Q3S6uSLnhZX0i7FQdrLPCRT8DtTPEH73jnw==",
|
||||||
|
"requires": {
|
||||||
|
"lib0": "^0.2.33"
|
||||||
|
}
|
||||||
|
},
|
||||||
"zen-observable": {
|
"zen-observable": {
|
||||||
"version": "0.8.15",
|
"version": "0.8.15",
|
||||||
"resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz",
|
"resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz",
|
||||||
|
|
|
@ -56,6 +56,7 @@
|
||||||
"bs58": "^4.0.1",
|
"bs58": "^4.0.1",
|
||||||
"bulma": "^0.9.0",
|
"bulma": "^0.9.0",
|
||||||
"bulma-timeline": "^3.0.4",
|
"bulma-timeline": "^3.0.4",
|
||||||
|
"crypto-js": "^4.0.0",
|
||||||
"graphql": "^15.3.0",
|
"graphql": "^15.3.0",
|
||||||
"react": "^16.12.0",
|
"react": "^16.12.0",
|
||||||
"react-dom": "^16.12.0",
|
"react-dom": "^16.12.0",
|
||||||
|
@ -66,6 +67,8 @@
|
||||||
"redux-saga": "^1.1.3",
|
"redux-saga": "^1.1.3",
|
||||||
"styled-components": "^4.4.1",
|
"styled-components": "^4.4.1",
|
||||||
"subscriptions-transport-ws": "^0.9.17",
|
"subscriptions-transport-ws": "^0.9.17",
|
||||||
"typescript": "^3.8.3"
|
"typescript": "^3.8.3",
|
||||||
|
"y-webrtc": "^10.1.6",
|
||||||
|
"yjs": "^13.4.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { createClient } from '../util/apollo';
|
||||||
import { ApolloProvider } from '@apollo/client';
|
import { ApolloProvider } from '@apollo/client';
|
||||||
import { LogoutPage } from './LogoutPage';
|
import { LogoutPage } from './LogoutPage';
|
||||||
import { UnauthorizedPage } from './UnauthorizedPage/UnauthorizedPage';
|
import { UnauthorizedPage } from './UnauthorizedPage/UnauthorizedPage';
|
||||||
|
import { ConferencePage } from './ConferencePage/ConferencePage';
|
||||||
|
|
||||||
export interface AppProps {
|
export interface AppProps {
|
||||||
|
|
||||||
|
@ -44,6 +45,7 @@ export const App: FunctionComponent<AppProps> = () => {
|
||||||
<Route path="/" exact component={HomePage} />
|
<Route path="/" exact component={HomePage} />
|
||||||
<Route path="/unauthorized" exact component={UnauthorizedPage} />
|
<Route path="/unauthorized" exact component={UnauthorizedPage} />
|
||||||
<PrivateRoute path="/profile" exact component={ProfilePage} />
|
<PrivateRoute path="/profile" exact component={ProfilePage} />
|
||||||
|
<PrivateRoute path="/conference" exact component={ConferencePage} />
|
||||||
<PrivateRoute path="/workgroups/:id" exact component={WorkgroupPage} />
|
<PrivateRoute path="/workgroups/:id" exact component={WorkgroupPage} />
|
||||||
<PrivateRoute path="/decisions/:id" component={DecisionSupportFilePage} />
|
<PrivateRoute path="/decisions/:id" component={DecisionSupportFilePage} />
|
||||||
<PrivateRoute path="/dashboard" exact component={DashboardPage} />
|
<PrivateRoute path="/dashboard" exact component={DashboardPage} />
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
import React, { FunctionComponent, useEffect } from 'react';
|
||||||
|
import { useUserProfile } from '../../gql/queries/profile';
|
||||||
|
import { useConference } from '../../hooks/useConference';
|
||||||
|
import { Page } from '../Page';
|
||||||
|
import { Gravatar } from './Gravatar';
|
||||||
|
|
||||||
|
export interface ConferencePageProps {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const StatusHandRaised = 'hand-raised';
|
||||||
|
const StatusThumbsUp = 'thumbs-up';
|
||||||
|
const StatusThumbsDown = 'thumbs-down';
|
||||||
|
const StatusNoVote = 'no-vote';
|
||||||
|
|
||||||
|
export const ConferencePage:FunctionComponent<ConferencePageProps> = () => {
|
||||||
|
const { user } = useUserProfile();
|
||||||
|
const { id, data, peers, setNickname, setEmail, ping, setStatus } = useConference();
|
||||||
|
|
||||||
|
const currentStatus = data.statuses[id];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (peers.length === 0) return;
|
||||||
|
if (!id || (!user.name && !user.email)) return;
|
||||||
|
setNickname(user.name || user.email.split('@')[0]);
|
||||||
|
setEmail(user.email);
|
||||||
|
}, [user.name, user.email, peers.length]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
ping();
|
||||||
|
const intervalId = setInterval(() => ping(), 30000);
|
||||||
|
return () => clearInterval(intervalId);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onStatusChange = (status: string) => {
|
||||||
|
setStatus(currentStatus === status ? '' : status);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Page title="Conference">
|
||||||
|
<div className="container is-fluid">
|
||||||
|
<section className="mt-5">
|
||||||
|
<h3 className="is-size-3">Mes actions</h3>
|
||||||
|
<div className="buttons has-addons">
|
||||||
|
<button
|
||||||
|
className={`button is-medium ${currentStatus === StatusHandRaised ? 'is-info is-selected' : ''}`}
|
||||||
|
onClick={onStatusChange.bind(null, StatusHandRaised)}>
|
||||||
|
<span className="icon">
|
||||||
|
<i className="fa fa-hand-paper"></i>
|
||||||
|
</span>
|
||||||
|
<span>Lever la main</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`button is-medium ${currentStatus === StatusThumbsUp ? 'is-success is-selected' : ''}`}
|
||||||
|
onClick={onStatusChange.bind(null, StatusThumbsUp)}>
|
||||||
|
<span className="icon">
|
||||||
|
<i className="fa fa-thumbs-up"></i>
|
||||||
|
</span>
|
||||||
|
<span>Voter pour</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`button is-medium ${currentStatus === StatusNoVote ? 'is-warning is-selected' : ''}`}
|
||||||
|
onClick={onStatusChange.bind(null, StatusNoVote)}>
|
||||||
|
<span className="icon">
|
||||||
|
<i className="fa fa-mitten"></i>
|
||||||
|
</span>
|
||||||
|
<span>Ne se prononce pas</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`button is-medium ${currentStatus === StatusThumbsDown ? 'is-danger is-selected' : ''}`}
|
||||||
|
onClick={onStatusChange.bind(null, StatusThumbsDown)}>
|
||||||
|
<span className="icon">
|
||||||
|
<i className="fa fa-thumbs-down"></i>
|
||||||
|
</span>
|
||||||
|
<span>Voter contre</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<h3 className="is-size-3">Assemblée</h3>
|
||||||
|
<div className="columns mt-1">
|
||||||
|
<UserCard className="column is-narrow"
|
||||||
|
nickname={data.nicknames[id]}
|
||||||
|
status={currentStatus}
|
||||||
|
email={user.email} />
|
||||||
|
{
|
||||||
|
peers.map(p => {
|
||||||
|
const nickname = data.nicknames[p] || '???';
|
||||||
|
const email = data.emails[p] || '';
|
||||||
|
return (
|
||||||
|
<UserCard key={`peer-${p}`} className="column is-narrow"
|
||||||
|
nickname={nickname}
|
||||||
|
status={data.statuses[p]}
|
||||||
|
email={email} />
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</Page>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserCardProps {
|
||||||
|
nickname: string
|
||||||
|
email: string
|
||||||
|
className?: string
|
||||||
|
status: string
|
||||||
|
};
|
||||||
|
|
||||||
|
export const UserCard:FunctionComponent<UserCardProps> = ({ nickname, email, className, status }) => {
|
||||||
|
return (
|
||||||
|
<div className={className}>
|
||||||
|
<div className="box">
|
||||||
|
<div className="has-text-centered">
|
||||||
|
<div className="mb-1">
|
||||||
|
{ !status ? <span className="icon"><i className="far fa-2x fa-meh-blank"></i></span> : null }
|
||||||
|
{ status === StatusHandRaised ? <span className="icon has-text-info"><i className="fa fa-2x fa-hand-paper"></i></span> : null }
|
||||||
|
{ status === StatusThumbsUp ? <span className="icon has-text-success"><i className="fa fa-2x fa-thumbs-up"></i></span> : null }
|
||||||
|
{ status === StatusNoVote ? <span className="icon has-text-warning"><i className="fa fa-2x fa-mitten"></i></span> : null }
|
||||||
|
{ status === StatusThumbsDown ? <span className="icon has-text-danger"><i className="fa fa-2x fa-thumbs-down"></i></span> : null }
|
||||||
|
</div>
|
||||||
|
<figure className="image is-128x128 is-inline-block">
|
||||||
|
<Gravatar className="is-rounded" email={email} />
|
||||||
|
</figure>
|
||||||
|
<h4 className="is-size-4">{nickname}</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,20 @@
|
||||||
|
import React, { FunctionComponent, useEffect, useState } from 'react';
|
||||||
|
import md5 from 'crypto-js/md5';
|
||||||
|
|
||||||
|
export interface GravatarProps {
|
||||||
|
className?: string
|
||||||
|
email: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultAvatarUrl = 'https://www.gravatar.com/avatar/00000000000000000000000000000000?d=mp&s=128';
|
||||||
|
|
||||||
|
export const Gravatar:FunctionComponent<GravatarProps> = ({ className, email }) => {
|
||||||
|
const [ avatarUrl, setAvatarUrl ] = useState(defaultAvatarUrl);
|
||||||
|
useEffect(() => {
|
||||||
|
const hash = md5(email.trim().toLowerCase());
|
||||||
|
setAvatarUrl(`https://www.gravatar.com/avatar/${hash}?d=mp&s=128`);
|
||||||
|
}, [email]);
|
||||||
|
return (
|
||||||
|
<img className={className} src={avatarUrl} />
|
||||||
|
);
|
||||||
|
}
|
|
@ -31,6 +31,20 @@ export function Navbar() {
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div className={`navbar-menu ${isActive ? 'is-active' : ''}`}>
|
<div className={`navbar-menu ${isActive ? 'is-active' : ''}`}>
|
||||||
|
<div className="navbar-start">
|
||||||
|
{
|
||||||
|
loggedIn ?
|
||||||
|
<React.Fragment>
|
||||||
|
<Link to="/dashboard" className="navbar-item">
|
||||||
|
<i className="fa fa-columns"></i> Tableau de bord
|
||||||
|
</Link>
|
||||||
|
<Link to="/conference" className="navbar-item">
|
||||||
|
<i className="fa fa-users"></i> Conférence
|
||||||
|
</Link>
|
||||||
|
</React.Fragment> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
</div>
|
||||||
<div className="navbar-end">
|
<div className="navbar-end">
|
||||||
<div className="navbar-item">
|
<div className="navbar-item">
|
||||||
<div className="buttons">
|
<div className="buttons">
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
import * as Y from 'yjs'
|
||||||
|
import { WebrtcProvider, } from 'y-webrtc'
|
||||||
|
import { useEffect, useRef, useState } from 'react'
|
||||||
|
|
||||||
|
export function useConference() {
|
||||||
|
const docRef = useRef(new Y.Doc());
|
||||||
|
|
||||||
|
const [ state, setState ] = useState({
|
||||||
|
data: {
|
||||||
|
heartbeats: {},
|
||||||
|
emails: {},
|
||||||
|
nicknames: {},
|
||||||
|
statuses: {},
|
||||||
|
},
|
||||||
|
peers: [],
|
||||||
|
id: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const setData = (key: string, value: any) => {
|
||||||
|
setState(state => ({...state, data: { ...state.data, [key]: value }}));
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const doc = docRef.current;
|
||||||
|
const roomName = `${window.location.protocol}//${window.location.host}/daddy/conference`;
|
||||||
|
const provider = new WebrtcProvider(roomName, docRef.current);
|
||||||
|
|
||||||
|
const onPeers = (evt) => {
|
||||||
|
let peers = [...state.peers];
|
||||||
|
peers = peers.filter(p => evt.removed.indexOf(p) === -1);
|
||||||
|
peers.push(...evt.added);
|
||||||
|
setState(state => ({ ...state, id: provider.room.peerId, peers }));
|
||||||
|
};
|
||||||
|
|
||||||
|
provider.on('peers', onPeers);
|
||||||
|
|
||||||
|
const heartbeats = doc.getMap('heartbeats');
|
||||||
|
heartbeats.observe(evt => setData('heartbeats', evt.currentTarget.toJSON()));
|
||||||
|
|
||||||
|
const nicknames = doc.getMap('nicknames');
|
||||||
|
nicknames.observe(evt => setData('nicknames', evt.currentTarget.toJSON()));
|
||||||
|
|
||||||
|
const emails = doc.getMap('emails');
|
||||||
|
emails.observe(evt => setData('emails', evt.currentTarget.toJSON()));
|
||||||
|
|
||||||
|
const statuses = doc.getMap('statuses');
|
||||||
|
statuses.observe(evt => setData('statuses', evt.currentTarget.toJSON()));
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
provider.off('peers', onPeers);
|
||||||
|
provider.destroy();
|
||||||
|
docRef.current.destroy();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: state.data,
|
||||||
|
peers: state.peers,
|
||||||
|
id: state.id,
|
||||||
|
|
||||||
|
setStatus: (status: string) => {
|
||||||
|
const doc = docRef.current;
|
||||||
|
const statuses = doc.getMap('statuses');
|
||||||
|
statuses.set(state.id, status);
|
||||||
|
},
|
||||||
|
|
||||||
|
ping: () => {
|
||||||
|
const doc = docRef.current;
|
||||||
|
const heartbeats = doc.getMap('heartbeats');
|
||||||
|
heartbeats.set(state.id, (new Date()).toJSON());
|
||||||
|
},
|
||||||
|
|
||||||
|
setNickname: (nickname: string) => {
|
||||||
|
const doc = docRef.current;
|
||||||
|
const nicknames = doc.getMap('nicknames');
|
||||||
|
nicknames.set(state.id, nickname);
|
||||||
|
},
|
||||||
|
|
||||||
|
setEmail: (email: string) => {
|
||||||
|
const doc = docRef.current;
|
||||||
|
const emails = doc.getMap('emails');
|
||||||
|
emails.set(state.id, email);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
|
@ -96,6 +96,7 @@ func Mount(r *chi.Mux, config *config.Config) error {
|
||||||
"/dashboard",
|
"/dashboard",
|
||||||
"/decisions/*",
|
"/decisions/*",
|
||||||
"/unauthorized",
|
"/unauthorized",
|
||||||
|
"/conference",
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, cr := range clientRoutes {
|
for _, cr := range clientRoutes {
|
||||||
|
|
Loading…
Reference in New Issue