diff --git a/.gitignore b/.gitignore index d1e880e8..0992cc72 100644 --- a/.gitignore +++ b/.gitignore @@ -99,4 +99,7 @@ tmp .ssh/ #this is used by Jest-mongodb for testing only - -globalConfig.json \ No newline at end of file +globalConfig.json + +#pulled from civil-server +assets/images \ No newline at end of file diff --git a/app/components/agenda-nav.jsx b/app/components/agenda-nav.jsx index 73818857..c40abad3 100644 --- a/app/components/agenda-nav.jsx +++ b/app/components/agenda-nav.jsx @@ -106,5 +106,8 @@ const useStyles = createUseStyles({ 'border-bottom': '1px solid lightGray', 'padding-top': '0.5rem', 'padding-bottom': '0.25rem', + '&:last-child': { + borderBottom: 'none' + } }, }) diff --git a/app/components/begin-button.js b/app/components/begin-button.js index 6b37bc12..54da07ef 100644 --- a/app/components/begin-button.js +++ b/app/components/begin-button.js @@ -5,6 +5,11 @@ import IconPlay from '../svgr/icon-play' const useStyles = createUseStyles({ outer: { + width: '100%', + height: '100%', + '&:disabled': { + backgroundColor: 'gray', + }, opacity: props => props.opacity, '& :hover': { opacity: props => props.opacity * 0.8, @@ -31,12 +36,12 @@ const useStyles = createUseStyles({ }, }) -const BeginButton = ({ children, width, height, onClick, ...props }) => { +const BeginButton = ({ children, width, height, onClick, disabled, ...props }) => { const classes = useStyles(props) return ( -
- -
+ ) } diff --git a/app/components/conversation-header.jsx b/app/components/conversation-header.jsx index a87a8053..bf9d4cc1 100644 --- a/app/components/conversation-header.jsx +++ b/app/components/conversation-header.jsx @@ -112,6 +112,8 @@ const styles = { boxContainer: { width: 'max-content', margin: '1vh', + position: 'absolute', + top: 0, }, portrait: {}, } @@ -137,20 +139,11 @@ function xxxx_xx_xxTommmdd_yyyy(str) { } const LogoLinks = ({ classes, logo }) => { - // curried function to make link - const link_image = src => classname => href => ( - - - - ) - const makeLink = link => { - //builds the link one html attribute at a time using currying - let link_html = link_image - for (const attribute in link) { - link_html = link_html(link[attribute]) - } - return link_html - } + + const makeLink = link => ( + + + ) // Defines the logo link attribute values for logo links in the header const list_of_links = { @@ -169,7 +162,7 @@ const LogoLinks = ({ classes, logo }) => { 'https://res.cloudinary.com/hf6mryjpf/image/upload/v1578591434/assets/Candidate_Conversations_logo-stacked_300_res.png', classname: 'logo', href: 'https://ballotpedia.org/Candidate_Conversations', - }, + } } const { enciv, undebate, ballotpedia } = list_of_links // actual layout of different links @@ -177,7 +170,7 @@ const LogoLinks = ({ classes, logo }) => { <> {' '} {makeLink(enciv)} - {logo && logo === 'undebate' ? makeLink(undebate) : makeLink(ballotpedia)} + {logo !== 'none' ? (list_of_links[logo] ? makeLink(list_of_links[logo]) : makeLink(ballotpedia)) : null} ) } @@ -281,8 +274,8 @@ class ConversationHeader extends React.Component {
{' '} {makeBox('leftBoxContainer')('leftBox')('conversationTopicContent')(subject)} - {makeBox('rightBoxContainer')('rightBox')('conversationElectionDate')( - xxxx_xx_xxTommmdd_yyyy(bp_info && bp_info.election_date) + {bp_info?.election_date && makeBox('rightBoxContainer')('rightBox')('conversationElectionDate')( + xxxx_xx_xxTommmdd_yyyy(bp_info.election_date) )}
diff --git a/app/components/lib/create-participant.js b/app/components/lib/create-participant.js index 44d61a7d..1722fb2d 100644 --- a/app/components/lib/create-participant.js +++ b/app/components/lib/create-participant.js @@ -1,7 +1,7 @@ 'use strict;' -import through2 from 'through2' import { auto_quality, placeholder_image } from './cloudinary-urls' import ss from '@sap_oss/node-socketio-stream' + /** * * createParticipant uploads the video blobs that were collected, and then creates a new Iota for the participant @@ -29,119 +29,131 @@ if (typeof window !== 'undefined') { } export default function createParticipant(props, human, userId, name, progressFunc, listeningRound, listeningSeat) { - var transferred = 0 - var totalSize = 0 - var participant = { speaking: [], name: name } - var uploadQueue = [] + try { + var transferred = 0 + var totalSize = 0 + var participant = { speaking: [], name: name } + var uploadQueue = [] - let adjustedSpeakingBlobs = human.speakingBlobs.slice() // make a copy so we don't mutate the original - if (listeningSeat === 'speaking') { - if (typeof adjustedSpeakingBlobs[listeningRound] !== 'undefined') - logger.error("createParticpant didn't expect blob for listening in speakingBlobs") - adjustedSpeakingBlobs.splice(listeningRound, 1) // if listening is in the speaking seat - skip that round - } - function updateProgress(chunk) { - transferred += chunk.length - var percentComplete = Math.round((transferred / totalSize) * 100) + '%' - progressFunc && progressFunc({ progress: percentComplete, uploadComplete: false }) - } + const eventError = (message) => { + transferred = 'error' + if (window.socket.disconnected) window.socket.open() // some problems with the pipe would cause the stream to disconnect. It's fixed but lets leave this here. + logger.error("createParticipant caught error", message) // but it might not make it to the sever if the transport may be broke + uploadQueue = [] // stop other files from being uploaded + try { + progressFunc?.({ progress: message, uploadComplete: false, uploadStarted: false, uploadError: true }) + } catch (err) { } // if that doesn't work just continue + // then carry on + } - function upload(blob, seat, round) { - var file_name = userId + '-' + round + '-' + seat + new Date().toISOString().replace(/[^A-Z0-9]/gi, '') + '.mp4' // mp4 was put here to get around something with Apple - check in future + if (!window.socket.connected) return eventError('The connection to the server is down.') - // socketIo-streams does not seem to be passing call backs (undefined is received) - // so we are using a socket io event to send the response back - const responseUrl = url => { - // responses don't necessarily come in order - if (url) { - logger.trace('url', url) - url = auto_quality(url) - if (seat === 'speaking') { - // what if the come out of order -- to be determined - participant.speaking[round] = url // specify the round because the order is not assures - don't use push - } else participant.listening = url - } else { - logger.error('createParticipant.stream-upload-video failed', file_name) - } - if ( - allThere(participant.speaking, adjustedSpeakingBlobs.length) && - !!human.listeningBlob === !!participant.listening - ) { - // have all of the pieces been uploaded - logger.trace('creat participant', participant) - var pIota = { - //participant iota - parentId: props.parentId || (props._id && props._id.toString()), // a viewer with a human has no parentId, but a recorder has the viewer as it's parentId - subject: 'Participant:' + props.subject, - description: 'A participant in the following discussion:' + props.description, - component: { - component: 'MergeParticipants', - participant: participant, - }, + const ssSocket = ss(window.socket) + + let adjustedSpeakingBlobs = human.speakingBlobs.slice() // make a copy so we don't mutate the original + if (listeningSeat === 'speaking') { + if (typeof adjustedSpeakingBlobs[listeningRound] !== 'undefined') + logger.error("createParticpant didn't expect blob for listening in speakingBlobs") + adjustedSpeakingBlobs.splice(listeningRound, 1) // if listening is in the speaking seat - skip that round + } + function updateProgress(length) { + if (transferred === 'error') return // don't overwrite the error with progressFunc + transferred += length + var percentComplete = Math.round((transferred / totalSize) * 100) + '%' + progressFunc?.({ progress: percentComplete, uploadComplete: false, uploadStarted: true, uploadError: false }) + } + + function upload(blob, seat, round) { + var file_name = userId + '-' + round + '-' + seat + new Date().toISOString().replace(/[^A-Z0-9]/gi, '') + '.mp4' // mp4 was put here to get around something with Apple - check in future + // socketIo-streams does not seem to be passing call backs (undefined is received) + // so we are using a socket io event to send the response back + const responseUrl = url => { + if (url) { + logger.trace('createParticipant upload url', url) + url = auto_quality(url) + if (seat === 'speaking') { + // what if the come out of order -- to be determined + participant.speaking[round] = url // specify the round because the order is not assures - don't use push + } else participant.listening = url + } else { + logger.error('createParticipant.stream-upload-video failed', file_name) + return eventError(`There was an error uploading the video no url`) } - if (props.bp_info) { - // don't cause the property to exist in the Iota if there is none. - pIota.component.participant.bp_info = Object.assign({}, props.bp_info) // make a copy cause we are going to delete stuff - delete pIota.component.participant.bp_info.campaign_email - delete pIota.component.participant.bp_info.personal_email - if (props.bp_info.candidate_name) pIota.component.participant.name = props.bp_info.candidate_name + if ((uploadArgs = uploadQueue.shift())) { + return upload(...uploadArgs) + } else if ( + allThere(participant.speaking, adjustedSpeakingBlobs.length) && + !!human.listeningBlob === !!participant.listening + ) { + // have all of the pieces been uploaded + logger.info('createParticipant upload complete') + progressFunc?.({ progress: 'complete.', uploadComplete: true, uploadStarted: true, uploadError: false }) + logger.trace('creat participant', participant) + var pIota = { + //participant iota + parentId: props.parentId || (props._id && props._id.toString()), // a viewer with a human has no parentId, but a recorder has the viewer as it's parentId + subject: 'Participant:' + props.subject, + description: 'A participant in the following discussion:' + props.description, + component: { + component: 'MergeParticipants', + participant: participant, + }, + } + if (props.bp_info) { + // don't cause the property to exist in the Iota if there is none. + pIota.component.participant.bp_info = Object.assign({}, props.bp_info) // make a copy cause we are going to delete stuff + delete pIota.component.participant.bp_info.campaign_email + delete pIota.component.participant.bp_info.personal_email + if (props.bp_info.candidate_name) pIota.component.participant.name = props.bp_info.candidate_name + } + window.socket.emit('create-participant', pIota, result => { + logger.trace('createParticipant participant created', result) + }) } - window.socket.emit('create-participant', pIota, result => { - logger.trace('createParticipant participant created', result) - }) } - } - var stream = ss.createStream() - stream.on('error', err => { - logger.error('createParticipant.upload socket stream error:', err) - }) - - var ssSocket = ss(window.socket) - //use this for debugging - //ssSocket._oldEmit = ssSocket.emit - //ssSocket.emit = ((...args) => (console.info("emit", ...args), ssSocket._oldEmit(...args))) - ssSocket.emit('stream-upload-video', stream, { name: file_name, size: blob.size }, responseUrl) + var stream = ss.createStream() + stream.on('error', err => { + logger.error('createParticipant.upload socket stream error:', err.message || err, "connected:", window.socket.connected) + eventError('There was an error uploading the video. See if you can try again') + }) + var bstream = ss.createBlobReadStream(blob, { highWaterMark: 1024 * 200 }) // high hiwWaterMark to increase upload speed + bstream.on('error', err => { + logger.error('createParticipant.upload blob stream error:', err.message || err) + eventError(`There was an error uploading: ${err.message || err}`) + }) + bstream.on('data', chunk => { + setTimeout(() => updateProgress(chunk.length)) // just to be safe don't do much within the pipe + }) + bstream.pipe(stream) + ssSocket.emit('stream-upload-video', stream, { name: file_name, size: blob.size }, responseUrl); + } - var bstream = ss - .createBlobReadStream(blob, { highWaterMark: 1024 * 200 }) // high hiwWaterMark to increase upload speed - .pipe( - through2((chunk, enc, cb) => { - updateProgress(chunk) - cb(null, chunk) // 'this' becomes this of the react component rather than this of through2 - so pass the data back in the callback - }) - ) - .pipe(stream) + logger.info('createParticipant.onUserUpload') + //logger.trace('createParticipant.onUserUpload', props) // do not log props. If there are Blobs in there it will cause the transport websocket to disconnect - bstream.on('error', err => { - logger.error('createParticipant.upload blob stream error:', err) - }) - stream.on('end', () => { - var uploadArgs - if ((uploadArgs = uploadQueue.shift())) { - return upload(...uploadArgs) - } else { - progressFunc && progressFunc({ progress: 'complete.', uploadComplete: true }) - logger.trace('createParticipant upload after login complete') - } - }) - } + for (let round = 0; round < adjustedSpeakingBlobs.length; round++) { + totalSize += adjustedSpeakingBlobs[round].size + uploadQueue.push([adjustedSpeakingBlobs[round], 'speaking', round]) + } + if (human.listeningBlob) { + uploadQueue.push([human.listeningBlob, 'listening', 0]) + totalSize += human.listeningBlob.size + } - logger.info('createParticipant.onUserUpload') - logger.trace('createParticipant.onUserUpload', props) + progressFunc?.({ progress: `${totalSize} to upload`, uploadComplete: false, uploadStarted: true, uploadError: false }) - for (let round = 0; round < adjustedSpeakingBlobs.length; round++) { - totalSize += adjustedSpeakingBlobs[round].size - uploadQueue.push([adjustedSpeakingBlobs[round], 'speaking', round]) - } - if (human.listeningBlob) { - uploadQueue.push([human.listeningBlob, 'listening', 0]) - totalSize += human.listeningBlob.size + let uploadArgs + if ((uploadArgs = uploadQueue.shift())) { + upload(...uploadArgs) + } } - - let uploadArgs - if ((uploadArgs = uploadQueue.shift())) { - upload(...uploadArgs) + catch (error) { + if (window.socket.disconnected) window.socket.open() // some problems with the pipe would cause the stream to disconnect. It's fixed but lets leave this here. + logger.error("createParticipant caught error", message) // but it might not make it to the sever if the transport may be broke + try { + progressFunc?.({ progress: `upload failed. ${error.messaage || error}`, uploadComplete: false, uploadStarted: false, uploadError: true }) + } catch (err) { } // if that doesn't work just continue + // then carry on } - progressFunc && progressFunc({ progress: `${totalSize} to upload`, uploadComplete: false }) } diff --git a/app/socket-apis/stream-upload-video.js b/app/socket-apis/stream-upload-video.js index e995cb18..afb0b084 100644 --- a/app/socket-apis/stream-upload-video.js +++ b/app/socket-apis/stream-upload-video.js @@ -3,6 +3,7 @@ import cloudinary from 'cloudinary' export default function streamUploadVideo(stream, data, cb) { try { + logger.info("streamUploadVideo", data) let self = this // an ss(socket) const public_id = data.name.split('.')[0] var cloudStream = cloudinary.v2.uploader.upload_stream( @@ -25,6 +26,11 @@ export default function streamUploadVideo(stream, data, cb) { } } ) + logger.info("streamUploadVideo cloudStream opened") + cloudStream.on('error', err => { + logger.info('cloudStream error:', err) + cb() + }) stream.pipe(cloudStream).on('error', err => { if (err.code === 'ERR_STREAM_PREMATURE_CLOSE') { //socket-stream does not close correctly so we need to do it manually @@ -34,12 +40,9 @@ export default function streamUploadVideo(stream, data, cb) { } else logger.error('Error uploading stream:', public_id, err) cb() }) - cloudStream.on('error', err => { - console.info('cloudStream error:', err) - cb() - }) + logger.info("streamUploadVideo stream.pipe opened") } catch (err) { - logger.error('caught error in streamUploadVideo, continuing') + logger.error('caught error in streamUploadVideo, continuing', err) return } } diff --git a/app/start.js b/app/start.js index cb1f7186..4d4ba455 100644 --- a/app/start.js +++ b/app/start.js @@ -12,6 +12,9 @@ Iota.load(iotas) // set the initial data for the database async function start() { try { const server = new theCivilServer() + server.directives.mediaSrc.push('video.wixstatic.com') + server.directives.frameSrc.push('*.deliberations.us') + server.directives.frameSrc.push('deliberations.us') server.App = App // set the outer React wrapper for this site await server.earlyStart() // connect to the database, and such server.routesDirPaths.push(path.resolve(__dirname, './routes')) diff --git a/app/web-components/candidate-conversation.jsx b/app/web-components/candidate-conversation.jsx index 05f9d300..3d96fd65 100644 --- a/app/web-components/candidate-conversation.jsx +++ b/app/web-components/candidate-conversation.jsx @@ -1903,7 +1903,7 @@ class RASPUndebate extends React.Component { onUserUpload() { logger.info('CandidateConversation.onUserUpload') - logger.trace('onUserUpload', this.props) + //logger.trace('onUserUpload', this.props) // do not log props. If there are Blobs in there it will casue the transport socket to disconnect const userId = (this.props.user && this.props.user.id) || this.state.newUserId createParticipant(this.props, this.participants.human, userId, this.state.name, progressObj => this.setState(progressObj) @@ -2656,8 +2656,8 @@ class RASPUndebate extends React.Component { className={cx( classes['countdown'], humanSpeaking && - (this.rerecord || !this.participants.human.speakingObjectURLs[round]) && - classes['counting'], + (this.rerecord || !this.participants.human.speakingObjectURLs[round]) && + classes['counting'], talkative && classes['talkative'] )} > diff --git a/app/web-components/cc-wrapper/ending.jsx b/app/web-components/cc-wrapper/ending.jsx index 67eff61c..83870355 100644 --- a/app/web-components/cc-wrapper/ending.jsx +++ b/app/web-components/cc-wrapper/ending.jsx @@ -5,57 +5,16 @@ import { AuthForm } from 'civil-client' // pull components for the client out fr import createParticipant from '../../components/lib/create-participant' import SurveyForm from './survey-form' import Input from '../../components/lib/input' - -const useStyles = createUseStyles({ - outerBox: { - display: 'block', - width: '100vw', - boxSizing: 'border-box', - height: 'auto', - minHeight: '100vh', - }, - thanks: { - 'font-size': '200%', - 'font-weight': '600', - display: 'block', - paddingTop: '0.5em', - paddingBottom: '0.5em', - }, - reviewIt: { - marginBottom: '2em', - }, - beginButton: { - cursor: 'pointer', - color: 'white', - background: 'linear-gradient(to bottom, #ff8f00 0%,#ff7002 51%,#ff7002 100%)', - 'border-radius': '7px', - 'border-width': '2px', - 'border-color': 'white', - 'font-size': '1.25em', - padding: '1em', - 'margin-top': '1em', - '&:disabled': { - 'text-decoration': 'none', - background: 'lightgray', - }, - }, - name: { - fontSize: '1.25em', - width: '11em', - height: '1em', - textAlign: 'center', - }, -}) +import cx from 'classnames' export const Ending = props => { const { closing = { thanks: 'Thank You' }, participants = {}, bp_info = {}, user, dispatch, ccState } = props const classes = useStyles() const [newUser, neverSetNewUser] = useState(!user) // if there is no user at the beginning, then this is a new user - which should persist throughout the existence of this component const [newUserInfo, setNewUserInfo] = useState({ userId: undefined, firstName: undefined, lastName: undefined }) - const [progressObj, setProgressObj] = useState({ progress: '', uploadComplete: false }) - const { progress, uploadComplete } = progressObj + const [progressObj, setProgressObj] = useState({ progress: '', uploadComplete: false, uploadStarted: false, uploadError: false }) + const { progress, uploadComplete, uploadStarted, uploadError } = progressObj const [inputName, setInputName] = useState('') - const [uploadStarted, setUploadStarted] = useState(false) const thanks = () => {closing.thanks} const reviewButton = () => @@ -66,26 +25,13 @@ export const Ending = props => { className={classes.beginButton} onClick={ () => dispatch({ type: dispatch.TYPES.ReviewIt }) - /* going to need to reset the viewer/recorder somehow - this.setState( - { - intro: true, - stylesSet: true, - allPaused: false, - round: 0, - seatOffset: 0, - done: 0, - finishUp: 0, - reviewing: 1, - }, - () => this.onIntroEnd() - )*/ } > Review It ) + const onUserLogin = info => { logger.info('ending.onUserLogin') logger.trace('ending.onUserLogin', info) @@ -94,11 +40,9 @@ export const Ending = props => { } const onUserUpload = () => { - // prevent double uploads - the users might double click the upload button - if (uploadStarted) return - else setUploadStarted(true) logger.info('ending.onUserUpload') - logger.trace('ending.onUserUpload', props) + //logger.trace('ending.onUserUpload', props) // do not log props ccState has Blobs in it and trying to send them through socket.io causes the websocket to disconnect -- or uncomment this if you want to test with the error + console.info("onUserUpload props length", JSON.stringify(props).length) const userId = (user && user.id) || newUserInfo.userId createParticipant(props, ccState.participants.human, userId, inputName, setProgressObj) } @@ -126,6 +70,7 @@ export const Ending = props => { ) const nameInput = () => + !uploadComplete && !bp_info.candidate_name && (newUserInfo.userId || user) && (
@@ -154,11 +99,11 @@ export const Ending = props => { !uploadComplete && ( <>
-
- {progress &&
{'uploading: ' + progress}
} + {progress &&
{'uploading: ' + progress}
} ) @@ -195,3 +140,47 @@ export const HungUp = props => {
) } + +const useStyles = createUseStyles({ + outerBox: { + display: 'block', + width: '100vw', + boxSizing: 'border-box', + height: 'auto', + minHeight: '100vh', + }, + thanks: { + 'font-size': '200%', + 'font-weight': '600', + display: 'block', + paddingTop: '0.5em', + paddingBottom: '0.5em', + }, + reviewIt: { + marginBottom: '2em', + }, + beginButton: { + cursor: 'pointer', + color: 'white', + background: 'linear-gradient(to bottom, #ff8f00 0%,#ff7002 51%,#ff7002 100%)', + 'border-radius': '7px', + 'border-width': '2px', + 'border-color': 'white', + 'font-size': '1.25em', + padding: '1em', + 'margin-top': '1em', + '&:disabled': { + 'text-decoration': 'none', + background: 'lightgray', + }, + }, + name: { + fontSize: '1.25em', + width: '11em', + height: '1em', + textAlign: 'center', + }, + error: { + color: 'red' + } +}) \ No newline at end of file diff --git a/app/web-components/cc-wrapper/index.js b/app/web-components/cc-wrapper/index.js index f44f86a4..c97c94b8 100644 --- a/app/web-components/cc-wrapper/index.js +++ b/app/web-components/cc-wrapper/index.js @@ -1,5 +1,5 @@ 'use strict;' -import React, { useReducer } from 'react' +import React, { useReducer, useState } from 'react' import { createUseStyles } from 'react-jss' import cx from 'classnames' import WrappedCandidatePreamble from './wrapped-candidate-preamble' @@ -68,7 +68,7 @@ function reducer(state, action) { function CcWrapper(props) { const classes = useStyles() const [ccState, dispatch] = useReducer(reducer, { - pageToShow: props.participants.human ? PTS.CandidatePreamble : PTS.ViewerRecorder, + pageToShow: PTS[props.firstPage] || (props.participants.human ? PTS.CandidatePreamble : PTS.ViewerRecorder), reviewing: false, // true then ViewerRecorder is in review mode rather than record mode participants: {}, // this is written directly by ViewerRecorder to preserve stored video, and computed video url, referenced by Ending to upload the videos }) diff --git a/app/web-components/cc-wrapper/mic-camera-constraints.jsx b/app/web-components/cc-wrapper/mic-camera-constraints.jsx index b0137694..970e6ece 100644 --- a/app/web-components/cc-wrapper/mic-camera-constraints.jsx +++ b/app/web-components/cc-wrapper/mic-camera-constraints.jsx @@ -29,14 +29,14 @@ function calculateConstraints(state) { return { ...state, constraints } } const throwIfUndefined = { - get: function(obj, prop) { + get: function (obj, prop) { if (prop in obj) return obj[prop] throw Error('undefined action TYPE: ' + prop) }, } const TYPES = new Proxy({}, throwIfUndefined) -;['NextMic', 'NextCamera', 'UpdateDevices'].forEach(k => (TYPES[k] = k)) + ;['NextMic', 'NextCamera', 'UpdateDevices'].forEach(k => (TYPES[k] = k)) function reducer(state, action) { switch (action.type) { @@ -71,7 +71,7 @@ export default function useMicCameraConstraints( const [state, dispatch] = useReducer(reducer, { audioinputs: [], videoinputs: [], - mixIndex: undefined, + micIndex: undefined, cameraIndex: undefined, constraints, }) @@ -95,17 +95,18 @@ export default function useMicCameraConstraints( return typeof navigator !== 'undefined' && navigator.mediaDevices && navigator.mediaDevices.enumerateDevices } - const onDeviceChange = () => + const onDeviceChange = () => { supportsEnumerateDevices() && navigator.mediaDevices.enumerateDevices().then(updateDevices) // some browsers don't support navigator.mediaDevices.enumerateDevices + } useEffect(() => { + navigator?.mediaDevices?.addEventListener('devicechange', onDeviceChange) if (supportsEnumerateDevices()) { // the values for inputDevices need to be set asynchronously because mediaDevices.enumerateDevices returns a promise - navigator.mediaDevices.enumerateDevices().then(updateDevices) //register an event listener such that update devices is called when a new device is plugged in or another decice gets removed. - navigator.mediaDevices.addEventListener('devicechange', onDeviceChange) - return () => navigator.mediaDevices.removeEventListener('devicechange', onDeviceChange) + navigator.mediaDevices.enumerateDevices().then(updateDevices) } + return () => navigator?.mediaDevices?.removeEventListener('devicechange', onDeviceChange) }, []) return [state, dispatch] } diff --git a/app/web-components/cc-wrapper/precheck.jsx b/app/web-components/cc-wrapper/precheck.jsx index e3ec9b10..52c168da 100644 --- a/app/web-components/cc-wrapper/precheck.jsx +++ b/app/web-components/cc-wrapper/precheck.jsx @@ -10,7 +10,7 @@ const agenda = [ '2. Check Lighting: Is your face well light from light in front of you', '3. Is your camera at eye level', '4. Are your head and shoulders in view', - '5. Click Finished Speaking when ready', + '5. Click Next Section when ready', ], ] const timeLimits = [0] diff --git a/app/web-components/cc-wrapper/react-camera-recorder.jsx b/app/web-components/cc-wrapper/react-camera-recorder.jsx index d4780131..f636ee23 100644 --- a/app/web-components/cc-wrapper/react-camera-recorder.jsx +++ b/app/web-components/cc-wrapper/react-camera-recorder.jsx @@ -54,8 +54,6 @@ const ReactCameraRecorder = React.forwardRef((props, ref) => { } }) - const [cameraIsStreaming, setCameraIsStreaming] = useState(false) - // the properties in reactThis are changed in realtime by events that are associated to an instance of this // the useState setter is not used here because that doesn't change the value of the state until sometime after the event has been processed, just before render // and changes to these properties are not intended to cause a rerender @@ -77,16 +75,25 @@ const ReactCameraRecorder = React.forwardRef((props, ref) => { // called by parent to turn on the camera and get the video in a stream - but doesn't start recording yet // it's up to the parent to render the video from the stream if and wherever it wants + const getCameraStream = () => { if (canNotRecordHere) return Promise.reject(new Error('can not record here')) else { - setCameraIsStreaming(true) - return new Promise((ok, ko) => { - getCameraStreamFromCalculatedConstraints(ok, ko) - }) + if (reactThis.cameraStream) { + logger.error('ReactCameraRecorder camera is already streaming') + Promise.resolve(reactThis.cameraStream) + } else { + return new Promise((ok, ko) => { + getCameraStreamFromCalculatedConstraints(ok, ko) + }) + } } } + // warning if navigator.mediaDevices.getUserMedia is called a second time - without releaseing the stream + // there will be an "notreadableerror could not start video source" - after a delay + // also consider that if the user changed the camera selection, that will cause getCameraStreamFromCalulatedConstraints to be called again + const getCameraStreamFromCalculatedConstraints = async (ok, ko) => { try { const stream = await navigator.mediaDevices.getUserMedia(props.constraints) @@ -226,9 +233,12 @@ const ReactCameraRecorder = React.forwardRef((props, ref) => { // if a camera or mic index changes, get the new stream - but don't do it initially only do this on index changes after the camera is streaming useEffect(() => { - if (cameraIsStreaming) getCameraStreamFromCalculatedConstraints(props.onCameraChange) + if (reactThis.cameraStream) { + releaseCamera() + getCameraStreamFromCalculatedConstraints(props.onCameraChange) + } }, [props.constraints]) - return false + return () => { releaseCamera() } // if unmounting release the camera }) export default ReactCameraRecorder diff --git a/app/web-components/cc-wrapper/viewer-camera-logic.jsx b/app/web-components/cc-wrapper/viewer-camera-logic.jsx index 8409711c..f57bd33f 100644 --- a/app/web-components/cc-wrapper/viewer-camera-logic.jsx +++ b/app/web-components/cc-wrapper/viewer-camera-logic.jsx @@ -149,8 +149,8 @@ export default class ViewerRecorderLogic extends React.Component { placeholder_image( (this.props.participants[participant].listeningURLs && this.props.participants[participant].listeningURLs[0]) || - this.props.participants[participant].listening || - this.props.participants[participant].speaking[0] + this.props.participants[participant].listening || + this.props.participants[participant].speaking[0] )) || '', youtube, @@ -189,12 +189,13 @@ export default class ViewerRecorderLogic extends React.Component { state = { seatOffset: 0, round: 0, - moderatorReadyToStart: !this.props.participants.moderator, // if no moderators, no need to wait + moderatorReadyToStart: !this.props?.participants?.moderator, // if no moderators, no need to wait begin: false, allPaused: true, // so the play button shows isRecording: false, countDown: 0, warmup: false, + moderatorSpeakingIndex: Array.isArray(this.props?.participants?.moderator?.speaking?.[0]) ? 0 : undefined, } /*** @@ -239,11 +240,11 @@ export default class ViewerRecorderLogic extends React.Component { listening() { const listeningRound = - this.props.participants.human.listening && typeof this.props.participants.human.listening.round !== 'undefined' + this.props.participants?.human?.listening && typeof this.props.participants.human.listening.round !== 'undefined' ? this.props.participants.human.listening.round : Infinity // 0 is a valid round const listeningSeat = - (this.props.participants.human.listening && this.props.participants.human.listening.seat) || 'seat2' + (this.props.participants?.human?.listening && this.props.participants.human.listening.seat) || 'seat2' return { listeningRound, listeningSeat } } @@ -274,7 +275,8 @@ export default class ViewerRecorderLogic extends React.Component { if (this.props.participants[participant].speaking[i]) this.preFetchObjectURL(participant, true, i) }) } - if (this.props.participants.human) this.beginButton() // there use to be a button click that started this -but now we just go after being mounted + if (this.props.autoBegin) this.beginButton() // there use to be a button click that started this -but now we just go after being mounted + if (this.props.autoCameraStart) this.getCameraMedia() // start the camera } /**************************************************************************************************************** @@ -351,7 +353,7 @@ export default class ViewerRecorderLogic extends React.Component { logger.trace(`nextMediaState part:${part}`) //if (part === 'human') return; // humans won't get here - const { round } = this.state + const { round, moderatorSpeakingIndex } = this.state const { reviewing } = this.props.ccState const { listeningRound, listeningSeat } = this.listening() @@ -377,6 +379,13 @@ export default class ViewerRecorderLogic extends React.Component { } else { objectURL = 'cameraStream' // set it to something - but this.cameraStream should really be used } + } else if (part === 'moderator' && typeof moderatorSpeakingIndex === 'number') { + objectURL = this.props.ccState.participants[part].speakingObjectURLs[round][moderatorSpeakingIndex] + if (!objectURL) { + this.props.ccState.participants[part].speakingImmediate[round] = true + this.stallWatch(part) + logger.error('Undebate.nextMediaState need to do something about stallWatch with preFetch') + } } else if (!(objectURL = this.props.ccState.participants[part].speakingObjectURLs[round])) { this.props.ccState.participants[part].speakingImmediate[round] = true this.stallWatch(part) @@ -775,8 +784,17 @@ export default class ViewerRecorderLogic extends React.Component { } nextSpeaker() { - var { seatOffset, round } = this.state + var { seatOffset, round, moderatorSpeakingIndex } = this.state logger.info('Undebate.nextSpeaker', seatOffset, round) + const moderatorSpeaking = this.seatOfParticipant('moderator') === 'speaking' + if (moderatorSpeaking && Array.isArray(this.props.ccState.participants.moderator.speakingObjectURLs[round])) { + if (++moderatorSpeakingIndex < this.props.ccState.participants.moderator.speakingObjectURLs[round].length) { + // do not go through newOrder that will cause a blink + this.playObjectURL('moderator', this.props.ccState.participants.moderator.speakingObjectURLs[round][moderatorSpeakingIndex], true, this.props.ccState.reviewing) + this.setState({ moderatorSpeakingIndex }) + return + } + } if (this.numParticipants === 1) { round += 1 if (round >= this.numRounds) return this.finished() @@ -794,8 +812,17 @@ export default class ViewerRecorderLogic extends React.Component { } autoNextSpeaker() { - var { seatOffset, round } = this.state + let { seatOffset, round, moderatorSpeakingIndex } = this.state logger.trace('Undebate.autoNextSpeaker', seatOffset, round) + const moderatorSpeaking = this.seatOfParticipant('moderator') === 'speaking' + if (moderatorSpeaking && Array.isArray(this.props.ccState.participants.moderator.speakingObjectURLs[round])) { + if (++moderatorSpeakingIndex < this.props.ccState.participants.moderator.speakingObjectURLs[round].length) { + // do not go through newOrder that will cause a blink + this.playObjectURL('moderator', this.props.ccState.participants.moderator.speakingObjectURLs[round][moderatorSpeakingIndex], true, this.props.ccState.reviewing) + this.setState({ moderatorSpeakingIndex }) + return + } + } if (this.numParticipants === 1) { round += 1 if (round >= this.numRounds) return this.finished() @@ -901,46 +928,52 @@ export default class ViewerRecorderLogic extends React.Component { }, } - newOrder(seatOffset, round) { + newOrder(seatOffset, round, moderatorSpeakingIndex) { const { participants } = this.props this.clearStallWatch() var followup = [] - Object.keys(participants).forEach((participant, i) => { - let oldChair = this.seat(i) - let newChair = this.seat(i, seatOffset) - logger.trace('rotateOrder', round, seatOffset, participant, oldChair, newChair) - - if (participant === 'human') { - const timeLimit = this.getTimeLimit() - const { listeningRound, listeningSeat } = this.listening() - if (oldChair === 'speaking' && newChair === 'speaking' && this.rerecord) { - // the user is initiating a rerecord - } else if ( - oldChair === 'speaking' && - (!this.props.ccState.participants.human.speakingObjectURLs[this.state.round] || this.rerecord) - ) { - // the oldChair and the old round - //this.rerecord = false - //this.stopRecording() - } else if (oldChair === listeningSeat && this.state.round === listeningRound && !this.props.ccState.reviewing) { - // the oldChair and the old round - //this.rerecord = false - //this.stopRecording() + if (moderatorSpeakingIndex) { + followup.push(() => this.nextMediaState('moderator')) + } else { + Object.keys(participants).forEach((participant, i) => { + let oldChair = this.seat(i) + let newChair = this.seat(i, seatOffset) + logger.trace('rotateOrder', round, seatOffset, participant, oldChair, newChair) + + if (participant === 'human') { + const timeLimit = this.getTimeLimit() + const { listeningRound, listeningSeat } = this.listening() + if (oldChair === 'speaking' && newChair === 'speaking' && this.rerecord) { + // the user is initiating a rerecord + } else if ( + oldChair === 'speaking' && + (!this.props.ccState.participants.human.speakingObjectURLs[this.state.round] || this.rerecord) + ) { + // the oldChair and the old round + //this.rerecord = false + //this.stopRecording() + } else if (oldChair === listeningSeat && this.state.round === listeningRound && !this.props.ccState.reviewing) { + // the oldChair and the old round + //this.rerecord = false + //this.stopRecording() + } + // then see if it needs to be turned on - both might happen at the same transition + followup.push(() => this.nextMediaState(participant)) + followup.push(() => this.maybeEnableRecording(newChair, listeningSeat, round, listeningRound, timeLimit)) + } else if (oldChair === 'speaking' || newChair === 'speaking' || this.state.allPaused) { + // will be speaking or need to start media again + followup.push(() => this.nextMediaState(participant)) + if (participant === 'moderator' && newChair === 'speaking' && Array.isArray(this.props.ccState.participants.moderator.speakingObjectURLs[round])) + moderatorSpeakingIndex = 0 + } else { + logger.trace('participant continue looping', participant) } - // then see if it needs to be turned on - both might happen at the same transition - followup.push(() => this.nextMediaState(participant)) - followup.push(() => this.maybeEnableRecording(newChair, listeningSeat, round, listeningRound, timeLimit)) - } else if (oldChair === 'speaking' || newChair === 'speaking' || this.state.allPaused) { - // will be speaking or need to start media again - followup.push(() => this.nextMediaState(participant)) - } else { - logger.trace('participant continue looping', participant) - } - }) - logger.trace('rotateOrder: ', seatOffset) + }) + logger.trace('rotateOrder: ', seatOffset) + } - this.setState({ seatOffset, round, talkative: false, allPaused: false }, () => { + this.setState({ seatOffset, round, moderatorSpeakingIndex, talkative: false, allPaused: false }, () => { while (followup.length) followup.shift()() }) } @@ -1023,7 +1056,7 @@ export default class ViewerRecorderLogic extends React.Component { const { listeningRound, listeningSeat } = this.listening() logger.trace('getUserMedia() got stream:', this.cameraStream) //it will be set by nextMediaState this.human.current.src = stream; - Object.keys(this.props.participants).forEach(part => this.nextMediaState(part)) + this.nextMediaState('human') // special case where human is in seat2 initially - because seat2 is where we record their silence if (listeningRound === 0 && this.seatOfParticipant('human') === listeningSeat) this.startRecording(blobs => this.saveRecordingToParticipants(false, 0, blobs)) // listening is not speaking @@ -1102,6 +1135,7 @@ export default class ViewerRecorderLogic extends React.Component { onIntroEnd() { this.setState({ begin: true }, () => { this.getCameraMedia() + Object.keys(this.props.participants).forEach(part => part !== 'human' && this.nextMediaState(part)) }) } @@ -1253,4 +1287,8 @@ export default class ViewerRecorderLogic extends React.Component { this.stallWatchTimeout = false if (this.state.stalled) this.setState({ stalled: false, waitingPercent: 0 }) } + componentWillUnmount() { + if (this.stallWatchTimeout) clearTimeout(this.stallWatchTimeout) + this.stallWatchTimeout = false + } } diff --git a/app/web-components/cc-wrapper/viewer-recorder.jsx b/app/web-components/cc-wrapper/viewer-recorder.jsx index 46cd807b..f3187fe0 100644 --- a/app/web-components/cc-wrapper/viewer-recorder.jsx +++ b/app/web-components/cc-wrapper/viewer-recorder.jsx @@ -55,7 +55,7 @@ class ViewerRecorder extends ViewerRecorderLogic { else { // we need to calculate the position of everything if rendered on the server, based on some size. let width, height - if (this.props.browserConfig.type === 'bot') { + if (this.props.browserConfig?.type === 'bot') { // running on a bot width = 1200 //Facebook image post size: 1200 x 900 height = 900 @@ -140,7 +140,7 @@ class ViewerRecorder extends ViewerRecorderLogic { }, buttonBarStyle: { - bottom: '5vh', + bottom: '1rem', height: '14vh', position: 'absolute', }, @@ -164,7 +164,7 @@ class ViewerRecorder extends ViewerRecorderLogic { if (typeof ResolutionToFontSizeTable[key] === 'number') { newFontSize = ResolutionToFontSizeTable[key] } else { - logger.trace('Undebate.calcFontSize not found', this.props.browserConfig.type, key, navigator.userAgent) + logger.trace('Undebate.calcFontSize not found', this.props.browserConfig?.type, key, navigator.userAgent) } if (!newFontSize) { @@ -244,8 +244,12 @@ class ViewerRecorder extends ViewerRecorderLogic { let seatVerticalPitch = seatWidthRatio * HDRatio * width + verticalSeatSpace let seatLeft = seatStyle.nextUp.left //Math.min(seatStyle.nextUp.left, horizontalSeatSpace) + buttonBarStyle.width = (parseInt(seatStyle.speaking.width) * 7) / 6 + 'vw' // three are 7 buttons, 5 should be under the speaking window + buttonBarStyle.left = (100 - parseInt(buttonBarStyle.width)) / 2 + 'vw' + buttonBarStyle.height = ((((parseInt(buttonBarStyle.width) / 7) * 140) / 100) * width * 0.8) / 100 + 'px' // there are 7 buttons. the aspect ratio of the button is 100 wide by 140 tall and we are scaling the icon to 80% of it's div + // down the left side - while (seatTop + seatVerticalPitch < height && seat <= 7) { + while (seatTop + seatVerticalPitch < (height - parseInt(buttonBarStyle.height)) && seat <= 7) { seatStyle['seat' + seat].top = seatTop seatStyle['seat' + seat].left = seatLeft seatStyle['seat' + seat].width = seatWidthRatio * 100 + 'vw' @@ -253,7 +257,8 @@ class ViewerRecorder extends ViewerRecorderLogic { seat++ } - seatTop = height - seatWidthRatio * HDRatio * width - verticalSeatSpace + //seatTop = height - seatWidthRatio * HDRatio * width - verticalSeatSpace + seatTop = parseInt(seatStyle.speaking.top) + ((parseInt(seatStyle.speaking.width) * width) / 100) * HDRatio + 2 * verticalSeatSpace seatLeft += seatWidthRatio * width + horizontalSeatSpace let seatHorizontalPitch = seatWidthRatio * width + horizontalSeatSpace // across the bottom @@ -288,16 +293,13 @@ class ViewerRecorder extends ViewerRecorderLogic { agendaStyle.width = width - agendaStyle.left - 2 * horizontalSeatSpace agendaStyle.height = agendaStyle.width - buttonBarStyle.width = (parseInt(seatStyle.speaking.width) * 7) / 5 + 'vw' // three are 7 buttons, 5 should be under the speaking window - buttonBarStyle.left = (100 - parseInt(buttonBarStyle.width)) / 2 + 'vw' - buttonBarStyle.height = ((((parseInt(buttonBarStyle.width) / 7) * 140) / 100) * width * 0.8) / 100 + 'px' // there are 7 buttons. the aspect ratio of the button is 100 wide by 140 tall and we are scaling the icon to 80% of it's div } else { const speakingWidthRatio = 0.5 const nextUpWidthRatio = 0.2 const seatWidthRatio = 0.2 const verticalSeatSpaceRatio = 0.05 const horizontalSeatSpaceRatio = 0.0125 - const navBarHeightRatio = 0.08 + const navBarHeightRatio = 0.06 const verticalSeatSpace = Math.max(verticalSeatSpaceRatio * height, titleHeight) const horizontalSeatSpace = Math.max(horizontalSeatSpaceRatio * width, fontSize) @@ -311,13 +313,18 @@ class ViewerRecorder extends ViewerRecorderLogic { seatStyle.nextUp.width = nextUpWidthRatio * 100 + 'vw' let seat = 2 + let seatTop = seatStyle.nextUp.top + nextUpWidthRatio * HDRatio * width + verticalSeatSpace let seatLeft = horizontalSeatSpace let seatHorizontalPitch = seatWidthRatio * width + horizontalSeatSpace let seatVerticalPitch = seatWidthRatio * HDRatio * width + verticalSeatSpace + buttonBarStyle.width = (parseInt(seatStyle.speaking.width) * 7) / 7 + 'vw' // three are 7 buttons, 6 should be under the speaking window + buttonBarStyle.left = (100 - parseInt(buttonBarStyle.width)) / 2 + 'vw' + buttonBarStyle.height = ((((parseInt(buttonBarStyle.width) / 7) * 140) / 100) * width * 0.8) / 100 + 'px' // there are 7 buttons. the aspect ratio of the button is 100 wide by 140 tall and we are scaling the icon to 80% of it's div + // down the left side - while (seatTop + seatVerticalPitch < height && seat <= 7) { + while (seatTop + seatVerticalPitch < (height - parseInt(buttonBarStyle.height)) && seat <= 7) { seatStyle['seat' + seat].top = seatTop seatStyle['seat' + seat].left = seatLeft seatStyle['seat' + seat].width = seatWidthRatio * 100 + 'vw' @@ -325,7 +332,8 @@ class ViewerRecorder extends ViewerRecorderLogic { seat++ } - seatTop = height - seatWidthRatio * HDRatio * width - verticalSeatSpace + //seatTop = height - seatWidthRatio * HDRatio * width - verticalSeatSpace + seatTop = parseInt(seatStyle.speaking.top) + ((parseInt(seatStyle.speaking.width) * width) / 100) * HDRatio + 1 * verticalSeatSpace seatLeft += seatHorizontalPitch // across the bottom @@ -357,10 +365,6 @@ class ViewerRecorder extends ViewerRecorderLogic { if (agendaStyle.left + agendaStyle.width > width) agendaStyle.width = width - agendaStyle.left - 2 * horizontalSeatSpace agendaStyle.height = Math.max(0.175 * width, 20 * fontSize) - - buttonBarStyle.width = (parseInt(seatStyle.speaking.width) * 7) / 5 + 'vw' // three are 7 buttons, 5 should be under the speaking window - buttonBarStyle.left = (100 - parseInt(buttonBarStyle.width)) / 2 + 'vw' - buttonBarStyle.height = ((((parseInt(buttonBarStyle.width) / 7) * 140) / 100) * width * 0.8) / 100 + 'px' // there are 7 buttons. the aspect ratio of the button is 100 wide by 140 tall and we are scaling the icon to 80% of it's div } } else { // portrait mode @@ -525,7 +529,7 @@ class ViewerRecorder extends ViewerRecorderLogic { } preventPortraitRecording = () => { - if (this.props.browserConfig.type !== 'phone') return // nothing to do here if not a phone + if (this.props.browserConfig?.type !== 'phone') return // nothing to do here if not a phone const { isPortraitPhoneRecording } = this.state const portraitMode = typeof window !== 'undefined' && window.innerWidth < window.innerHeight if (isPortraitPhoneRecording && !portraitMode) { @@ -609,7 +613,7 @@ class ViewerRecorder extends ViewerRecorderLogic { const innerWidth = typeof window !== 'undefined' ? window.innerWidth : 1920 const humanSpeaking = this.speakingNow() === 'human' const ifShowPreamble = this.props.ccState.participants.human && !opening.noPreamble && !intro && !begin && !done - const bot = browserConfig.type === 'bot' + const bot = browserConfig?.type === 'bot' const recordingPlaceholderBar = () => { if (participants.human) { @@ -654,7 +658,7 @@ class ViewerRecorder extends ViewerRecorderLogic {
- +
@@ -803,6 +807,7 @@ class ViewerRecorder extends ViewerRecorderLogic { } const renderHangupButton = () => + !hangupButton.disabled && !ifShowPreamble && !hungUp && this.props.ccState.participants.human && ( @@ -902,7 +907,8 @@ class ViewerRecorder extends ViewerRecorderLogic { )} style={agendaStyle} agendaItem={ - (participants.moderator && participants.moderator.agenda && participants.moderator.agenda[round]) || + (Array.isArray(participants?.moderator?.speaking?.[round]) && participants?.moderator?.agenda?.[round][this.state.moderatorSpeakingIndex || (this.state.seatOffset !== 0 ? (participants?.moderator?.agenda?.[round]?.length - 1) : 0)]) || + (participants?.moderator?.agenda?.[round]) || (this.props.agenda && this.props.agenda[round]) } prevSection={this.prevSection.bind(this)} @@ -913,9 +919,9 @@ class ViewerRecorder extends ViewerRecorderLogic { className={cx( classes['countdown'], humanSpeaking && - this.getTimeLimit() && - (this.rerecord || !this.props.ccState.participants.human.speakingObjectURLs[round]) && - classes['counting'], + this.getTimeLimit() && + (this.rerecord || !this.props.ccState.participants.human.speakingObjectURLs[round]) && + classes['counting'], talkative && classes['talkative'], warmup && classes['warmup'] )} @@ -946,7 +952,7 @@ class ViewerRecorder extends ViewerRecorderLogic { onCanNotRecordHere={status => ( dispatch({ type: dispatch.TYPES.CanNotRecordHere }), (this.canNotRecordHere = status) )} - onCameraStream={stream => (this.cameraStream = stream)} + onCameraStream={stream => (this.cameraStream = stream, typeof micCameraConstraintsState.micIndex === 'undefined' && navigator.mediaDevices.dispatchEvent(new Event('devicechange')) /* if the user has just allowed the audio/video we need to update the micCameraConstraintsState */)} onCameraChange={() => this.nextMediaState('human')} constraints={micCameraConstraintsState.constraints} /> @@ -968,7 +974,7 @@ class ViewerRecorder extends ViewerRecorderLogic { isRecording={isRecording} buttonLogic={this.buttonList} /> - {!participants.human && !bot && beginOverlay()} + {!bot && beginOverlay()} {permissionOverlay()} {waitingOnModeratorOverlay()} {renderHangupButton()} @@ -1052,7 +1058,7 @@ const styles = { }, }, hangUpButton: { - right: 0, + right: '5vw', position: 'absolute', }, finishButton: { diff --git a/app/web-components/cc-wrapper/wrapped-candidate-preamble.jsx b/app/web-components/cc-wrapper/wrapped-candidate-preamble.jsx index 50938aed..bc7536c8 100644 --- a/app/web-components/cc-wrapper/wrapped-candidate-preamble.jsx +++ b/app/web-components/cc-wrapper/wrapped-candidate-preamble.jsx @@ -4,7 +4,7 @@ import CandidatePreamble from '../../components/candidate-preamble' // don't want to rewire Candidate Preamble yet so here's a wrapper for now const WrappedCandidatePreamble = props => { - const { subject, bp_info = {}, participants, instructionLink, dispatch } = props + const { subject, bp_info = {}, participants, instructionLink, logo, dispatch } = props return ( { candidate_questions={participants.moderator.agenda} instructionLink={instructionLink} timeLimits={participants.moderator.timeLimits} + logo={logo} /> ) } diff --git a/app/web-components/undebate.jsx b/app/web-components/undebate.jsx index 6c9d6dd3..ce4ee8e7 100644 --- a/app/web-components/undebate.jsx +++ b/app/web-components/undebate.jsx @@ -698,8 +698,8 @@ class Undebate extends React.Component { placeholder_image( (this.props.participants[participant].listeningURLs && this.props.participants[participant].listeningURLs[0]) || - this.props.participants[participant].listening || - this.props.participants[participant].speaking[0] + this.props.participants[participant].listening || + this.props.participants[participant].speaking[0] )) || '', youtube, @@ -2198,7 +2198,7 @@ class Undebate extends React.Component { // prevent double uploads - the users might double click the upload button else this.uploadStarted = true logger.info('Undebate.onUserUpload') - logger.trace('onUserUpload', this.props) + // logger.trace('onUserUpload', this.props) // do not log props - if there are Blobs it may crash the transport socket to the server const userId = (this.props.user && this.props.user.id) || this.state.newUserId const { listeningRound, listeningSeat } = this.listening() createParticipant( @@ -3008,8 +3008,8 @@ class Undebate extends React.Component { const style = finishUp ? {} : noOverlay || bot || intro - ? agendaStyle - : Object.assign({}, agendaStyle, introSeatStyle['agenda']) + ? agendaStyle + : Object.assign({}, agendaStyle, introSeatStyle['agenda']) return (
=0.10.0" } }, - "node_modules/base64-arraybuffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.1.tgz", - "integrity": "sha512-vFIUq7FdLtjZMhATwDul5RZWv2jpXQ09Pd6jcVEOvIsqCWTRFD/ONHNfyOS8dA/Ippi5dsIgpyKWKZaAKZltbA==", - "peer": true, - "engines": { - "node": ">= 0.6.0" - } - }, "node_modules/base64-js": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", @@ -19080,8 +19061,8 @@ } }, "node_modules/civil-server": { - "version": "0.0.18", - "resolved": "git+ssh://git@github.com/EnCiv/civil-server.git#36ac227f8161fb051a7ef4630b653a62a2c7d98e", + "version": "0.0.26", + "resolved": "git+ssh://git@github.com/EnCiv/civil-server.git#8254efdda3abd363c0a31e3e1800290acd84acd0", "license": "SEE LICENSE IN LICENSE.txt", "peer": true, "dependencies": { @@ -19118,20 +19099,21 @@ "react-helmet": "^6.1.0", "react-hot-loader": "^4.13.0", "react-jss": "^10.9.0", + "sib-api-v3-sdk": "^8.4.2", "sniffr": "^1.2.0", - "socket.io": "^4.4.0", - "socket.io-client": "^4.4.0", + "socket.io": "^4.6.1", + "socket.io-client": "^4.6.1", "stream-browserify": "^3.0.0", "superagent": "^5.3.1" }, "bin": { - "logwatch": "dist/tools/logwatch.js", + "do-civil": "app/tools/do-civil.sh", + "logwatch": "app/tools/logwatch.js", "mongo-id": "dist/tools/mongo-id.js", "react-directory-indexer": "dist/tools/react-directory-indexer.js" }, "engines": { - "node": "16.16.0", - "npm": "8.13.2" + "node": "16.16.0" }, "optionalDependencies": { "@shelf/jest-mongodb": "^2.2.0", @@ -19188,6 +19170,47 @@ "@hapi/hoek": "^8.3.0" } }, + "node_modules/civil-server/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "peer": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/civil-server/node_modules/sib-api-v3-sdk": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/sib-api-v3-sdk/-/sib-api-v3-sdk-8.5.0.tgz", + "integrity": "sha512-6Ratp5kLN/rEEvk4XVIQ4L8IrCIrcfE9m1HjvHz/WepC+CVXPsjOlgRcK/jQjpN5kC+dmhDAqrTo1OtnF6i1wA==", + "peer": true, + "dependencies": { + "querystring": "0.2.0", + "superagent": "3.7.0" + } + }, + "node_modules/civil-server/node_modules/sib-api-v3-sdk/node_modules/superagent": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.7.0.tgz", + "integrity": "sha512-/8trxO6NbLx4YXb7IeeFTSmsQ35pQBiTBsLNvobZx7qBzBeHYvKCyIIhW2gNcWbLzYxPAjdgFbiepd8ypwC0Gw==", + "deprecated": "Please upgrade to v7.0.2+ of superagent. We have fixed numerous issues with streams, form-data, attach(), filesystem errors not bubbling up (ENOENT on attach()), and all tests are now passing. See the releases tab for more information at .", + "peer": true, + "dependencies": { + "component-emitter": "^1.2.0", + "cookiejar": "^2.1.0", + "debug": "^3.1.0", + "extend": "^3.0.0", + "form-data": "^2.3.1", + "formidable": "^1.1.1", + "methods": "^1.1.1", + "mime": "^1.4.1", + "qs": "^6.5.1", + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 4.0" + } + }, "node_modules/cjs-module-lexer": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", @@ -22072,9 +22095,9 @@ } }, "node_modules/engine.io": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.1.0.tgz", - "integrity": "sha512-ErhZOVu2xweCjEfYcTdkCnEYUiZgkAcBBAhW4jbIvNG8SLU3orAqoJCiytZjYF7eTpVmmCrLDjLIEaPlUAs1uw==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.1.tgz", + "integrity": "sha512-JFYQurD/nbsA5BSPmbaOSLa3tSVj8L6o4srSwXXY3NqE+gGUNmmPTbhn8tjzcCtSqhFgIeqef81ngny8JM25hw==", "peer": true, "dependencies": { "@types/cookie": "^0.4.1", @@ -22085,34 +22108,30 @@ "cookie": "~0.4.1", "cors": "~2.8.5", "debug": "~4.3.1", - "engine.io-parser": "~5.0.0", - "ws": "~8.2.3" + "engine.io-parser": "~5.0.3", + "ws": "~8.11.0" }, "engines": { "node": ">=10.0.0" } }, "node_modules/engine.io-client": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.1.1.tgz", - "integrity": "sha512-V05mmDo4gjimYW+FGujoGmmmxRaDsrVr7AXA3ZIfa04MWM1jOfZfUwou0oNqhNwy/votUDvGDt4JA4QF4e0b4g==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.4.0.tgz", + "integrity": "sha512-GyKPDyoEha+XZ7iEqam49vz6auPnNJ9ZBfy89f+rMMas8AuiMWOZ9PVzu8xb9ZC6rafUqiGHSCfu22ih66E+1g==", "peer": true, "dependencies": { - "@socket.io/component-emitter": "~3.0.0", + "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1", - "engine.io-parser": "~5.0.0", - "has-cors": "1.1.0", - "parseqs": "0.0.6", - "parseuri": "0.0.6", - "ws": "~8.2.3", - "xmlhttprequest-ssl": "~2.0.0", - "yeast": "0.1.2" + "engine.io-parser": "~5.0.3", + "ws": "~8.11.0", + "xmlhttprequest-ssl": "~2.0.0" } }, "node_modules/engine.io-client/node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "peer": true, "dependencies": { "ms": "2.1.2" @@ -22127,9 +22146,9 @@ } }, "node_modules/engine.io-client/node_modules/ws": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", "peer": true, "engines": { "node": ">=10.0.0" @@ -22148,30 +22167,27 @@ } }, "node_modules/engine.io-parser": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.2.tgz", - "integrity": "sha512-wuiO7qO/OEkPJSFueuATIXtrxF7/6GTbAO9QLv7nnbjwZ5tYhLm9zxvLwxstRs0dcT0KUlWTjtIOs1T86jt12g==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.6.tgz", + "integrity": "sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==", "peer": true, - "dependencies": { - "base64-arraybuffer": "~1.0.1" - }, "engines": { "node": ">=10.0.0" } }, "node_modules/engine.io/node_modules/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", "peer": true, "engines": { "node": ">= 0.6" } }, "node_modules/engine.io/node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "peer": true, "dependencies": { "ms": "2.1.2" @@ -22186,9 +22202,9 @@ } }, "node_modules/engine.io/node_modules/ws": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", "peer": true, "engines": { "node": ">=10.0.0" @@ -24535,12 +24551,6 @@ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==" }, - "node_modules/has-cors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", - "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=", - "peer": true - }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -32431,18 +32441,6 @@ "parse5": "^6.0.1" } }, - "node_modules/parseqs": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz", - "integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==", - "peer": true - }, - "node_modules/parseuri": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz", - "integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==", - "peer": true - }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -33506,7 +33504,6 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "optional": true, "engines": { "node": ">=0.4.x" } @@ -35610,49 +35607,71 @@ "integrity": "sha512-k7C0ZcHBU330LcSkKyc2cOOB0uHosME8b2t9qFJqdqB1cKwGmZWd7BVwBz5mWOMJ5dggK1dy2qv+DSwteKLBzQ==" }, "node_modules/socket.io": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.4.0.tgz", - "integrity": "sha512-bnpJxswR9ov0Bw6ilhCvO38/1WPtE3eA2dtxi2Iq4/sFebiDJQzgKNYA7AuVVdGW09nrESXd90NbZqtDd9dzRQ==", + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.6.1.tgz", + "integrity": "sha512-KMcaAi4l/8+xEjkRICl6ak8ySoxsYG+gG6/XfRCPJPQ/haCRIJBTL4wIl8YCsmtaBovcAXGLOShyVWQ/FG8GZA==", "peer": true, "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "debug": "~4.3.2", - "engine.io": "~6.1.0", - "socket.io-adapter": "~2.3.3", - "socket.io-parser": "~4.0.4" + "engine.io": "~6.4.1", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.1" }, "engines": { "node": ">=10.0.0" } }, "node_modules/socket.io-adapter": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.3.3.tgz", - "integrity": "sha512-Qd/iwn3VskrpNO60BeRyCyr8ZWw9CPZyitW4AQwmRZ8zCiyDiL+znRnWX6tDHXnWn1sJrM1+b6Mn6wEDJJ4aYQ==", - "peer": true + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", + "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "peer": true, + "dependencies": { + "ws": "~8.11.0" + } + }, + "node_modules/socket.io-adapter/node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "peer": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } }, "node_modules/socket.io-client": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.4.0.tgz", - "integrity": "sha512-g7riSEJXi7qCFImPow98oT8X++MSsHz6MMFRXkWNJ6uEROSHOa3kxdrsYWMq85dO+09CFMkcqlpjvbVXQl4z6g==", + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.6.1.tgz", + "integrity": "sha512-5UswCV6hpaRsNg5kkEHVcbBIXEYoVbMQaHJBXJCyEQ+CiFPV1NIOY0XOFWG4XR4GZcB8Kn6AsRs/9cy9TbqVMQ==", "peer": true, "dependencies": { - "@socket.io/component-emitter": "~3.0.0", - "backo2": "~1.0.2", + "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.2", - "engine.io-client": "~6.1.1", - "parseuri": "0.0.6", - "socket.io-parser": "~4.1.1" + "engine.io-client": "~6.4.0", + "socket.io-parser": "~4.2.1" }, "engines": { "node": ">=10.0.0" } }, "node_modules/socket.io-client/node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "peer": true, "dependencies": { "ms": "2.1.2" @@ -35666,27 +35685,13 @@ } } }, - "node_modules/socket.io-client/node_modules/socket.io-parser": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.1.1.tgz", - "integrity": "sha512-USQVLSkDWE5nbcY760ExdKaJxCE65kcsG/8k5FDGZVVxpD1pA7hABYXYkCUvxUuYYh/+uQw0N/fvBzfT8o07KA==", - "peer": true, - "dependencies": { - "@socket.io/component-emitter": "~3.0.0", - "debug": "~4.3.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/socket.io-parser": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz", - "integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz", + "integrity": "sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==", "peer": true, "dependencies": { - "@types/component-emitter": "^1.2.10", - "component-emitter": "~1.3.0", + "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1" }, "engines": { @@ -35694,9 +35699,9 @@ } }, "node_modules/socket.io-parser/node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "peer": true, "dependencies": { "ms": "2.1.2" @@ -35711,9 +35716,9 @@ } }, "node_modules/socket.io/node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "peer": true, "dependencies": { "ms": "2.1.2" @@ -36933,27 +36938,6 @@ "optional": true, "peer": true }, - "node_modules/through2": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", - "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", - "dependencies": { - "readable-stream": "3" - } - }, - "node_modules/through2/node_modules/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==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/thunky": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", @@ -39364,12 +39348,6 @@ "fd-slicer": "~1.1.0" } }, - "node_modules/yeast": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", - "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", - "peer": true - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -42348,9 +42326,9 @@ } }, "@socket.io/component-emitter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.0.0.tgz", - "integrity": "sha512-2pTGuibAXJswAPJjaKisthqS/NOK5ypG4LYT6tEAV0S/mxW0zOIvYvGK0V8w8+SHxAm6vRMSjqSalFXeBAqs+Q==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", "peer": true }, "@storybook/addon-actions": { @@ -50943,12 +50921,6 @@ "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", "optional": true }, - "@types/component-emitter": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.11.tgz", - "integrity": "sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ==", - "peer": true - }, "@types/cookie": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", @@ -50956,10 +50928,13 @@ "peer": true }, "@types/cors": { - "version": "2.8.12", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", - "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==", - "peer": true + "version": "2.8.13", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", + "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", + "peer": true, + "requires": { + "@types/node": "*" + } }, "@types/duplexify": { "version": "3.6.0", @@ -52679,12 +52654,6 @@ "babel-preset-current-node-syntax": "^1.0.0" } }, - "backo2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", - "peer": true - }, "bail": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", @@ -52751,12 +52720,6 @@ } } }, - "base64-arraybuffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.1.tgz", - "integrity": "sha512-vFIUq7FdLtjZMhATwDul5RZWv2jpXQ09Pd6jcVEOvIsqCWTRFD/ONHNfyOS8dA/Ippi5dsIgpyKWKZaAKZltbA==", - "peer": true - }, "base64-js": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", @@ -53965,8 +53928,8 @@ } }, "civil-server": { - "version": "git+ssh://git@github.com/EnCiv/civil-server.git#36ac227f8161fb051a7ef4630b653a62a2c7d98e", - "from": "civil-server@github:EnCiv/civil-server", + "version": "git+ssh://git@github.com/EnCiv/civil-server.git#8254efdda3abd363c0a31e3e1800290acd84acd0", + "from": "civil-server@github:EnCiv/civil-server#sockup", "peer": true, "requires": { "@babel/runtime": "^7.16.3", @@ -54013,9 +53976,10 @@ "react-helmet": "^6.1.0", "react-hot-loader": "^4.13.0", "react-jss": "^10.9.0", + "sib-api-v3-sdk": "^8.4.2", "sniffr": "^1.2.0", - "socket.io": "^4.4.0", - "socket.io-client": "^4.4.0", + "socket.io": "^4.6.1", + "socket.io-client": "^4.6.1", "stream-browserify": "^3.0.0", "superagent": "^5.3.1", "webpack-dev-server": "^4.6.0" @@ -54053,6 +54017,45 @@ "requires": { "@hapi/hoek": "^8.3.0" } + }, + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "peer": true, + "requires": { + "ms": "^2.1.1" + } + }, + "sib-api-v3-sdk": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/sib-api-v3-sdk/-/sib-api-v3-sdk-8.5.0.tgz", + "integrity": "sha512-6Ratp5kLN/rEEvk4XVIQ4L8IrCIrcfE9m1HjvHz/WepC+CVXPsjOlgRcK/jQjpN5kC+dmhDAqrTo1OtnF6i1wA==", + "peer": true, + "requires": { + "querystring": "0.2.0", + "superagent": "3.7.0" + }, + "dependencies": { + "superagent": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.7.0.tgz", + "integrity": "sha512-/8trxO6NbLx4YXb7IeeFTSmsQ35pQBiTBsLNvobZx7qBzBeHYvKCyIIhW2gNcWbLzYxPAjdgFbiepd8ypwC0Gw==", + "peer": true, + "requires": { + "component-emitter": "^1.2.0", + "cookiejar": "^2.1.0", + "debug": "^3.1.0", + "extend": "^3.0.0", + "form-data": "^2.3.1", + "formidable": "^1.1.1", + "methods": "^1.1.1", + "mime": "^1.4.1", + "qs": "^6.5.1", + "readable-stream": "^2.0.5" + } + } + } } } }, @@ -56364,9 +56367,9 @@ } }, "engine.io": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.1.0.tgz", - "integrity": "sha512-ErhZOVu2xweCjEfYcTdkCnEYUiZgkAcBBAhW4jbIvNG8SLU3orAqoJCiytZjYF7eTpVmmCrLDjLIEaPlUAs1uw==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.1.tgz", + "integrity": "sha512-JFYQurD/nbsA5BSPmbaOSLa3tSVj8L6o4srSwXXY3NqE+gGUNmmPTbhn8tjzcCtSqhFgIeqef81ngny8JM25hw==", "peer": true, "requires": { "@types/cookie": "^0.4.1", @@ -56377,77 +56380,70 @@ "cookie": "~0.4.1", "cors": "~2.8.5", "debug": "~4.3.1", - "engine.io-parser": "~5.0.0", - "ws": "~8.2.3" + "engine.io-parser": "~5.0.3", + "ws": "~8.11.0" }, "dependencies": { "cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", "peer": true }, "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "peer": true, "requires": { "ms": "2.1.2" } }, "ws": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", "peer": true, "requires": {} } } }, "engine.io-client": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.1.1.tgz", - "integrity": "sha512-V05mmDo4gjimYW+FGujoGmmmxRaDsrVr7AXA3ZIfa04MWM1jOfZfUwou0oNqhNwy/votUDvGDt4JA4QF4e0b4g==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.4.0.tgz", + "integrity": "sha512-GyKPDyoEha+XZ7iEqam49vz6auPnNJ9ZBfy89f+rMMas8AuiMWOZ9PVzu8xb9ZC6rafUqiGHSCfu22ih66E+1g==", "peer": true, "requires": { - "@socket.io/component-emitter": "~3.0.0", + "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1", - "engine.io-parser": "~5.0.0", - "has-cors": "1.1.0", - "parseqs": "0.0.6", - "parseuri": "0.0.6", - "ws": "~8.2.3", - "xmlhttprequest-ssl": "~2.0.0", - "yeast": "0.1.2" + "engine.io-parser": "~5.0.3", + "ws": "~8.11.0", + "xmlhttprequest-ssl": "~2.0.0" }, "dependencies": { "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "peer": true, "requires": { "ms": "2.1.2" } }, "ws": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", "peer": true, "requires": {} } } }, "engine.io-parser": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.2.tgz", - "integrity": "sha512-wuiO7qO/OEkPJSFueuATIXtrxF7/6GTbAO9QLv7nnbjwZ5tYhLm9zxvLwxstRs0dcT0KUlWTjtIOs1T86jt12g==", - "peer": true, - "requires": { - "base64-arraybuffer": "~1.0.1" - } + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.6.tgz", + "integrity": "sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==", + "peer": true }, "enhanced-resolve": { "version": "5.8.3", @@ -58322,12 +58318,6 @@ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==" }, - "has-cors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", - "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=", - "peer": true - }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -64422,18 +64412,6 @@ "parse5": "^6.0.1" } }, - "parseqs": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz", - "integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==", - "peer": true - }, - "parseuri": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz", - "integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==", - "peer": true - }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -65252,8 +65230,7 @@ "querystring": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "optional": true + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" }, "querystring-es3": { "version": "0.2.1", @@ -66969,23 +66946,23 @@ "integrity": "sha512-k7C0ZcHBU330LcSkKyc2cOOB0uHosME8b2t9qFJqdqB1cKwGmZWd7BVwBz5mWOMJ5dggK1dy2qv+DSwteKLBzQ==" }, "socket.io": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.4.0.tgz", - "integrity": "sha512-bnpJxswR9ov0Bw6ilhCvO38/1WPtE3eA2dtxi2Iq4/sFebiDJQzgKNYA7AuVVdGW09nrESXd90NbZqtDd9dzRQ==", + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.6.1.tgz", + "integrity": "sha512-KMcaAi4l/8+xEjkRICl6ak8ySoxsYG+gG6/XfRCPJPQ/haCRIJBTL4wIl8YCsmtaBovcAXGLOShyVWQ/FG8GZA==", "peer": true, "requires": { "accepts": "~1.3.4", "base64id": "~2.0.0", "debug": "~4.3.2", - "engine.io": "~6.1.0", - "socket.io-adapter": "~2.3.3", - "socket.io-parser": "~4.0.4" + "engine.io": "~6.4.1", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.1" }, "dependencies": { "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "peer": true, "requires": { "ms": "2.1.2" @@ -66994,61 +66971,60 @@ } }, "socket.io-adapter": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.3.3.tgz", - "integrity": "sha512-Qd/iwn3VskrpNO60BeRyCyr8ZWw9CPZyitW4AQwmRZ8zCiyDiL+znRnWX6tDHXnWn1sJrM1+b6Mn6wEDJJ4aYQ==", - "peer": true + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", + "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "peer": true, + "requires": { + "ws": "~8.11.0" + }, + "dependencies": { + "ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "peer": true, + "requires": {} + } + } }, "socket.io-client": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.4.0.tgz", - "integrity": "sha512-g7riSEJXi7qCFImPow98oT8X++MSsHz6MMFRXkWNJ6uEROSHOa3kxdrsYWMq85dO+09CFMkcqlpjvbVXQl4z6g==", + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.6.1.tgz", + "integrity": "sha512-5UswCV6hpaRsNg5kkEHVcbBIXEYoVbMQaHJBXJCyEQ+CiFPV1NIOY0XOFWG4XR4GZcB8Kn6AsRs/9cy9TbqVMQ==", "peer": true, "requires": { - "@socket.io/component-emitter": "~3.0.0", - "backo2": "~1.0.2", + "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.2", - "engine.io-client": "~6.1.1", - "parseuri": "0.0.6", - "socket.io-parser": "~4.1.1" + "engine.io-client": "~6.4.0", + "socket.io-parser": "~4.2.1" }, "dependencies": { "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "peer": true, "requires": { "ms": "2.1.2" } - }, - "socket.io-parser": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.1.1.tgz", - "integrity": "sha512-USQVLSkDWE5nbcY760ExdKaJxCE65kcsG/8k5FDGZVVxpD1pA7hABYXYkCUvxUuYYh/+uQw0N/fvBzfT8o07KA==", - "peer": true, - "requires": { - "@socket.io/component-emitter": "~3.0.0", - "debug": "~4.3.1" - } } } }, "socket.io-parser": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz", - "integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz", + "integrity": "sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==", "peer": true, "requires": { - "@types/component-emitter": "^1.2.10", - "component-emitter": "~1.3.0", + "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1" }, "dependencies": { "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "peer": true, "requires": { "ms": "2.1.2" @@ -67998,26 +67974,6 @@ "optional": true, "peer": true }, - "through2": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", - "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", - "requires": { - "readable-stream": "3" - }, - "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" - } - } - } - }, "thunky": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", @@ -69827,12 +69783,6 @@ "fd-slicer": "~1.1.0" } }, - "yeast": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", - "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", - "peer": true - }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 560e3439..37ab4b3a 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ }, "peerDependencies": { "civil-client": "github:EnCiv/civil-client", - "civil-server": "github:EnCiv/civil-server" + "civil-server": "github:EnCiv/civil-server#sockup" }, "devDependencies": { "@babel/cli": "^7.16.0", @@ -109,7 +109,6 @@ "sib-api-v3-sdk": "^7.2.4", "sniffr": "^1.2.0", "superagent": "^5.3.1", - "through2": "^4.0.2", "underscore.string": "^3.3.5", "wav-file-info": "git+https://github.com/nSimonFR/Node-WAV-File-Info.git", "webrtc-adapter": "^7.7.1" diff --git a/stories/deliberations.stories.js b/stories/deliberations.stories.js new file mode 100644 index 00000000..b92134d6 --- /dev/null +++ b/stories/deliberations.stories.js @@ -0,0 +1,362 @@ +import React from 'react' + +import App from '../app/components/app' +import { merge } from 'lodash' +import { DynamicFontSizeClientHelmet } from '../app/components/dynamic-font-size-helmet' + +export default { + title: 'Deliberations', + component: App, + argTypes: {}, + decorators: [ + Story => ( + <> + + + + ), + ], +} + +const iota = { + webComponent: { + "autoCameraStart": true, + "opening": { + "noPreamble": true + }, + "logo": "none", + "firstPage": "ViewerRecorder", + "showMicCamera": true, + "instructionLink": "/candidate-conversation-candidate-recorder/instructions", + "metaTags": [ + "property=\"og:title\" content=\"What is Democracy\"", + "property=\"og:image\" content=\"https://res.cloudinary.com/hf6mryjpf/image/upload/v1587412519/what-is-democracy-preview-2020Apr20_cl3ziv.png\"" + ], + "hangupButton": { + "name": "Exit" + }, + "maxParticipants": 5, + "webComponent": "CcWrapper", + "participants": { + "moderator": { + "name": "David Fridley", + "speaking": [ + ["https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1679341399/5e8cec3912d6e10017ed9caa-0-speaking20230320T194318392Z.mp4", "https://video.wixstatic.com/video/2796e1_f65b9429e7a144cc80f6cfe50d49e780/480p/mp4/file.mp4", "https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1679341401/5e8cec3912d6e10017ed9caa-1-speaking20230320T194319647Z.mp4"], + "https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1679341402/5e8cec3912d6e10017ed9caa-2-speaking20230320T194320764Z.mp4", + "https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1679341403/5e8cec3912d6e10017ed9caa-3-speaking20230320T194322403Z.mp4", + "https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1679341405/5e8cec3912d6e10017ed9caa-4-speaking20230320T194323624Z.mp4", + "https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1679341406/5e8cec3912d6e10017ed9caa-5-speaking20230320T194325114Z.mp4", + "https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1679341407/5e8cec3912d6e10017ed9caa-6-speaking20230320T194326426Z.mp4", + "https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1679341408/5e8cec3912d6e10017ed9caa-7-speaking20230320T194327292Z.mp4", + "https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1679341409/5e8cec3912d6e10017ed9caa-8-speaking20230320T194328169Z.mp4", + "https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1679341410/5e8cec3912d6e10017ed9caa-9-speaking20230320T194329403Z.mp4" + ], + "listening": "https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1679341412/5e8cec3912d6e10017ed9caa-0-listening20230320T194330611Z.mp4", + "agenda": [ + [ + ['The Electoral College'], + ["Reform 1: The National Popular Vote"], + ["Do you support or oppose a transition to a National Popular Vote system and the elimination of the Electoral College? Why? Why not?"], + ], + ["By having each person's vote count equally nationwide, does a National Popular Vote system create a more equal and fair system for the election of our president? Should this principle of equality be a necessary part of our democratic republic? "], + ["Is it valuable for every state to have at least a certain amount of influence in the election of our president, regardless of their size, as is the case in the current system?"], + ["Would smaller states and rural areas see their influence on national politics diminish as a result of a transition to a national popular vote? If so, is this an acceptable or unacceptable condition for our presidential elections?"], + ["What effect would a transition to a National Popular Vote have upon the way presidential candidates' campaign? Would they ignore small states and less populated areas in favor of more populous areas of the country? "], + ["Would the changes in the way presidential candidate's campaign be a fair outcome of each person's vote counting equally?"], + ["What effect would a transition to a National Popular Vote have upon voter turnout? "], + ["What is required for a transition to a National Popular Vote? Is a transition to a National Popular Vote practical and feasible within our current political system? Why? Why not?"], + ["Thank you"], + ], + "timeLimits": [60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60] + }, + "audience1": { + "speaking": [ + "https://res.cloudinary.com/hisfgxdff/video/upload/q_auto/v1583876500/5e68098e9f2f3600177ab1af-0-speaking20200310T214140489Z.mp4", + "https://res.cloudinary.com/hisfgxdff/video/upload/q_auto/v1583876503/5e68098e9f2f3600177ab1af-1-speaking20200310T214141310Z.mp4", + "https://res.cloudinary.com/hisfgxdff/video/upload/q_auto/v1583876507/5e68098e9f2f3600177ab1af-2-speaking20200310T214143708Z.mp4", + "https://res.cloudinary.com/hisfgxdff/video/upload/q_auto/v1583876512/5e68098e9f2f3600177ab1af-3-speaking20200310T214148300Z.mp4" + ], + "name": "David Fridley", + "listening": "https://res.cloudinary.com/hisfgxdff/video/upload/q_auto/v1583876513/5e68098e9f2f3600177ab1af-0-listening20200310T214152882Z.mp4", + }, + "audience2": { + "speaking": [ + "https://res.cloudinary.com/hisfgxdff/video/upload/q_auto/v1583940332/5e68ff1619a56d0017c492ac-0-speaking20200311T152531818Z.mp4", + "https://res.cloudinary.com/hisfgxdff/video/upload/q_auto/v1583940335/5e68ff1619a56d0017c492ac-1-speaking20200311T152532379Z.mp4", + "https://res.cloudinary.com/hisfgxdff/video/upload/q_auto/v1583940339/5e68ff1619a56d0017c492ac-2-speaking20200311T152535299Z.mp4", + "https://res.cloudinary.com/hisfgxdff/video/upload/q_auto/v1583940344/5e68ff1619a56d0017c492ac-3-speaking20200311T152539296Z.mp4" + ], + "name": "Adolf G Gundersen", + "listening": "https://res.cloudinary.com/hisfgxdff/video/upload/q_auto/v1583940350/5e68ff1619a56d0017c492ac-0-listening20200311T152544519Z.mp4", + "bp_info": { + "stage_id": "0", + "election_date": "2020-11-03" + } + }, + "human": { + "listening": { + "round": 1, + "seat": "nextUp" + } + } + }, + "closing": { + "thanks": "Thank You!", + "iframe": { + "src": "https://docs.google.com/forms/d/e/1FAIpQLScYiUwoqCmlqr5KQf99ewVRxEvyCrx9CvSH196xlqqtj7cEkg/viewform?embedded=true", + "width": 640, + "height": 1579 + } + } + }, + env: 'development', + path: '/country:us/organization:cfa/office:moderator/2021-03-21-recorder-622157bbc1644259e8614a9b', + user: { + email: 'ddfridley@yahoo.com', + id: '621e826899902756d4ba49f5', + }, + browserConfig: { + os: { + name: 'windows', + version: [10, 0], + versionString: '10.0', + }, + browser: { + name: 'chrome', + version: [98, 0, 4758, 102], + versionString: '98.0.4758.102', + }, + type: 'desktop', + model: '', + ip: '::ffff:127.0.0.1', + }, + _id: '622157bbc1644259e8614a9c', + component: { + component: 'UndebateCreator', + participants: {}, + }, + bp_info: { + office: 'Moderator', + election_date: '03/21/2021', + candidate_name: 'Bill Smith', + last_name: 'Smith', + unique_id: '622157bbc1644259e8614a9b', + candidate_emails: ['billsmith@gmail.com'], + party: '', + election_source: 'CodeForAmerica.NAC', + }, + subject: 'Moderator-Candidate Recorder', + description: 'A Candidate Recorder for the undebate: Moderator', + parentId: '621e8aefe7db9b6338d0ab74', +} + +const otherProps = { + env: 'development', + user: { + email: 'ddfridley@yahoo.com', + id: '621e826899902756d4ba49f5', + }, + browserConfig: { + os: { + name: 'windows', + version: [10, 0], + versionString: '10.0', + }, + browser: { + name: 'chrome', + version: [98, 0, 4758, 102], + versionString: '98.0.4758.102', + }, + type: 'desktop', + model: '', + ip: '::ffff:127.0.0.1', + }, + bp_info: { + office: 'Moderator', + election_date: '03/21/2021', + candidate_name: 'Bill Smith', + last_name: 'Smith', + unique_id: '622157bbc1644259e8614a9b', + candidate_emails: ['billsmith@gmail.com'], + party: '', + election_source: 'CodeForAmerica.NAC', + }, +} + +const Template = args => + +export const Undebate = Template.bind({}) +Undebate.args = { + iota: iota, +} + +const cc3 = { + _id: { + $oid: '5e7e6e147c213e3443f116e5', + }, + path: '/candidate-conversation-3', + subject: 'Candidate Conversation with 3 participants', + description: 'A prototype Candidate Conversation for schoolboard', + component: { + component: 'MergeChildren', + }, + webComponent: { + webComponent: 'CandidateConversation', + opening: { + line1: 'You are about to experience a new kind of conversation', + line2: 'This is how voters can learn about candidates in a more human way', + line3: + 'And this is how we can efficiently facilitate 500K conversations all over the country, every election season', + line4: 'The topic of the discussion is:', + bigLine: 'US School Board Candidate Conversation', + subLine: 'This is a mock conversation, these are not real candidates', + }, + participants: { + moderator: { + name: 'David Fridley', + speaking: [ + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1566788682/candidate-conversation-moderator-0_at5un1.mp4', + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1566788667/candidate-converation-moderator-1_z2kjhr.mp4', + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1566788659/candidate-confersation-moderator-2_cid3dq.mp4', + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1566788634/candidate-conversation-moderator-3_iq0npa.mp4', + ], + listening: + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1566788719/candidate-conversation-moderator-listening_nlfeoy.mp4', + agenda: [ + [ + 'Introductions', + '1- Who you are', + '2- Where you are', + '3- One word to describe yourself', + '4- What office you are running for', + ], + ['What type of skills should students be learning for success in the 21st century?'], + ['Closing Remarks'], + ['Thank you'], + ], + timeLimits: [10, 60, 60], + }, + audience1: { + speaking: [ + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1566942893/5d5dc697d32514001766ca87-1-speaking20190827T215452394Z.mp4', + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1566942898/5d5dc697d32514001766ca87-2-speaking20190827T215455964Z.mp4', + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1566942903/5d5dc697d32514001766ca87-3-speaking20190827T215503161Z.mp4', + ], + name: 'Will', + listening: + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1566942901/5d5dc697d32514001766ca87-2-nextUp20190827T215500659Z.mp4', + }, + audience2: { + speaking: [ + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1567120064/5d685ab98d5ab100175a1dd7-1-speaking20190829T230738520Z.mp4', + 'https://res.cloudinary.com/hf6mryjpf/video/upload/du_41/v1567120076/5d685ab98d5ab100175a1dd7-2-speaking20190829T230743431Z.mp4', + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1567120089/5d685ab98d5ab100175a1dd7-3-speaking20190829T230802074Z.mp4', + ], + name: 'MaryBeth MaryBeth McGarvey', + listening: + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1567120083/5d685ab98d5ab100175a1dd7-2-nextUp20190829T230755711Z.mp4', + }, + audience3: { + speaking: [ + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1566839072/5d64111ba62cb60017dad9eb-1-speaking20190826T170428600Z.mp4', + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1566839081/5d64111ba62cb60017dad9eb-2-speaking20190826T170432708Z.mp4', + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1566839337/5d64111ba62cb60017dad9eb-3-speaking20190826T170818981Z.mp4', + ], + name: 'Alex Johnson', + listening: + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1566926818/5d64111ba62cb60017dad9eb-listening20190826T170818981Z_s1wphm.mp4', + }, + }, + }, +} + +export const CandidateConversation = Template.bind({}) +const ccIota = merge({}, cc3, otherProps) +CandidateConversation.args = { + iota: ccIota, +} + +const ccWrapperIota = { + _id: { + $oid: '5f7ca753e7179a6ea5213fb0', + }, + path: '/qa/ccwrapper-recorder', + subject: 'You are invited to record a Candidate Conversation', + description: 'A Recorder for the Candidate Conversation with 2 participants using CC wrapper', + webComponent: { + autoBegin: true, + webComponent: 'CcWrapper', + instructionLink: 'https://docs.google.com/document/d/1fORs9PlLss9azlsnf0A0lxFoOzDQJ9RJ-zNRZ583SVo/edit?usp=sharing', + participants: { + moderator: { + name: 'David Fridley', + speaking: [ + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1566788719/candidate-conversation-creator-moderator-0_d7a3zr.mp4', + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1566788635/candidate-conversation-creator-moderator-1_gtchg2.mp4', + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1566788669/candidate-conversation-creator-moderator-2_bsceus.mp4', + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1566788656/candidate-conversation-creator-moderator-3_qomqgj.mp4', + ], + listening: + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1566788719/candidate-conversation-moderator-listening_nlfeoy.mp4', + agenda: [ + [ + 'Introductions', + '1- Who you are', + '2- Where you are', + '3- One word to describe yourself', + '4- What office you are running for', + ], + ['What type of skills should students be learning for success in the 21st century?'], + ['Closing Remarks'], + ['Thank you'], + ], + timeLimits: [10, 60, 60], + }, + audience1: { + name: 'Adolf Gundersen', + speaking: [ + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1565640905/undebate-short-a1.mp4', + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1565640905/undebate-short-a2.mp4', + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1565640905/undebate-short-a3.mp4', + ], + listening: 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1565640905/undebate-short-as.mp4', + }, + human: { + listening: { + round: 2, + seat: 'nextUp', + }, + }, + }, + closing: { + thanks: 'Thank You.', + iframe: { + src: 'https://docs.google.com/forms/d/e/1FAIpQLSeh7kAVWpyjnSYmHhjfpjfalgznfDA_AF2xmrFB8ZzQj75Vyw/viewform?embedded=true', + width: '640', + height: '1511', + }, + }, + }, + parentId: '5f7ca481e7179a6ea5213f43', +} + +export const CcWrapper = Template.bind({}) +CcWrapper.args = { + iota: merge({}, ccWrapperIota, otherProps), +} + +export const CcWrapperWithListeningFirst = Template.bind({}) +CcWrapperWithListeningFirst.args = { + iota: merge({}, iota, { webComponent: { webComponent: 'CcWrapper', autoBegin: true } }), +} + +/* Todo fix Component so they do not error if empty props +export const NothingHere = Template.bind({}) +NothingHere.args = { + iota: { + webComponent: { + webComponent: 'Undebate', + }, + }, +} +*/ diff --git a/stories/undebate.stories.js b/stories/undebate.stories.js index f600a155..4f9fd044 100644 --- a/stories/undebate.stories.js +++ b/stories/undebate.stories.js @@ -242,6 +242,7 @@ const ccWrapperIota = { description: 'A Recorder for the Candidate Conversation with 2 participants using CC wrapper', webComponent: { webComponent: 'CcWrapper', + autoBegin: true, instructionLink: 'https://docs.google.com/document/d/1fORs9PlLss9azlsnf0A0lxFoOzDQJ9RJ-zNRZ583SVo/edit?usp=sharing', participants: { moderator: { @@ -303,7 +304,7 @@ CcWrapper.args = { export const CcWrapperWithListeningFirst = Template.bind({}) CcWrapperWithListeningFirst.args = { - iota: merge({}, iota, { webComponent: { webComponent: 'CcWrapper' } }), + iota: merge({}, iota, { webComponent: { webComponent: 'CcWrapper', autoBegin: 'true' } }), } /* Todo fix Component so they do not error if empty props diff --git a/stories/viewer-recorder.stories.js b/stories/viewer-recorder.stories.js new file mode 100644 index 00000000..20435372 --- /dev/null +++ b/stories/viewer-recorder.stories.js @@ -0,0 +1,392 @@ +import React from 'react' + +import ViewerRecorder from '../app/web-components/cc-wrapper/viewer-recorder' +import { merge } from 'lodash' +import { DynamicFontSizeClientHelmet } from '../app/components/dynamic-font-size-helmet' + +export default { + title: 'ViewerRecorder', + component: ViewerRecorder, + argTypes: {}, + decorators: [ + Story => ( + <> + + + + ), + ], +} + +const iota = { + webComponent: { + "autoCameraStart": true, + "opening": { + "noPreamble": true + }, + "logo": "none", + "instructionLink": "/candidate-conversation-candidate-recorder/instructions", + "metaTags": [ + "property=\"og:title\" content=\"What is Democracy\"", + "property=\"og:image\" content=\"https://res.cloudinary.com/hf6mryjpf/image/upload/v1587412519/what-is-democracy-preview-2020Apr20_cl3ziv.png\"" + ], + "hangupButton": { + "name": "Exit" + }, + "maxParticipants": 5, + "webComponent": "CcWrapper", + "participants": { + "moderator": { + "name": "David Fridley", + "speaking": [ + ["https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1679341399/5e8cec3912d6e10017ed9caa-0-speaking20230320T194318392Z.mp4", "https://video.wixstatic.com/video/2796e1_f65b9429e7a144cc80f6cfe50d49e780/480p/mp4/file.mp4", "https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1679341401/5e8cec3912d6e10017ed9caa-1-speaking20230320T194319647Z.mp4"], + "https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1679341402/5e8cec3912d6e10017ed9caa-2-speaking20230320T194320764Z.mp4", + "https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1679341403/5e8cec3912d6e10017ed9caa-3-speaking20230320T194322403Z.mp4", + "https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1679341405/5e8cec3912d6e10017ed9caa-4-speaking20230320T194323624Z.mp4", + "https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1679341406/5e8cec3912d6e10017ed9caa-5-speaking20230320T194325114Z.mp4", + "https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1679341407/5e8cec3912d6e10017ed9caa-6-speaking20230320T194326426Z.mp4", + "https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1679341408/5e8cec3912d6e10017ed9caa-7-speaking20230320T194327292Z.mp4", + "https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1679341409/5e8cec3912d6e10017ed9caa-8-speaking20230320T194328169Z.mp4", + "https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1679341410/5e8cec3912d6e10017ed9caa-9-speaking20230320T194329403Z.mp4" + ], + "listening": "https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1679341412/5e8cec3912d6e10017ed9caa-0-listening20230320T194330611Z.mp4", + "agenda": [ + [ + ['The Electoral College'], + ["Reform 1: The National Popular Vote"], + ["Do you support or oppose a transition to a National Popular Vote system and the elimination of the Electoral College? Why? Why not?"], + ], + ["By having each person's vote count equally nationwide, does a National Popular Vote system create a more equal and fair system for the election of our president? Should this principle of equality be a necessary part of our democratic republic? "], + ["Is it valuable for every state to have at least a certain amount of influence in the election of our president, regardless of their size, as is the case in the current system?"], + ["Would smaller states and rural areas see their influence on national politics diminish as a result of a transition to a national popular vote? If so, is this an acceptable or unacceptable condition for our presidential elections?"], + ["What effect would a transition to a National Popular Vote have upon the way presidential candidates' campaign? Would they ignore small states and less populated areas in favor of more populous areas of the country? "], + ["Would the changes in the way presidential candidate's campaign be a fair outcome of each person's vote counting equally?"], + ["What effect would a transition to a National Popular Vote have upon voter turnout? "], + ["What is required for a transition to a National Popular Vote? Is a transition to a National Popular Vote practical and feasible within our current political system? Why? Why not?"], + ["Thank you"], + ], + "timeLimits": [60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60] + }, + "audience1": { + "speaking": [ + "https://res.cloudinary.com/hisfgxdff/video/upload/q_auto/v1583876500/5e68098e9f2f3600177ab1af-0-speaking20200310T214140489Z.mp4", + "https://res.cloudinary.com/hisfgxdff/video/upload/q_auto/v1583876503/5e68098e9f2f3600177ab1af-1-speaking20200310T214141310Z.mp4", + "https://res.cloudinary.com/hisfgxdff/video/upload/q_auto/v1583876507/5e68098e9f2f3600177ab1af-2-speaking20200310T214143708Z.mp4", + "https://res.cloudinary.com/hisfgxdff/video/upload/q_auto/v1583876512/5e68098e9f2f3600177ab1af-3-speaking20200310T214148300Z.mp4" + ], + "name": "David Fridley", + "listening": "https://res.cloudinary.com/hisfgxdff/video/upload/q_auto/v1583876513/5e68098e9f2f3600177ab1af-0-listening20200310T214152882Z.mp4", + }, + "audience2": { + "speaking": [ + "https://res.cloudinary.com/hisfgxdff/video/upload/q_auto/v1583940332/5e68ff1619a56d0017c492ac-0-speaking20200311T152531818Z.mp4", + "https://res.cloudinary.com/hisfgxdff/video/upload/q_auto/v1583940335/5e68ff1619a56d0017c492ac-1-speaking20200311T152532379Z.mp4", + "https://res.cloudinary.com/hisfgxdff/video/upload/q_auto/v1583940339/5e68ff1619a56d0017c492ac-2-speaking20200311T152535299Z.mp4", + "https://res.cloudinary.com/hisfgxdff/video/upload/q_auto/v1583940344/5e68ff1619a56d0017c492ac-3-speaking20200311T152539296Z.mp4" + ], + "name": "Adolf G Gundersen", + "listening": "https://res.cloudinary.com/hisfgxdff/video/upload/q_auto/v1583940350/5e68ff1619a56d0017c492ac-0-listening20200311T152544519Z.mp4", + "bp_info": { + "stage_id": "0", + "election_date": "2020-11-03" + } + }, + "audience3": { + "speaking": [ + "https://res.cloudinary.com/hisfgxdff/video/upload/q_auto/v1583940332/5e68ff1619a56d0017c492ac-0-speaking20200311T152531818Z.mp4", + "https://res.cloudinary.com/hisfgxdff/video/upload/q_auto/v1583940335/5e68ff1619a56d0017c492ac-1-speaking20200311T152532379Z.mp4", + "https://res.cloudinary.com/hisfgxdff/video/upload/q_auto/v1583940339/5e68ff1619a56d0017c492ac-2-speaking20200311T152535299Z.mp4", + "https://res.cloudinary.com/hisfgxdff/video/upload/q_auto/v1583940344/5e68ff1619a56d0017c492ac-3-speaking20200311T152539296Z.mp4" + ], + "name": "Adolf G Gundersen", + "listening": "https://res.cloudinary.com/hisfgxdff/video/upload/q_auto/v1583940350/5e68ff1619a56d0017c492ac-0-listening20200311T152544519Z.mp4", + "bp_info": { + "stage_id": "0", + "election_date": "2020-11-03" + } + }, + "audience4": { + "speaking": [ + "https://res.cloudinary.com/hisfgxdff/video/upload/q_auto/v1583940332/5e68ff1619a56d0017c492ac-0-speaking20200311T152531818Z.mp4", + "https://res.cloudinary.com/hisfgxdff/video/upload/q_auto/v1583940335/5e68ff1619a56d0017c492ac-1-speaking20200311T152532379Z.mp4", + "https://res.cloudinary.com/hisfgxdff/video/upload/q_auto/v1583940339/5e68ff1619a56d0017c492ac-2-speaking20200311T152535299Z.mp4", + "https://res.cloudinary.com/hisfgxdff/video/upload/q_auto/v1583940344/5e68ff1619a56d0017c492ac-3-speaking20200311T152539296Z.mp4" + ], + "name": "Adolf G Gundersen", + "listening": "https://res.cloudinary.com/hisfgxdff/video/upload/q_auto/v1583940350/5e68ff1619a56d0017c492ac-0-listening20200311T152544519Z.mp4", + "bp_info": { + "stage_id": "0", + "election_date": "2020-11-03" + } + }/* + "human": { + "listening": { + "round": 1, + "seat": "nextUp" + } + }*/ + }, + "closing": { + "thanks": "Thank You!", + "iframe": { + "src": "https://docs.google.com/forms/d/e/1FAIpQLScYiUwoqCmlqr5KQf99ewVRxEvyCrx9CvSH196xlqqtj7cEkg/viewform?embedded=true", + "width": 640, + "height": 1579 + } + } + }, + env: 'development', + path: '/country:us/organization:cfa/office:moderator/2021-03-21-recorder-622157bbc1644259e8614a9b', + user: { + email: 'ddfridley@yahoo.com', + id: '621e826899902756d4ba49f5', + }, + browserConfig: { + os: { + name: 'windows', + version: [10, 0], + versionString: '10.0', + }, + browser: { + name: 'chrome', + version: [98, 0, 4758, 102], + versionString: '98.0.4758.102', + }, + type: 'desktop', + model: '', + ip: '::ffff:127.0.0.1', + }, + _id: '622157bbc1644259e8614a9c', + component: { + component: 'UndebateCreator', + participants: {}, + }, + bp_info: { + office: 'Moderator', + election_date: '03/21/2021', + candidate_name: 'Bill Smith', + last_name: 'Smith', + unique_id: '622157bbc1644259e8614a9b', + candidate_emails: ['billsmith@gmail.com'], + party: '', + election_source: 'CodeForAmerica.NAC', + }, + subject: 'Moderator-Candidate Recorder', + description: 'A Candidate Recorder for the undebate: Moderator', + parentId: '621e8aefe7db9b6338d0ab74', +} + +const otherProps = { + env: 'development', + user: { + email: 'ddfridley@yahoo.com', + id: '621e826899902756d4ba49f5', + }, + browserConfig: { + os: { + name: 'windows', + version: [10, 0], + versionString: '10.0', + }, + browser: { + name: 'chrome', + version: [98, 0, 4758, 102], + versionString: '98.0.4758.102', + }, + type: 'desktop', + model: '', + ip: '::ffff:127.0.0.1', + }, + bp_info: { + office: 'Moderator', + election_date: '03/21/2021', + candidate_name: 'Bill Smith', + last_name: 'Smith', + unique_id: '622157bbc1644259e8614a9b', + candidate_emails: ['billsmith@gmail.com'], + party: '', + election_source: 'CodeForAmerica.NAC', + }, +} + +const Template = args => + +export const Undebate = Template.bind({}) +Undebate.args = { + dispatch: () => { }, + ccState: { + reviewing: false, // true then ViewerRecorder is in review mode rather than record mode + participants: {}, // this is written directly by ViewerRecorder to preserve stored video, and computed video url, referenced by Ending to upload the videos} + }, + ...iota.webComponent, +} + +const cc3 = { + _id: { + $oid: '5e7e6e147c213e3443f116e5', + }, + path: '/candidate-conversation-3', + subject: 'Candidate Conversation with 3 participants', + description: 'A prototype Candidate Conversation for schoolboard', + component: { + component: 'MergeChildren', + }, + webComponent: { + webComponent: 'CandidateConversation', + opening: { + line1: 'You are about to experience a new kind of conversation', + line2: 'This is how voters can learn about candidates in a more human way', + line3: + 'And this is how we can efficiently facilitate 500K conversations all over the country, every election season', + line4: 'The topic of the discussion is:', + bigLine: 'US School Board Candidate Conversation', + subLine: 'This is a mock conversation, these are not real candidates', + }, + participants: { + moderator: { + name: 'David Fridley', + speaking: [ + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1566788682/candidate-conversation-moderator-0_at5un1.mp4', + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1566788667/candidate-converation-moderator-1_z2kjhr.mp4', + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1566788659/candidate-confersation-moderator-2_cid3dq.mp4', + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1566788634/candidate-conversation-moderator-3_iq0npa.mp4', + ], + listening: + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1566788719/candidate-conversation-moderator-listening_nlfeoy.mp4', + agenda: [ + [ + 'Introductions', + '1- Who you are', + '2- Where you are', + '3- One word to describe yourself', + '4- What office you are running for', + ], + ['What type of skills should students be learning for success in the 21st century?'], + ['Closing Remarks'], + ['Thank you'], + ], + timeLimits: [10, 60, 60], + }, + audience1: { + speaking: [ + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1566942893/5d5dc697d32514001766ca87-1-speaking20190827T215452394Z.mp4', + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1566942898/5d5dc697d32514001766ca87-2-speaking20190827T215455964Z.mp4', + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1566942903/5d5dc697d32514001766ca87-3-speaking20190827T215503161Z.mp4', + ], + name: 'Will', + listening: + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1566942901/5d5dc697d32514001766ca87-2-nextUp20190827T215500659Z.mp4', + }, + audience2: { + speaking: [ + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1567120064/5d685ab98d5ab100175a1dd7-1-speaking20190829T230738520Z.mp4', + 'https://res.cloudinary.com/hf6mryjpf/video/upload/du_41/v1567120076/5d685ab98d5ab100175a1dd7-2-speaking20190829T230743431Z.mp4', + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1567120089/5d685ab98d5ab100175a1dd7-3-speaking20190829T230802074Z.mp4', + ], + name: 'MaryBeth MaryBeth McGarvey', + listening: + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1567120083/5d685ab98d5ab100175a1dd7-2-nextUp20190829T230755711Z.mp4', + }, + audience3: { + speaking: [ + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1566839072/5d64111ba62cb60017dad9eb-1-speaking20190826T170428600Z.mp4', + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1566839081/5d64111ba62cb60017dad9eb-2-speaking20190826T170432708Z.mp4', + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1566839337/5d64111ba62cb60017dad9eb-3-speaking20190826T170818981Z.mp4', + ], + name: 'Alex Johnson', + listening: + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1566926818/5d64111ba62cb60017dad9eb-listening20190826T170818981Z_s1wphm.mp4', + }, + }, + }, +} + +export const CandidateConversation = Template.bind({}) +const ccIota = merge({}, cc3, otherProps) +CandidateConversation.args = { + iota: ccIota, +} + +const ccWrapperIota = { + _id: { + $oid: '5f7ca753e7179a6ea5213fb0', + }, + path: '/qa/ccwrapper-recorder', + subject: 'You are invited to record a Candidate Conversation', + description: 'A Recorder for the Candidate Conversation with 2 participants using CC wrapper', + webComponent: { + webComponent: 'CcWrapper', + instructionLink: 'https://docs.google.com/document/d/1fORs9PlLss9azlsnf0A0lxFoOzDQJ9RJ-zNRZ583SVo/edit?usp=sharing', + participants: { + moderator: { + name: 'David Fridley', + speaking: [ + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1566788719/candidate-conversation-creator-moderator-0_d7a3zr.mp4', + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1566788635/candidate-conversation-creator-moderator-1_gtchg2.mp4', + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1566788669/candidate-conversation-creator-moderator-2_bsceus.mp4', + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1566788656/candidate-conversation-creator-moderator-3_qomqgj.mp4', + ], + listening: + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1566788719/candidate-conversation-moderator-listening_nlfeoy.mp4', + agenda: [ + [ + 'Introductions', + '1- Who you are', + '2- Where you are', + '3- One word to describe yourself', + '4- What office you are running for', + ], + ['What type of skills should students be learning for success in the 21st century?'], + ['Closing Remarks'], + ['Thank you'], + ], + timeLimits: [10, 60, 60], + }, + audience1: { + name: 'Adolf Gundersen', + speaking: [ + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1565640905/undebate-short-a1.mp4', + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1565640905/undebate-short-a2.mp4', + 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1565640905/undebate-short-a3.mp4', + ], + listening: 'https://res.cloudinary.com/hf6mryjpf/video/upload/q_auto/v1565640905/undebate-short-as.mp4', + }, + human: { + listening: { + round: 2, + seat: 'nextUp', + }, + }, + }, + closing: { + thanks: 'Thank You.', + iframe: { + src: 'https://docs.google.com/forms/d/e/1FAIpQLSeh7kAVWpyjnSYmHhjfpjfalgznfDA_AF2xmrFB8ZzQj75Vyw/viewform?embedded=true', + width: '640', + height: '1511', + }, + }, + }, + parentId: '5f7ca481e7179a6ea5213f43', +} + +export const CcWrapper = Template.bind({}) +CcWrapper.args = { + iota: merge({}, ccWrapperIota, otherProps), +} + +export const CcWrapperWithListeningFirst = Template.bind({}) +CcWrapperWithListeningFirst.args = { + iota: merge({}, iota, { webComponent: { webComponent: 'CcWrapper' } }), +} + +/* Todo fix Component so they do not error if empty props +export const NothingHere = Template.bind({}) +NothingHere.args = { + iota: { + webComponent: { + webComponent: 'Undebate', + }, + }, +} +*/ diff --git a/webpack-prod.config.js b/webpack-prod.config.js index 9968ea38..beb4b098 100644 --- a/webpack-prod.config.js +++ b/webpack-prod.config.js @@ -1,13 +1,14 @@ const path = require('path') const webpack = require('webpack') +const production = process.env.NODE_ENV === 'production' module.exports = { - mode: 'production', + mode: 'production', // meaning no hot loading of changes context: path.resolve(__dirname, 'dist'), // dist because app failed when building this as a node_module in another component entry: { main: './client/main-app.js', }, - devtool: 'inline-cheap-source-map', + devtool: production ? 'inline-cheap-source-map' : 'source-map', output: { path: path.join(__dirname, 'assets/webpack'), filename: '[name].js',