Move prop logic out of index.
This commit is contained in:
parent
9b2618d376
commit
8469c37254
14 changed files with 313 additions and 217 deletions
|
@ -3,5 +3,6 @@ module.exports = {
|
|||
rules: {
|
||||
"no-restricted-globals": "off",
|
||||
"no-undef": "off",
|
||||
"eqeqeq": "off",
|
||||
},
|
||||
};
|
||||
|
|
5
frontend/src/controller/admin.js
Normal file
5
frontend/src/controller/admin.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
function getProps () {
|
||||
return {};
|
||||
}
|
||||
|
||||
export default getProps;
|
49
frontend/src/controller/client.js
Normal file
49
frontend/src/controller/client.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
import BufferClientBackend from '../buffer-client-backend';
|
||||
import StartAudioContext from 'startaudiocontext';
|
||||
|
||||
function getProps(render) {
|
||||
const client = {
|
||||
clientName: '',
|
||||
clientId: '',
|
||||
sources: [],
|
||||
realPosition: 0,
|
||||
};
|
||||
|
||||
client.options = {
|
||||
onTimeOffset: timeOffset => {
|
||||
client.realPosition = backend.getRealPosition();
|
||||
render();
|
||||
},
|
||||
onClientId: clientId => {
|
||||
client.clientId = clientId;
|
||||
render();
|
||||
},
|
||||
onSourcesUpdated: sources => {
|
||||
client.sources = sources
|
||||
render();
|
||||
},
|
||||
onSyncPacket: packet => {
|
||||
client.realPosition = backend.getRealPosition();
|
||||
render();
|
||||
}
|
||||
};
|
||||
|
||||
const backend = new BufferClientBackend(client.options);
|
||||
|
||||
client.onChangeName = (clientName) => {
|
||||
backend.changeName(clientName);
|
||||
client.clientName = clientName;
|
||||
render();
|
||||
};
|
||||
|
||||
client.onComponentMount = () => {
|
||||
StartAudioContext(backend._context, '.playit')
|
||||
.then(() => {
|
||||
backend.afterContextStarted();
|
||||
});
|
||||
};
|
||||
|
||||
return client;
|
||||
};
|
||||
|
||||
export default getProps;
|
143
frontend/src/controller/simulator.js
Normal file
143
frontend/src/controller/simulator.js
Normal file
|
@ -0,0 +1,143 @@
|
|||
import BufferClientBackend from '../buffer-client-backend';
|
||||
import StartAudioContext from 'startaudiocontext';
|
||||
|
||||
const HOST = window.location.host.split(':')[0];
|
||||
|
||||
function getProps (render) {
|
||||
let AudioContext = window.AudioContext || window.webkitAudioContext;
|
||||
let context = new AudioContext();
|
||||
|
||||
const createClient = () => {
|
||||
let mixerNode = null;
|
||||
|
||||
let options = {
|
||||
context: context,
|
||||
onTimeOffset: timeOffset => {
|
||||
client.realPosition = backend.getRealPosition();
|
||||
render();
|
||||
},
|
||||
onClientId: clientId => {
|
||||
client.clientId = clientId;
|
||||
render();
|
||||
},
|
||||
onSourcesUpdated: sources => {
|
||||
client.sources = sources;
|
||||
render();
|
||||
},
|
||||
onSyncPacket: packet => {
|
||||
client.realPosition = backend.getRealPosition();
|
||||
render();
|
||||
},
|
||||
onMixerNodeCreated: node => {
|
||||
mixerNode = node;
|
||||
mixerNode.panningModel = 'HRTF';
|
||||
mixerNode.distanceModel = 'inverse';
|
||||
mixerNode.refDistance = 1;
|
||||
mixerNode.maxDistance = 500;
|
||||
mixerNode.rolloffFactor = 0.01;
|
||||
mixerNode.coneInnerAngle = 360;
|
||||
mixerNode.coneOuterAngle = 0;
|
||||
mixerNode.codeOuterGain = 0;
|
||||
|
||||
client.x = 50;
|
||||
client.y = 50;
|
||||
|
||||
render();
|
||||
},
|
||||
};
|
||||
|
||||
let backend = new BufferClientBackend(options);
|
||||
|
||||
const updateX = (newX) => {
|
||||
client.x = newX;
|
||||
|
||||
mixerNode.positionX.value = newX - 250;
|
||||
};
|
||||
|
||||
const updateY = (newY) => {
|
||||
client.y = newY;
|
||||
|
||||
mixerNode.positionZ.value = 250 - newY;
|
||||
}
|
||||
|
||||
let client = {
|
||||
sources: [],
|
||||
realPosition: 0,
|
||||
clientId: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
onDrag: (evt, data) => {
|
||||
updateX(data.x);
|
||||
updateY(data.y);
|
||||
render();
|
||||
},
|
||||
onChangeName: (name) => {
|
||||
backend.changeName(name);
|
||||
client.name = name;
|
||||
render();
|
||||
},
|
||||
};
|
||||
|
||||
StartAudioContext(backend._context, '.playit')
|
||||
.then(() => {
|
||||
backend.afterContextStarted();
|
||||
});
|
||||
|
||||
return client;
|
||||
};
|
||||
|
||||
let clients = [];
|
||||
|
||||
const doHello = (password) => {
|
||||
ws.send(JSON.stringify({type: 'hello_admin', password: password }));
|
||||
}
|
||||
|
||||
const updateX = (newX) => {
|
||||
simulator.listenerX = newX;
|
||||
|
||||
context.listener.positionX.value = newX - 250;
|
||||
};
|
||||
|
||||
const updateY = (newY) => {
|
||||
simulator.listenerY = newY;
|
||||
|
||||
context.listener.positionZ.value = 250 - newY;
|
||||
}
|
||||
|
||||
let simulator = {
|
||||
clients: clients,
|
||||
passwordSubmitted: false,
|
||||
password: '',
|
||||
listenerX: 250,
|
||||
listenerY: 250,
|
||||
onDrag: (evt, data) => {
|
||||
updateX(data.x);
|
||||
updateY(data.y);
|
||||
render();
|
||||
},
|
||||
onNewMixerNode: () => {
|
||||
clients.push(createClient());
|
||||
render();
|
||||
},
|
||||
onSubmitPassword: (password) => doHello(password),
|
||||
onChangePassword: (password) => {
|
||||
simulator.password = password;
|
||||
render();
|
||||
},
|
||||
};
|
||||
|
||||
let ws = new WebSocket(`ws://${HOST}:3031`);
|
||||
|
||||
ws.addEventListener('message', evt => {
|
||||
let data = JSON.parse(evt.data);
|
||||
|
||||
if(data.type == 'hello_admin') {
|
||||
simulator.passwordSubmitted = true;
|
||||
render();
|
||||
}
|
||||
});
|
||||
|
||||
return simulator;
|
||||
};
|
||||
|
||||
export default getProps;
|
|
@ -1,231 +1,36 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import registerServiceWorker from './registerServiceWorker';
|
||||
import BufferClientBackend from './buffer-client-backend';
|
||||
import StartAudioContext from 'startaudiocontext';
|
||||
import { HashRouter, Route, Switch } from 'react-router-dom';
|
||||
|
||||
import './index.css';
|
||||
import './range.css';
|
||||
|
||||
import App from './app';
|
||||
import Client from './client';
|
||||
import Admin from './admin';
|
||||
import Simulator from './simulator';
|
||||
import App from './view/app';
|
||||
|
||||
const HOST = window.location.host.split(':')[0];
|
||||
import Client from './view/client';
|
||||
import getClientProps from './controller/client';
|
||||
|
||||
import Admin from './view/admin';
|
||||
import getAdminProps from './controller/admin';
|
||||
|
||||
import Simulator from './view/simulator';
|
||||
import getSimulatorProps from './controller/simulator';
|
||||
|
||||
const render = () => ReactDOM.render(router(), document.getElementById('root'));
|
||||
|
||||
let clientProps, adminProps, simulatorProps;
|
||||
|
||||
const router = () => {
|
||||
return <HashRouter>
|
||||
<Switch>
|
||||
<Route path="/" exact component={App} />
|
||||
<Route path="/client" render={(routeProps) => <Client {...routeProps} {...props.client} />} />
|
||||
<Route path="/admin" render={(routeProps) => <Admin {...routeProps} {...props.admin} />} />
|
||||
<Route path="/simulator" render={(routeProps) => <Simulator {...routeProps} {...props.simulator} />} />
|
||||
<Route path="/client" render={(routeProps) => <Client {...routeProps} {...clientProps || (clientProps = getClientProps(render))} />} />
|
||||
<Route path="/admin" render={(routeProps) => <Admin {...routeProps} {...adminProps || (adminProps = getAdminProps(render))} />} />
|
||||
<Route path="/simulator" render={(routeProps) => <Simulator {...routeProps} {...simulatorProps || (simulatorProps = getSimulatorProps(render))} />} />
|
||||
</Switch>
|
||||
</HashRouter>
|
||||
};
|
||||
|
||||
const getProps = () => {
|
||||
const props = {
|
||||
admin: {},
|
||||
};
|
||||
|
||||
const createClient = () => {
|
||||
const client = {
|
||||
clientName: '',
|
||||
clientId: '',
|
||||
sources: [],
|
||||
realPosition: 0,
|
||||
};
|
||||
|
||||
client.options = {
|
||||
onTimeOffset: timeOffset => {
|
||||
client.realPosition = backend.getRealPosition();
|
||||
render();
|
||||
},
|
||||
onClientId: clientId => {
|
||||
client.clientId = clientId;
|
||||
render();
|
||||
},
|
||||
onSourcesUpdated: sources => {
|
||||
client.sources = sources
|
||||
render();
|
||||
},
|
||||
onSyncPacket: packet => {
|
||||
client.realPosition = backend.getRealPosition();
|
||||
render();
|
||||
}
|
||||
};
|
||||
|
||||
const backend = new BufferClientBackend(client.options);
|
||||
|
||||
client.onChangeName = (clientName) => {
|
||||
backend.changeName(clientName);
|
||||
client.clientName = clientName;
|
||||
render();
|
||||
};
|
||||
|
||||
client.onComponentMount = () => {
|
||||
StartAudioContext(backend._context, '.playit')
|
||||
.then(() => {
|
||||
backend.afterContextStarted();
|
||||
});
|
||||
};
|
||||
|
||||
return client;
|
||||
};
|
||||
|
||||
props.client = createClient();
|
||||
|
||||
const createSimulator = () => {
|
||||
let AudioContext = window.AudioContext || window.webkitAudioContext;
|
||||
let context = new AudioContext();
|
||||
|
||||
const createClient = () => {
|
||||
let mixerNode = null;
|
||||
|
||||
let options = {
|
||||
context: context,
|
||||
onTimeOffset: timeOffset => {
|
||||
client.realPosition = backend.getRealPosition();
|
||||
render();
|
||||
},
|
||||
onClientId: clientId => {
|
||||
client.clientId = clientId;
|
||||
render();
|
||||
},
|
||||
onSourcesUpdated: sources => {
|
||||
client.sources = sources;
|
||||
render();
|
||||
},
|
||||
onSyncPacket: packet => {
|
||||
client.realPosition = backend.getRealPosition();
|
||||
render();
|
||||
},
|
||||
onMixerNodeCreated: node => {
|
||||
mixerNode = node;
|
||||
mixerNode.panningModel = 'HRTF';
|
||||
mixerNode.distanceModel = 'inverse';
|
||||
mixerNode.refDistance = 1;
|
||||
mixerNode.maxDistance = 500;
|
||||
mixerNode.rolloffFactor = 0.01;
|
||||
mixerNode.coneInnerAngle = 360;
|
||||
mixerNode.coneOuterAngle = 0;
|
||||
mixerNode.codeOuterGain = 0;
|
||||
|
||||
client.x = 50;
|
||||
client.y = 50;
|
||||
|
||||
render();
|
||||
},
|
||||
};
|
||||
|
||||
let backend = new BufferClientBackend(options);
|
||||
|
||||
const updateX = (newX) => {
|
||||
client.x = newX;
|
||||
|
||||
mixerNode.positionX.value = newX - 250;
|
||||
};
|
||||
|
||||
const updateY = (newY) => {
|
||||
client.y = newY;
|
||||
|
||||
mixerNode.positionZ.value = 250 - newY;
|
||||
}
|
||||
|
||||
let client = {
|
||||
sources: [],
|
||||
realPosition: 0,
|
||||
clientId: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
onDrag: (evt, data) => {
|
||||
updateX(data.x);
|
||||
updateY(data.y);
|
||||
render();
|
||||
},
|
||||
onChangeName: (name) => {
|
||||
backend.changeName(name);
|
||||
client.name = name;
|
||||
render();
|
||||
},
|
||||
};
|
||||
|
||||
StartAudioContext(backend._context, '.playit')
|
||||
.then(() => {
|
||||
backend.afterContextStarted();
|
||||
});
|
||||
|
||||
return client;
|
||||
};
|
||||
|
||||
let clients = [];
|
||||
|
||||
const doHello = (password) => {
|
||||
ws.send(JSON.stringify({type: 'hello_admin', password: password }));
|
||||
}
|
||||
|
||||
const updateX = (newX) => {
|
||||
simulator.listenerX = newX;
|
||||
|
||||
context.listener.positionX.value = newX - 250;
|
||||
};
|
||||
|
||||
const updateY = (newY) => {
|
||||
simulator.listenerY = newY;
|
||||
|
||||
context.listener.positionZ.value = 250 - newY;
|
||||
}
|
||||
|
||||
let simulator = {
|
||||
clients: clients,
|
||||
passwordSubmitted: false,
|
||||
password: '',
|
||||
listenerX: 250,
|
||||
listenerY: 250,
|
||||
onDrag: (evt, data) => {
|
||||
updateX(data.x);
|
||||
updateY(data.y);
|
||||
render();
|
||||
},
|
||||
onNewMixerNode: () => {
|
||||
clients.push(createClient());
|
||||
render();
|
||||
},
|
||||
onSubmitPassword: (password) => doHello(password),
|
||||
onChangePassword: (password) => {
|
||||
simulator.password = password;
|
||||
render();
|
||||
},
|
||||
};
|
||||
|
||||
let ws = new WebSocket(`ws://${HOST}:3031`);
|
||||
|
||||
ws.addEventListener('message', evt => {
|
||||
let data = JSON.parse(evt.data);
|
||||
|
||||
if(data.type == 'hello_admin') {
|
||||
simulator.passwordSubmitted = true;
|
||||
render();
|
||||
}
|
||||
});
|
||||
|
||||
return simulator;
|
||||
};
|
||||
|
||||
props.simulator = createSimulator();
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
const props = getProps();
|
||||
|
||||
//<Route path="/admin" component={Admin} />
|
||||
//<Route path="/simulator" component={Simulator} />
|
||||
|
||||
render();
|
||||
registerServiceWorker();
|
||||
|
|
85
frontend/src/range.css
Normal file
85
frontend/src/range.css
Normal file
|
@ -0,0 +1,85 @@
|
|||
input[type=range] {
|
||||
-webkit-appearance: none;
|
||||
width: 100%;
|
||||
margin: 9.95px 0;
|
||||
}
|
||||
input[type=range]:focus {
|
||||
outline: none;
|
||||
}
|
||||
input[type=range]::-webkit-slider-runnable-track {
|
||||
width: 100%;
|
||||
height: 10.1px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0.2px 0.2px 1.4px #000000, 0px 0px 0.2px #0d0d0d;
|
||||
background: #a2f9fc;
|
||||
border-radius: 11.8px;
|
||||
border: 0.4px solid #010101;
|
||||
}
|
||||
input[type=range]::-webkit-slider-thumb {
|
||||
box-shadow: 2.6px 2.6px 1.1px rgba(2, 55, 86, 0.85), 0px 0px 2.6px rgba(3, 71, 111, 0.85);
|
||||
border: 3px solid #f17a6b;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
border-radius: 15px;
|
||||
background: #f0957d;
|
||||
cursor: pointer;
|
||||
-webkit-appearance: none;
|
||||
margin-top: -10.35px;
|
||||
}
|
||||
input[type=range]:focus::-webkit-slider-runnable-track {
|
||||
background: #e7fdfe;
|
||||
}
|
||||
input[type=range]::-moz-range-track {
|
||||
width: 100%;
|
||||
height: 10.1px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0.2px 0.2px 1.4px #000000, 0px 0px 0.2px #0d0d0d;
|
||||
background: #a2f9fc;
|
||||
border-radius: 11.8px;
|
||||
border: 0.4px solid #010101;
|
||||
}
|
||||
input[type=range]::-moz-range-thumb {
|
||||
box-shadow: 2.6px 2.6px 1.1px rgba(2, 55, 86, 0.85), 0px 0px 2.6px rgba(3, 71, 111, 0.85);
|
||||
border: 3px solid #f17a6b;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
border-radius: 15px;
|
||||
background: #f0957d;
|
||||
cursor: pointer;
|
||||
}
|
||||
input[type=range]::-ms-track {
|
||||
width: 100%;
|
||||
height: 10.1px;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border-color: transparent;
|
||||
color: transparent;
|
||||
}
|
||||
input[type=range]::-ms-fill-lower {
|
||||
background: #5df5fa;
|
||||
border: 0.4px solid #010101;
|
||||
border-radius: 23.6px;
|
||||
box-shadow: 0.2px 0.2px 1.4px #000000, 0px 0px 0.2px #0d0d0d;
|
||||
}
|
||||
input[type=range]::-ms-fill-upper {
|
||||
background: #a2f9fc;
|
||||
border: 0.4px solid #010101;
|
||||
border-radius: 23.6px;
|
||||
box-shadow: 0.2px 0.2px 1.4px #000000, 0px 0px 0.2px #0d0d0d;
|
||||
}
|
||||
input[type=range]::-ms-thumb {
|
||||
box-shadow: 2.6px 2.6px 1.1px rgba(2, 55, 86, 0.85), 0px 0px 2.6px rgba(3, 71, 111, 0.85);
|
||||
border: 3px solid #f17a6b;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
border-radius: 15px;
|
||||
background: #f0957d;
|
||||
cursor: pointer;
|
||||
height: 10.1px;
|
||||
}
|
||||
input[type=range]:focus::-ms-fill-lower {
|
||||
background: #a2f9fc;
|
||||
}
|
||||
input[type=range]:focus::-ms-fill-upper {
|
||||
background: #e7fdfe;
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
.clients input[type="range"] {
|
||||
display: block;
|
||||
float: left;
|
||||
float: left;
|
||||
width: 5em;
|
||||
height: 10em;
|
||||
-webkit-appearance: slider-vertical;
|
||||
|
@ -52,14 +52,14 @@
|
|||
}
|
||||
|
||||
.knobs .pan {
|
||||
background-image: url('../node_modules/material-design-icons/action/svg/design/ic_pan_tool_24px.svg');
|
||||
background-image: url('../../node_modules/material-design-icons/action/svg/design/ic_pan_tool_24px.svg');
|
||||
background-size: 50%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 40% 35%;
|
||||
}
|
||||
|
||||
.knobs .gain {
|
||||
background-image: url('../node_modules/material-design-icons/av/svg/design/ic_volume_up_24px.svg');
|
||||
background-image: url('../../node_modules/material-design-icons/av/svg/design/ic_volume_up_24px.svg');
|
||||
background-size: 70%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 70% 46%;
|
|
@ -170,6 +170,9 @@ class Admin extends React.Component {
|
|||
<h1>
|
||||
Source administrator
|
||||
</h1>
|
||||
<p>
|
||||
This interface allows you to control the state of connected clients; which tracks are playing as well as the pan and gain for each track.
|
||||
</p>
|
||||
{!this.state.passwordSubmitted
|
||||
? (
|
||||
<div className="password-input">
|
||||
|
@ -180,11 +183,11 @@ class Admin extends React.Component {
|
|||
</div>
|
||||
) : (
|
||||
<div>
|
||||
Current play position: {this.state.syncPacket.timecode / 1000} seconds<br />
|
||||
Current play position: <strong>{this.state.syncPacket.timecode / 1000} seconds</strong><br />
|
||||
<input type="range" min={0} max={370000} step={1} value={this.state.syncPacket.timecode} onChange={ evt => this.adjustTimecode(evt.target.value) } />
|
||||
<div className="track-controls">
|
||||
<button onClick={ () => this.restart() }>
|
||||
Start over
|
||||
Restart song
|
||||
</button>
|
||||
<button onClick={ () => this.muteAll() }>
|
||||
Mute All Clients
|
||||
|
@ -197,6 +200,7 @@ class Admin extends React.Component {
|
|||
{state.clients.length
|
||||
? state.clients.map((client) =>
|
||||
<li key={client.id}>
|
||||
<div style={{position: 'relative', display: 'inline-block' }} className="speaker"></div>
|
||||
Client {client.name || client.id} <br />
|
||||
<br />
|
||||
Sources: <br />
|
4
frontend/src/view/buffer-client.css
Normal file
4
frontend/src/view/buffer-client.css
Normal file
|
@ -0,0 +1,4 @@
|
|||
.enabled-sources li {
|
||||
margin: 0 1em;
|
||||
padding: 0 0.5em;
|
||||
}
|
|
@ -10,6 +10,7 @@ class BufferClient extends React.Component {
|
|||
let enabledSources = this.props.sources.filter(source => source.enabled);
|
||||
return (
|
||||
<div>
|
||||
<div style={{position: 'relative', display: 'inline-block' }} className="speaker"></div>
|
||||
<strong>{"Client " + (this.props.clientName || this.props.clientId)}</strong>
|
||||
<br />
|
||||
<div>
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
.speaker::before {
|
||||
content: "";
|
||||
mask-image: url('../node_modules/material-design-icons/hardware/svg/design/ic_speaker_24px.svg');
|
||||
mask-image: url('../../node_modules/material-design-icons/hardware/svg/design/ic_speaker_24px.svg');
|
||||
mask-repeat: none;
|
||||
mask-size: contain;
|
||||
background-color: #109030;
|
||||
|
@ -37,7 +37,7 @@
|
|||
|
||||
.listener::before {
|
||||
content: "";
|
||||
mask-image: url('../node_modules/material-design-icons/av/svg/design/ic_hearing_24px.svg');
|
||||
mask-image: url('../../node_modules/material-design-icons/av/svg/design/ic_hearing_24px.svg');
|
||||
mask-repeat: none;
|
||||
mask-size: contain;
|
||||
background-color: #F17A6B;
|
|
@ -1,5 +1,4 @@
|
|||
import BufferClient from './buffer-client.js';
|
||||
import BufferClientBackend from './buffer-client-backend.js';
|
||||
import React from 'react';
|
||||
import Draggable from 'react-draggable';
|
||||
|
Loading…
Add table
Reference in a new issue