diff --git a/.eslintignore b/.eslintignore
index 862cc4906..a10aafae3 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -11,4 +11,6 @@ server/build
.cache
locales
src/models
-packages
\ No newline at end of file
+packages
+tempServer
+temp
\ No newline at end of file
diff --git a/.prettierignore b/.prettierignore
index 425f720b5..45c6a2bdd 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -5,4 +5,6 @@ build
dist
locales
coverage
-packages
\ No newline at end of file
+packages
+tempServer
+temp
\ No newline at end of file
diff --git a/public/cardIcons/StationControl.svg b/public/cardIcons/StationControl.svg
new file mode 100644
index 000000000..4ce83ceee
--- /dev/null
+++ b/public/cardIcons/StationControl.svg
@@ -0,0 +1,5 @@
+
+
+
diff --git a/server/bootstrap/apollo.js b/server/bootstrap/apollo.js
index e2d1d5f46..1554dab65 100644
--- a/server/bootstrap/apollo.js
+++ b/server/bootstrap/apollo.js
@@ -27,9 +27,9 @@ export default (app, SERVER_PORT) => {
introspection: true,
playground: true,
uploads: false,
- context: ({req}) => ({
- clientId: req && req.headers.clientid,
- core: req && req.headers.core,
+ context: ({req, connection}) => ({
+ clientId: req?.headers.clientid || connection?.context.clientId,
+ core: req?.headers.core,
}),
};
const apollo = new ApolloServer(graphqlOptions);
diff --git a/server/classes/simulator.js b/server/classes/simulator.js
index ce0cc6ea2..363a4ee09 100644
--- a/server/classes/simulator.js
+++ b/server/classes/simulator.js
@@ -177,6 +177,13 @@ export default class Simulator {
this.midiSets = params.midiSets || [];
this.crackedClients = params.crackedClients || {};
+ // The name of the current card which each
+ // station is on.
+ this.clientCards = params.clientCards || {};
+
+ // Cards assigned to another station from a different station.
+ this.stationAssignedCards = params.stationAssignedCards || {};
+
this.flipped = params.flipped || false;
// Set up the teams
if (params.teams) {
@@ -256,6 +263,28 @@ export default class Simulator {
setLayout(layout) {
this.layout = layout;
}
+ setClientCard(client, cardName) {
+ this.clientCards[client] = cardName;
+ }
+ addStationAssignedCard(station, card) {
+ const stationCards = this.stationAssignedCards[station];
+ this.stationAssignedCards[station] = stationCards
+ ? stationCards.concat(card)
+ : [card];
+ }
+ removeStationAssignedCard(cardName) {
+ const stationEntry = Object.entries(
+ this.stationAssignedCards,
+ ).find(([key, value]) => value.find(c => c.name === cardName));
+ const station = stationEntry?.[0];
+ if (!station) return;
+
+ const stationCards = this.stationAssignedCards[station];
+ this.stationAssignedCards[station] = stationCards
+ ? stationCards.filter(c => c.name !== cardName)
+ : [];
+ }
+
setTimelineStep(step, timelineId) {
if (timelineId) {
this.setAuxTimelineStep(timelineId, step);
diff --git a/server/events/clients.js b/server/events/clients.js
index 25502dd0a..86bc5661b 100644
--- a/server/events/clients.js
+++ b/server/events/clients.js
@@ -96,6 +96,13 @@ App.on("clientSetStation", ({client, stationName, cb}) => {
}
cb && cb();
});
+App.on("clientSetCard", ({id, card}) => {
+ const clientObj = App.clients.find(c => c.id === id);
+ const {simulatorId} = clientObj;
+ const simulator = App.simulators.find(s => s.id === simulatorId);
+ simulator.setClientCard(id, card);
+ pubsub.publish("clientChanged", App.clients);
+});
App.on("clientLogin", ({client, loginName}) => {
const clientObj = App.clients.find(c => c.id === client);
clientObj.login(loginName);
diff --git a/server/events/simulator.js b/server/events/simulator.js
index 9c43230f8..7289a55af 100644
--- a/server/events/simulator.js
+++ b/server/events/simulator.js
@@ -447,3 +447,22 @@ App.on("flipSimulator", ({simulatorId, flip}) => {
pubsub.publish("simulatorsUpdate", App.simulators);
});
+App.on("stationAssignCard", ({simulatorId, assignedToStation, cardName}) => {
+ const simulator = App.simulators.find(s => s.id === simulatorId);
+ const card = simulator.stations
+ .reduce((acc, next) => acc.concat(next.cards), [])
+ .find(c => c.name === cardName);
+
+ // Remove it so it isn't double-assigned
+ simulator.removeStationAssignedCard(cardName);
+
+ simulator.addStationAssignedCard(assignedToStation, card);
+ pubsub.publish("simulatorsUpdate", App.simulators);
+ pubsub.publish("clientChanged", App.clients);
+});
+App.on("stationUnassignCard", ({simulatorId, cardName}) => {
+ const simulator = App.simulators.find(s => s.id === simulatorId);
+ simulator.removeStationAssignedCard(cardName);
+ pubsub.publish("simulatorsUpdate", App.simulators);
+ pubsub.publish("clientChanged", App.clients);
+});
diff --git a/server/typeDefs/clients.js b/server/typeDefs/clients.js
index 41bf9db9d..00fb1a323 100644
--- a/server/typeDefs/clients.js
+++ b/server/typeDefs/clients.js
@@ -26,6 +26,8 @@ const schema = gql`
cracked: Boolean
commandLineOutput: [String]
commandLineFeedback: [CommandLineFeedback]
+ currentCard: Card
+
# Space EdVentures
token: String
email: String
@@ -176,6 +178,7 @@ const schema = gql`
): String
setClientOverlay(id: ID!, overlay: Boolean!): String
clientCrack(id: ID!, crack: Boolean!): String
+ clientSetCard(id: ID!, card: String!): String
setKeypadCode(id: ID!, code: [Int]): String
setKeypadEnteredCode(id: ID!, code: [Int!]): String
@@ -282,6 +285,31 @@ const resolver = {
mobile(client) {
return Boolean(client.mobile);
},
+ currentCard(client) {
+ const simulator = App.simulators.find(s => s.id === client.simulatorId);
+ if (!simulator) return null;
+ const station = StationResolver(client);
+ if (!station) return null;
+
+ // Get any assigned cards
+ const {stationAssignedCards = {}} = simulator;
+ const assignedCardList = Object.values(stationAssignedCards)
+ .reduce((acc, next) => acc.concat(next), [])
+ .map(c => c.name)
+ .filter(Boolean);
+
+ const concatCards = stationAssignedCards[station.name] || [];
+ let cards = station.cards
+ .map(c => ({...c, assigned: assignedCardList.includes(c.name)}))
+ .concat(concatCards.map(c => ({...c, newStation: true})))
+ .filter(Boolean);
+
+ const card = cards
+ .filter(c => !c.hidden)
+ .find(c => c.name === simulator.clientCards[client.id]);
+
+ return card || cards[0];
+ },
},
Keypad: {
giveHints(client) {
diff --git a/server/typeDefs/simulator.js b/server/typeDefs/simulator.js
index 8860c866c..71034510d 100644
--- a/server/typeDefs/simulator.js
+++ b/server/typeDefs/simulator.js
@@ -96,6 +96,14 @@ const schema = gql`
Macro: Station: Unhide Card
"""
unhideSimulatorCard(simulatorId: ID!, cardName: String!): String
+
+ stationAssignCard(
+ simulatorId: ID!
+ assignedToStation: String!
+ cardName: String!
+ ): String
+ stationUnassignCard(simulatorId: ID!, cardName: String!): String
+
"""
Macro: Simulator: Flip Simulator
"""
diff --git a/server/typeDefs/station.js b/server/typeDefs/station.js
index 8f4d5cb4f..722436244 100644
--- a/server/typeDefs/station.js
+++ b/server/typeDefs/station.js
@@ -29,6 +29,16 @@ const schema = gql`
name: String
component: String
hidden: Boolean
+ # Whether this card is assigned to another station
+ assigned: Boolean
+
+ # Whether this card is assigned to a station
+ # it doesn't belong to
+ newStation: Boolean
+ }
+ input CardInput {
+ name: String
+ component: String
}
extend type Simulator {
stationSets: [StationSet]
@@ -120,9 +130,28 @@ const resolver = {
},
},
Station: {
- cards(station, {showHidden}) {
- if (showHidden) return station.cards;
- return station.cards.filter(c => !c.hidden);
+ cards(station, {showHidden}, context) {
+ let simulator = context.simulator;
+ if (!simulator) {
+ const clientObj = App.clients.find(c => c.id === context.clientId);
+ simulator = App.simulators.find(s => s.id === clientObj?.simulatorId);
+ context.simulator = simulator;
+ }
+ const {stationAssignedCards = {}} = simulator || {};
+
+ const assignedCardList = Object.values(stationAssignedCards)
+ .reduce((acc, next) => acc.concat(next), [])
+ .map(c => c.name)
+ .filter(Boolean);
+ const concatCards = stationAssignedCards[station.name] || [];
+ let cards = station.cards
+ .map(c => ({...c, assigned: assignedCardList.includes(c.name)}))
+ .concat(concatCards.map(c => ({...c, newStation: true})))
+ .filter(Boolean);
+
+ // Indicate cards assigned to other stations
+ if (showHidden) return cards;
+ return cards.filter(c => !c.hidden);
},
},
Query: {
diff --git a/src/components/client/Card.js b/src/components/client/Card.js
index e62c6db3b..2af8779a5 100644
--- a/src/components/client/Card.js
+++ b/src/components/client/Card.js
@@ -1,4 +1,4 @@
-import React, {Component} from "react";
+import React from "react";
import Layouts from "../layouts";
import Keyboard from "components/views/Keyboard";
import InterfaceCard from "components/views/Interfaces";
@@ -8,7 +8,7 @@ import SoundPlayer from "./soundPlayer";
import Reset from "./reset";
import TrainingPlayer from "helpers/trainingPlayer";
import {subscribe, publish} from "../../helpers/pubsub";
-import {Mutation} from "react-apollo";
+import {useMutation} from "react-apollo";
import gql from "graphql-tag.macro";
import {playSound} from "../generic/SoundPlayer";
import {randomFromList} from "helpers/randomFromList";
@@ -94,164 +94,121 @@ export function isMedia(src = "") {
];
return extensions.find(e => src.toLowerCase().indexOf(e) > -1);
}
-export default class CardFrame extends Component {
- constructor(props) {
- super(props);
- if (props.test) {
- this.state = {
- card: "Test",
- };
- } else {
- this.state = {
- card: this.props.station.cards && this.props.station.cards[0].name,
- };
- }
+
+const SET_TRAINING_MUTATION = gql`
+ mutation ClientSetTraining($id: ID!, $training: Boolean!) {
+ clientSetTraining(client: $id, training: $training)
+ }
+`;
+
+const CHANGE_CARD_MUTATION = gql`
+ mutation SetCard($id: ID!, $card: String!) {
+ clientSetCard(id: $id, card: $card)
}
- componentDidMount() {
- setTimeout(() => {
- this.setState({visible: true});
- }, 500);
- this.cardChangeRequestSubscription = subscribe(
- "cardChangeRequest",
- payload => {
- // Searching in order of priority, find a matching card by component (card
- // names may have been changed to protect the innocent) then change to that card's name.
- let found = false;
- for (let i = 0; i < payload.changeToCard.length; i++) {
- let matchingCard = this.props.station.cards.find(
- c => c.component === payload.changeToCard[i],
- );
- if (matchingCard) {
- this.changeCard(matchingCard.name);
- found = true;
- break;
- }
+`;
+
+const CardFrame = props => {
+ const {
+ station: {cards, widgets, training: stationTraining},
+ station,
+ simulator,
+ simulator: {soundEffects, caps, flipped, training: simTraining},
+ client,
+ } = props;
+ const cardChanged = React.useRef(false);
+ const [visible, setVisible] = React.useState(false);
+ const cardName = client?.currentCard?.name;
+ React.useEffect(() => {
+ setTimeout(() => setVisible(true), 500);
+ }, []);
+ const [changeCardMutation] = useMutation(CHANGE_CARD_MUTATION);
+ const changeCard = React.useCallback(
+ name => {
+ const card = cards.find(c => c.name === name) ? name : cards?.[0]?.name;
+ if (cardChanged.current || cardName === card) return;
+ cardChanged.current = true;
+ setTimeout(() => (cardChanged.current = false), 500);
+ if (soundEffects?.cardChange) {
+ playSound({
+ url: `/assets${randomFromList(soundEffects.cardChange)}`,
+ });
+ }
+ changeCardMutation({variables: {id: client.id, card: name}});
+ },
+ [cards, changeCardMutation, cardName, client.id, soundEffects],
+ );
+
+ React.useEffect(() => {
+ return subscribe("cardChangeRequest", payload => {
+ // Searching in order of priority, find a matching card by component (card
+ // names may have been changed to protect the innocent) then change to that card's name.
+ let found = false;
+ for (let i = 0; i < payload.changeToCard.length; i++) {
+ let matchingCard = cards.find(
+ c => c.component === payload.changeToCard[i],
+ );
+ if (matchingCard) {
+ changeCard(matchingCard.name);
+ found = true;
+ break;
}
- if (!found) {
- const widgetName = payload.changeToCard.find(c =>
- this.props.station.widgets.includes(c),
- );
- if (widgetName) {
- publish("widgetOpen", widgetName);
- }
+ }
+ if (!found) {
+ const widgetName = payload.changeToCard.find(c => widgets.includes(c));
+ if (widgetName) {
+ publish("widgetOpen", widgetName);
}
- },
- );
- }
- componentWillUnmount() {
- // Unsubscribe
- this.cardChangeRequestSubscription();
- }
- componentDidUpdate(prevProps) {
- if (prevProps.station.name !== this.props.station.name) {
- this.setState({
- card:
- this.props.station.cards &&
- this.props.station.cards[0] &&
- this.props.station.cards[0].name,
- });
- }
- }
- changeCard = name => {
- const card = this.props.station.cards.find(c => c.name === name)
- ? name
- : this.props.station.cards &&
- this.props.station.cards[0] &&
- this.props.station.cards[0].name;
- if (this.cardChanged || this.state.card === card) return;
- this.cardChanged = true;
- setTimeout(() => (this.cardChanged = false), 500);
- if (
- this.props.simulator &&
- this.props.simulator.soundEffects &&
- this.props.simulator.soundEffects.cardChange
- ) {
- playSound({
- url: `/assets${randomFromList(
- this.props.simulator.soundEffects.cardChange,
- )}`,
- });
- }
- this.setState({
- card,
+ }
});
- };
- render() {
- const {
- station: {training: stationTraining},
- simulator: {caps, flipped, training: simTraining},
- client,
- } = this.props;
- const {visible} = this.state;
+ }, [cards, changeCard, widgets]);
- return (
-
-
- {client.cracked &&
}
-
- {this.props.client && (
-
- this.setState({
- card:
- this.props.station.cards &&
- this.props.station.cards[0] &&
- this.props.station.cards[0].name,
- })
- }
- />
- )}
- {simTraining &&
- stationTraining &&
- client.training &&
- isMedia(stationTraining) && (
-
- {action => (
-
- )}
-
- )}
- {client.offlineState !== "blackout" && (
-
+
+ {client.cracked && }
+
+ {client && }
+ {simTraining &&
+ stationTraining &&
+ client.training &&
+ isMedia(stationTraining) && (
+
)}
-
- );
- }
-}
+ {client.offlineState !== "blackout" && (
+
+ )}
+
+ );
+};
+
+export default CardFrame;
diff --git a/src/components/client/index.js b/src/components/client/index.js
index c1dd6cbeb..024c80151 100644
--- a/src/components/client/index.js
+++ b/src/components/client/index.js
@@ -26,6 +26,10 @@ const fragments = {
station {
name
}
+ currentCard {
+ name
+ component
+ }
loginName
loginState
offlineState
diff --git a/src/components/client/simulatorData.js b/src/components/client/simulatorData.js
index e46ea2e0b..b595b7b89 100644
--- a/src/components/client/simulatorData.js
+++ b/src/components/client/simulatorData.js
@@ -42,6 +42,8 @@ const fragments = {
name
component
hidden
+ assigned
+ newStation
}
}
}
diff --git a/src/components/core/CoreComponents.js b/src/components/core/CoreComponents.js
index 285a4d516..d2052924d 100644
--- a/src/components/core/CoreComponents.js
+++ b/src/components/core/CoreComponents.js
@@ -174,7 +174,6 @@ class CoreComponents extends Component {
{simulator && (
changeCard(name)}
alt="Card"
ref={this.card}
- className={`card-icon ${name === currentCard ? "active" : ""}`}
+ className={`card-icon ${name === currentCard ? "active" : ""} ${
+ assigned ? "card-icon-assigned" : ""
+ }`}
src={`/cardIcons/${cardName}.svg`}
draggable="false"
/>
@@ -59,6 +61,7 @@ const CardSwitcher = ({cards, changeCard, currentCard}) => {
changeCard={changeCard}
currentCard={currentCard}
component={card.component}
+ assigned={card.assigned || card.newStation}
/>
))}
diff --git a/src/components/layouts/LayoutBlack/style.scss b/src/components/layouts/LayoutBlack/style.scss
index 92eacfb8c..5261c6261 100644
--- a/src/components/layouts/LayoutBlack/style.scss
+++ b/src/components/layouts/LayoutBlack/style.scss
@@ -28,7 +28,21 @@ $alertp: #7a24cf;
box-sizing: content-box;
transition: background-color 0.2s ease, border-color 0.2s ease;
}
+ img.card-icon-assigned.card-icon-assigned {
+ background-color: gold;
+ border: solid 2px gold;
+
+ &:hover {
+ background-color: darken(gold, 20%);
+ border: solid 2px darken(gold, 20%);
+ }
+ &.active {
+ background-color: lighten(gold, 20%);
+ border: solid 2px lighten(gold, 20%);
+ }
+ }
}
+
@mixin cardSwitcherColors($color) {
.card-switcher {
img {
@@ -63,7 +77,6 @@ $alertp: #7a24cf;
&.alertColorp {
@include cardSwitcherColors($alertp);
}
-
.card-frame {
width: 100vw;
height: 100vh;
diff --git a/src/components/layouts/LayoutCorners/CardSwitcher.js b/src/components/layouts/LayoutCorners/CardSwitcher.js
index 23435f49c..1af20323a 100644
--- a/src/components/layouts/LayoutCorners/CardSwitcher.js
+++ b/src/components/layouts/LayoutCorners/CardSwitcher.js
@@ -17,6 +17,7 @@ class CardSwitcher extends Component {
cardNum={index}
name={card.name}
component={card.component}
+ assigned={card.assigned || card.newStation}
{...this.props}
/>
);
@@ -51,7 +52,11 @@ const CardButton = props => {
src={`/cardIcons/${cardName}.svg`}
draggable="false"
/>
-
+
);
diff --git a/src/components/layouts/LayoutCorners/cardSwitcher.scss b/src/components/layouts/LayoutCorners/cardSwitcher.scss
index a5b50b76f..4a0d5a4a5 100644
--- a/src/components/layouts/LayoutCorners/cardSwitcher.scss
+++ b/src/components/layouts/LayoutCorners/cardSwitcher.scss
@@ -84,9 +84,15 @@
background: #bbb;
border-radius: 50%;
}
+ .card-icon-assigned {
+ background: gold;
+ }
.active .card-icon-background {
background: #fff;
}
+ .active .card-icon-assigned {
+ background: lighten(gold, 10%);
+ }
.card-icon-color {
position: absolute;
top: 4px;
diff --git a/src/components/layouts/LayoutEpsilon/cardSwitcher.js b/src/components/layouts/LayoutEpsilon/cardSwitcher.js
index c4388a748..7f9211e9b 100644
--- a/src/components/layouts/LayoutEpsilon/cardSwitcher.js
+++ b/src/components/layouts/LayoutEpsilon/cardSwitcher.js
@@ -41,7 +41,7 @@ const CardSwitcher = ({cards, cardName, changeCard, hyperCard}) => {
key={`card-${c.name}`}
className={`navigation-card ${
currentCard.component === cardName ? " active" : ""
- }`}
+ } ${c.assigned || c.newStation ? "text-warning" : ""}`}
onClick={() => {
setShown(false);
changeCard(c.name);
diff --git a/src/components/layouts/LayoutLine/cardSwitcher.js b/src/components/layouts/LayoutLine/cardSwitcher.js
index 362d4aca9..544e1f53b 100644
--- a/src/components/layouts/LayoutLine/cardSwitcher.js
+++ b/src/components/layouts/LayoutLine/cardSwitcher.js
@@ -15,7 +15,7 @@ class CardButton extends Component {
});
};
render() {
- const {component, changeCard, currentCard, name} = this.props;
+ const {component, changeCard, currentCard, name, assigned} = this.props;
const cardName = component.match(intregex)
? "Interface"
: component.match(spregex)
@@ -28,7 +28,9 @@ class CardButton extends Component {
onClick={() => changeCard(name)}
alt="Card"
ref={this.card}
- className={`card-icon ${name === currentCard ? "active" : ""}`}
+ className={`card-icon ${name === currentCard ? "active" : ""} ${
+ assigned ? "card-icon-assigned" : ""
+ }`}
src={`/cardIcons/${cardName}.svg`}
draggable="false"
/>
@@ -59,6 +61,7 @@ const CardSwitcher = ({cards, changeCard, currentCard}) => {
changeCard={changeCard}
currentCard={currentCard}
component={card.component}
+ assigned={card.assigned || card.newStation}
/>
))}
diff --git a/src/components/layouts/LayoutLine/style.scss b/src/components/layouts/LayoutLine/style.scss
index cca8b093d..489e7c0e8 100644
--- a/src/components/layouts/LayoutLine/style.scss
+++ b/src/components/layouts/LayoutLine/style.scss
@@ -26,7 +26,21 @@ $alertp: #7a24cf;
box-sizing: content-box;
transition: background-color 0.2s ease, border-color 0.2s ease;
}
+ img.card-icon-assigned.card-icon-assigned {
+ background-color: gold;
+ border: solid 2px gold;
+
+ &:hover {
+ background-color: darken(gold, 20%);
+ border: solid 2px darken(gold, 20%);
+ }
+ &.active {
+ background-color: lighten(gold, 20%);
+ border: solid 2px lighten(gold, 20%);
+ }
+ }
}
+
@mixin cardSwitcherColors($color) {
.card-switcher {
img {
diff --git a/src/components/layouts/LayoutPhoenix/cardSwitcher.js b/src/components/layouts/LayoutPhoenix/cardSwitcher.js
index 932c56962..a8875e751 100644
--- a/src/components/layouts/LayoutPhoenix/cardSwitcher.js
+++ b/src/components/layouts/LayoutPhoenix/cardSwitcher.js
@@ -22,7 +22,9 @@ class CardSwitcher extends Component {
{(cards || []).map(c => (
{
this.setState({shown: false});
changeCard(c.name);
diff --git a/src/components/layouts/cardRenderer.js b/src/components/layouts/cardRenderer.js
index 129727f43..7338d879b 100644
--- a/src/components/layouts/cardRenderer.js
+++ b/src/components/layouts/cardRenderer.js
@@ -3,7 +3,31 @@ import Views from "components/views";
import {Transition} from "react-transition-group";
import {Button} from "helpers/reactstrap";
import ErrorBoundary from "helpers/errorBoundary";
+import gql from "graphql-tag.macro";
+import {useMutation} from "react-apollo";
+const UNASSIGN_CARD = gql`
+ mutation UnassignCard($simulatorId: ID!, $cardName: String!) {
+ stationUnassignCard(simulatorId: $simulatorId, cardName: $cardName)
+ }
+`;
+const CardAssigned = ({cardName, simulatorId}) => {
+ const [unassign] = useMutation(UNASSIGN_CARD, {
+ variables: {simulatorId, cardName},
+ });
+ return (
+
+
Screen Unavailable
+
+ This screen has been assigned to another station and is not currently
+ available. Click the button below to restore control.
+
+
+
+ );
+};
const CardWrapper = ({card}) => {
return (
@@ -31,7 +55,14 @@ const CardWrapper = ({card}) => {
}
>
-
+ {card.assigned ? (
+
+ ) : (
+
+ )}
);
@@ -59,7 +90,7 @@ function renderCards(props) {
Views[clientObj.hypercard]
) {
const Comp = Views[clientObj.hypercard];
- return ;
+ return [{name: "Hypercard", component: Comp, in: true, props}];
}
function getCompName(name) {
if (name.indexOf("interface-id:") > -1) {
diff --git a/src/components/macros/updateViewscreenComponent.js b/src/components/macros/updateViewscreenComponent.js
index 9f07a8fe5..daaaa62d5 100644
--- a/src/components/macros/updateViewscreenComponent.js
+++ b/src/components/macros/updateViewscreenComponent.js
@@ -57,7 +57,6 @@ export default ({updateArgs, args, clients, steps = []}) => {
);
}, [])
.reduce((acc, next) => acc.concat(next), []);
- console.log(clients);
return (
diff --git a/src/components/views/Clients/core.js b/src/components/views/Clients/core.js
index 0d77d21f8..a66051f36 100644
--- a/src/components/views/Clients/core.js
+++ b/src/components/views/Clients/core.js
@@ -14,6 +14,9 @@ const fragment = gql`
station {
name
}
+ currentCard {
+ name
+ }
loginName
hypercard
}
@@ -110,6 +113,7 @@ const ClientCore = ({clients, simulator}) => (
Station |
Name |
+ Card |
Client |
Hypercard |
@@ -127,6 +131,7 @@ const ClientCore = ({clients, simulator}) => (
{c.station.name} |
{c.loginName} |
+ {c.currentCard.name} |
{c.id} |
{" "}
diff --git a/src/components/views/CommDecoding/decodingCanvas.js b/src/components/views/CommDecoding/decodingCanvas.js
index 314132e6d..c6c542a2d 100644
--- a/src/components/views/CommDecoding/decodingCanvas.js
+++ b/src/components/views/CommDecoding/decodingCanvas.js
@@ -45,7 +45,6 @@ export default function DecodingCanvas({
function loop() {
setP(p => {
const newP = p + 20;
- console.log(newP, width * 2);
if (newP < width * 2) {
return newP;
}
diff --git a/src/components/views/Midi/LiveData/lightingIntensity.js b/src/components/views/Midi/LiveData/lightingIntensity.js
index a7df907fb..15b99f490 100644
--- a/src/components/views/Midi/LiveData/lightingIntensity.js
+++ b/src/components/views/Midi/LiveData/lightingIntensity.js
@@ -27,7 +27,7 @@ const LightingIntensity = ({simulatorId, value, setValue}) => {
valueSet.current = true;
setTimeout(() => {
valueSet.current = false;
- }, 1000 / 30);
+ }, 1000 / 10);
setValue(intensity);
}, [intensity, setValue]);
diff --git a/src/components/views/Cards/Cards.stories.js b/src/components/views/StationControl/Cards.stories.js
similarity index 100%
rename from src/components/views/Cards/Cards.stories.js
rename to src/components/views/StationControl/Cards.stories.js
diff --git a/src/components/views/Cards/CardsCore.test.js b/src/components/views/StationControl/CardsCore.test.js
similarity index 100%
rename from src/components/views/Cards/CardsCore.test.js
rename to src/components/views/StationControl/CardsCore.test.js
diff --git a/src/components/views/Cards/core.js b/src/components/views/StationControl/core.js
similarity index 100%
rename from src/components/views/Cards/core.js
rename to src/components/views/StationControl/core.js
diff --git a/src/components/views/StationControl/index.js b/src/components/views/StationControl/index.js
new file mode 100644
index 000000000..c2d4d9833
--- /dev/null
+++ b/src/components/views/StationControl/index.js
@@ -0,0 +1,217 @@
+import React from "react";
+import gql from "graphql-tag.macro";
+import {
+ Container,
+ Row,
+ Col,
+ Button,
+ ListGroup,
+ ListGroupItem,
+} from "reactstrap";
+import useQueryAndSubscription from "helpers/hooks/useQueryAndSubscribe";
+import "./style.scss";
+import {useMutation} from "react-apollo";
+
+const fragment = gql`
+ fragment StationClientData on Client {
+ id
+ station {
+ name
+ cards {
+ name
+ assigned
+ newStation
+ }
+ }
+ currentCard {
+ name
+ }
+ }
+`;
+
+export const STATION_CLIENT_QUERY = gql`
+ query Clients($simulatorId: ID!) {
+ clients(simulatorId: $simulatorId) {
+ ...StationClientData
+ }
+ }
+ ${fragment}
+`;
+export const STATION_CLIENT_SUB = gql`
+ subscription TemplateUpdate($simulatorId: ID!) {
+ clientChanged(simulatorId: $simulatorId) {
+ ...StationClientData
+ }
+ }
+ ${fragment}
+`;
+
+const UNASSIGN_CARD = gql`
+ mutation UnassignCard($simulatorId: ID!, $cardName: String!) {
+ stationUnassignCard(simulatorId: $simulatorId, cardName: $cardName)
+ }
+`;
+
+const ASSIGN_CARD = gql`
+ mutation AssignCard(
+ $simulatorId: ID!
+ $assignedToStation: String!
+ $cardName: String!
+ ) {
+ stationAssignCard(
+ simulatorId: $simulatorId
+ assignedToStation: $assignedToStation
+ cardName: $cardName
+ )
+ }
+`;
+
+const StationControl = props => {
+ const [selectedClient, setSelectedClient] = React.useState(null);
+ const [selectedCard, setSelectedCard] = React.useState(null);
+ const {simulator} = props;
+ const {loading, data} = useQueryAndSubscription(
+ {query: STATION_CLIENT_QUERY, variables: {simulatorId: simulator.id}},
+ {query: STATION_CLIENT_SUB, variables: {simulatorId: simulator.id}},
+ );
+
+ const [removeAssignment] = useMutation(UNASSIGN_CARD, {
+ variables: {simulatorId: simulator.id, cardName: selectedCard},
+ });
+ const [performAssignment] = useMutation(ASSIGN_CARD);
+
+ if (loading || !data) return null;
+ const {clients} = data;
+ if (!clients) return No Clients ;
+ const stationNameCount = {};
+
+ const clientList = clients
+ .map((client, i, arr) => {
+ if (!client.station) return null;
+ const count = stationNameCount?.[client.station.name] + 1 || 1;
+ stationNameCount[client.station.name] = count;
+ let showCount = count > 1;
+ if (count === 1) {
+ if (
+ arr.find(
+ (c, ii) => ii > i && c?.station?.name === client?.station?.name,
+ )
+ ) {
+ showCount = true;
+ }
+ }
+ return {
+ id: client.id,
+ currentCard: client.currentCard.name,
+ label: `${client.station.name}${showCount ? ` (${count})` : ""}`,
+ station: client.station,
+ cards: client.station.cards,
+ };
+ })
+ .filter(Boolean);
+ const stations = clients
+ .reduce((acc, next) => acc.concat(next?.station?.name), [])
+ .filter((a, i, arr) => arr.indexOf(a) === i)
+ .filter(Boolean);
+
+ const client = clients.find(c => c.id === selectedClient);
+ const assignCard = client?.station.cards.find(c => c.name === selectedCard);
+ const assignCardAssignedTo = clientList.find(client =>
+ client.cards.find(
+ card => card.name === assignCard?.name && card.newStation,
+ ),
+ );
+
+ function assignCardToClient(station) {
+ performAssignment({
+ variables: {
+ simulatorId: simulator.id,
+ cardName: selectedCard,
+ assignedToStation: station,
+ },
+ });
+ }
+
+ return (
+
+
+
+ Stations
+
+ {clientList.map(c => (
+ {
+ setSelectedClient(c.id);
+ setSelectedCard(null);
+ }}
+ active={selectedClient === c.id}
+ >
+ {c.label}
+
+ {c.currentCard}
+
+
+ ))}
+
+
+ {client && (
+
+ Screens
+
+ {client.station.cards
+ .filter(c => !c.newStation)
+ .map(c => {
+ const assignedTo =
+ c.assigned &&
+ clientList.find(client =>
+ client.cards.find(
+ card => card.name === c.name && card.newStation,
+ ),
+ );
+ return (
+ setSelectedCard(c.name)}
+ active={c.name === selectedCard}
+ >
+ {c.name}
+
+
+ {assignedTo && `Assigned To: ${assignedTo.label}`}
+
+
+
+ );
+ })}
+
+
+ )}
+ {assignCard && (
+
+ Assign To:
+
+ {stations
+ .filter(station => station !== client.station.name)
+ .map(c => (
+ assignCardToClient(c)}
+ active={assignCardAssignedTo?.station.name === c}
+ >
+ {c}
+
+ ))}
+
+ {assignCardAssignedTo && (
+
+ )}
+
+ )}
+
+
+ );
+};
+export default StationControl;
diff --git a/src/components/views/StationControl/style.scss b/src/components/views/StationControl/style.scss
new file mode 100644
index 000000000..2e4a3ff0d
--- /dev/null
+++ b/src/components/views/StationControl/style.scss
@@ -0,0 +1,17 @@
+.card-stationControl {
+ // Stuff goes here
+ height: 100%;
+ display: block;
+ .row {
+ height: 100%;
+ }
+ .col-sm-4 {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ }
+ .list-group {
+ flex: 1;
+ overflow-y: auto;
+ }
+}
diff --git a/src/components/views/Timeline/TimelineThumbnailCore.test.js b/src/components/views/Timeline/TimelineThumbnailCore.test.js
index 5195fa177..01702d003 100644
--- a/src/components/views/Timeline/TimelineThumbnailCore.test.js
+++ b/src/components/views/Timeline/TimelineThumbnailCore.test.js
@@ -12,7 +12,6 @@ it("should render", async () => {
debug();
await waitForElementToBeRemoved(() => getByText("Loading..."));
await wait();
- console.log(container.innerHTML);
expect(container.innerHTML).toBeTruthy();
expect(container.innerHTML).not.toBe("Error");
});
diff --git a/src/components/views/TorpedoLoading/inventory.js b/src/components/views/TorpedoLoading/inventory.js
index 9d10c3ef5..a7b008abe 100644
--- a/src/components/views/TorpedoLoading/inventory.js
+++ b/src/components/views/TorpedoLoading/inventory.js
@@ -39,9 +39,8 @@ const TorpedoInventory = ({id, inventory, refetchQueries = []}) => {
- console.log(count) ||
- ((parseInt(count, 10) || parseInt(count, 10) === 0) &&
- updateCount({variables: {id, type: "photon", count}}))
+ (parseInt(count, 10) || parseInt(count, 10) === 0) &&
+ updateCount({variables: {id, type: "photon", count}})
}
>
{types.photon}
diff --git a/src/components/views/index.js b/src/components/views/index.js
index ddaa431d6..841c2fb7e 100644
--- a/src/components/views/index.js
+++ b/src/components/views/index.js
@@ -113,6 +113,7 @@ const SpaceEdventuresToken = React.lazy(() => import("./SpaceEdventuresToken"));
const Crm = React.lazy(() => import("./Crm"));
const CrmFighter = React.lazy(() => import("./CrmFighter"));
const Records = React.lazy(() => import("./Records"));
+const StationControl = React.lazy(() => import("./StationControl"));
// Cores
const EngineControlCore = React.lazy(() => import("./EngineControl/core"));
@@ -203,7 +204,7 @@ const SpaceEdventuresTokenCore = React.lazy(() =>
import("./SpaceEdventuresToken/core"),
);
const CrmCore = React.lazy(() => import("./Crm/core"));
-const CardsCore = React.lazy(() => import("./Cards/core"));
+const CardsCore = React.lazy(() => import("./StationControl/core"));
const CommandLineCore = React.lazy(() => import("./CommandLine/core"));
const AuxTimelineCore = React.lazy(() => import("./Timeline/auxTimelineData"));
const LightingCore = React.lazy(() => import("./Lighting"));
@@ -313,6 +314,7 @@ const Views = {
Crm,
CrmFighter,
Records,
+ StationControl,
};
export const Widgets = {
diff --git a/src/helpers/graphqlClient.js b/src/helpers/graphqlClient.js
index 91e185e4a..c261052c2 100644
--- a/src/helpers/graphqlClient.js
+++ b/src/helpers/graphqlClient.js
@@ -18,6 +18,19 @@ const graphqlUrl =
? "/graphql"
: `http://${hostname}:${parseInt(window.location.port || 3000, 10) +
1}/graphql`;
+
+const webSocketLink = new WebSocketLink({
+ uri:
+ process.env.NODE_ENV === "production"
+ ? `ws://${window.location.host}/graphql`
+ : `ws://${hostname}:${parseInt(window.location.port || 3000, 10) +
+ 1}/graphql`,
+ options: {
+ reconnect: true,
+ connectionParams: () => getClientId().then(clientId => ({clientId})),
+ },
+});
+
const wsLink = ApolloLink.from([
onError(args => {
const {response, graphQLErrors, networkError} = args;
@@ -37,16 +50,7 @@ const wsLink = ApolloLink.from([
}
if (response) response.errors = null;
}),
- new WebSocketLink({
- uri:
- process.env.NODE_ENV === "production"
- ? `ws://${window.location.host}/graphql`
- : `ws://${hostname}:${parseInt(window.location.port || 3000, 10) +
- 1}/graphql`,
- options: {
- reconnect: true,
- },
- }),
+ webSocketLink,
]);
const headersMiddleware = setContext((operation, {headers}) => {
diff --git a/src/mocks/cards/Cards.mock.js b/src/mocks/cards/Cards.mock.js
index 7ee39cdef..c0b5070cb 100644
--- a/src/mocks/cards/Cards.mock.js
+++ b/src/mocks/cards/Cards.mock.js
@@ -1,8 +1,8 @@
+import simulators from "../data/simulators";
import {
CARDS_CORE_QUERY,
CARDS_CORE_SUB,
-} from "../../components/views/Cards/core";
-import simulators from "../data/simulators";
+} from "components/views/StationControl/core";
export default [
{
diff --git a/src/mocks/data/clients.js b/src/mocks/data/clients.js
index f7b14c2b9..363eb19fe 100644
--- a/src/mocks/data/clients.js
+++ b/src/mocks/data/clients.js
@@ -18,6 +18,11 @@ export default [
description: null,
__typename: "Station",
},
+ currentCard: {
+ name: "Test",
+ component: "TestCard",
+ __typename: "Card",
+ },
__typename: "Client",
},
{
@@ -39,6 +44,11 @@ export default [
description: "",
__typename: "Station",
},
+ currentCard: {
+ name: "Test",
+ component: "TestCard",
+ __typename: "Card",
+ },
__typename: "Client",
},
];
|