Skip to content

Commit

Permalink
Merge pull request #144 from matthewejones/imagemodel
Browse files Browse the repository at this point in the history
Imagemodel
  • Loading branch information
ChristophNiehoff authored Sep 7, 2021
2 parents 8f1bb3c + 4306a9d commit d5e3ce6
Show file tree
Hide file tree
Showing 17 changed files with 1,634 additions and 1,251 deletions.
Empty file added db/.gitkeep
Empty file.
Empty file added db/images/.gitkeep
Empty file.
4 changes: 2 additions & 2 deletions docker/server.dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ WORKDIR /usr/src/app
RUN chown node:node /usr/src/app
USER node
ENV NODE_ENV production
RUN mkdir -p /usr/src/app/db/images
COPY --chown=node:node --from=builder /usr/src/app/node_modules /usr/src/app/node_modules
COPY --chown=node:node ./src/server /usr/src/app/src/server
COPY --chown=node:node ./src/game /usr/src/app/src/game
COPY --chown=node:node ./src/utils /usr/src/app/src/utils
CMD [ "dumb-init", "node", "-r", "esm", "/usr/src/app/src/server/server.js" ]

CMD [ "dumb-init", "node", "--unhandled-rejections=warn", "-r", "esm", "/usr/src/app/src/server/server.js" ]
3 changes: 2 additions & 1 deletion heroku/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ COPY package*.json ./
RUN npm install
COPY src ./src
COPY public ./public
RUN mkdir -p ./db/images

RUN npm run build

Expand All @@ -18,4 +19,4 @@ COPY heroku/conf/nginx.conf /etc/nginx/http.d/default.conf
RUN cp -a build/. /var/www/html/

# add support for $PORT env variable
CMD sed -i -e 's/$PORT/'"$PORT"'/g' /etc/nginx/http.d/default.conf && /usr/bin/supervisord
CMD sed -i -e 's/$PORT/'"$PORT"'/g' /etc/nginx/http.d/default.conf && /usr/bin/supervisord
1,887 changes: 972 additions & 915 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,24 @@
"@fortawesome/fontawesome-svg-core": "^1.2.36",
"@fortawesome/free-solid-svg-icons": "^5.15.3",
"@fortawesome/react-fontawesome": "^0.1.15",
"@koa/cors": "^3.1.0",
"basic-auth": "^2.0.1",
"boardgame.io": "^0.46.1",
"bootstrap": "^4.6.0",
"cornucopia-cards-modified": "file:cornucopiaCards",
"esm": "^3.2.25",
"jointjs": "^3.4.1",
"koa": "^2.13.1",
"koa-body": "^4.2.0",
"koa-router": "^10.1.1",
"koa-send": "^5.0.1",
"lodash": "^4.17.21",
"node-persist": "^3.1.0",
"react": "^17.0.2",
"react-countdown-circle-timer": "^2.5.4",
"react-dom": "^17.0.2",
"react-helmet": "^6.1.0",
"react-map-interaction": "^2.1.0",
"react-nl2br": "^1.0.2",
"react-router-dom": "^5.3.0",
"react-scripts": "^3.4.4",
Expand All @@ -32,7 +38,7 @@
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"server": "node -r esm src/server/server.js"
"server": "node --unhandled-rejections=warn -r esm src/server/server.js"
},
"eslintConfig": {
"extends": "react-app"
Expand Down
114 changes: 84 additions & 30 deletions src/client/components/board/board.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import Model from '../model/model';
import Deck from '../deck/deck';
import Sidebar from '../sidebar/sidebar';
import Threatbar from '../threatbar/threatbar';
import ImageModel from '../imagemodel/imagemodel';
import Timer from '../timer/timer';
import './board.css';
import request from 'superagent';
import Status from '../status/status';
import { getDealtCard } from '../../../utils/utils';
import { API_PORT } from '../../../utils/constants';
import { API_PORT, MODEL_TYPE_IMAGE } from '../../../utils/constants';
import LicenseAttribution from '../license/licenseAttribution';

class Board extends React.Component {
Expand All @@ -31,7 +32,7 @@ class Board extends React.Component {
}
this.state = {
names,
model: null,
model: null
};
this.apiBase = (process.env.NODE_ENV === 'production') ? '/api' : `${window.location.protocol}//${window.location.hostname}:${API_PORT}`;
}
Expand All @@ -46,42 +47,42 @@ class Board extends React.Component {
})
}

async updateNames() {
try{
const g = await request
.get(`${this.apiBase}/game/${this.props.matchID}/players`)
async apiGetRequest(endpoint) {
// Using superagent makes auth easier but for consistency using fetch may be better
try {
return await request
.get(`${this.apiBase}/game/${this.props.matchID}/${endpoint}`)
.auth(this.props.playerID, this.props.credentials);

g.body.players.forEach(p => {
if (typeof p.name !== 'undefined') {
this.updateName(p.id, p.name);
}
});
} catch (err) {
console.error(err);
}
}

async updateNames() {
const g = await this.apiGetRequest('players');
g.body.players.forEach(p => {
if (typeof p.name !== 'undefined') {
this.updateName(p.id, p.name);
}
});
}

async updateModel() {
try{
const r = await request
.get(`${this.apiBase}/game/${this.props.matchID}/model`)
.auth(this.props.playerID, this.props.credentials);
const r = await this.apiGetRequest('model');

const model = r.body;
const model = r.body;

this.setState({
...this.state,
model,
})
} catch (err) {
console.error(err);
}
this.setState({
...this.state,
model,
})
}

componentDidMount() {
this.updateNames();
this.updateModel();
if (this.props.G.modelType !== MODEL_TYPE_IMAGE) {
this.updateModel();
}
}

render() {
Expand All @@ -91,13 +92,41 @@ class Board extends React.Component {

let dealtCard = getDealtCard(this.props.G);


return (
<div>
<Model model={this.state.model} selectedDiagram={this.props.G.selectedDiagram} selectedComponent={this.props.G.selectedComponent} onSelectDiagram={this.props.moves.selectDiagram} onSelectComponent={this.props.moves.selectComponent} />
{ this.props.G.modelType === MODEL_TYPE_IMAGE
?
<ImageModel
playerID={this.props.playerID}
credentials={this.props.credentials}
matchID={this.props.matchID}
onSelect={() => this.props.moves.selectComponent(0)}
onDeselect={() => this.props.moves.selectComponent('')}
/>
:
<Model
model={this.state.model}
selectedDiagram={this.props.G.selectedDiagram}
selectedComponent={this.props.G.selectedComponent}
onSelectDiagram={this.props.moves.selectDiagram}
onSelectComponent={this.props.moves.selectComponent}
/>
}
<div className="player-wrap">
<div className="playingCardsContainer">
<div className="status-bar">
<Status gameMode={this.props.G.gameMode} playerID={this.props.playerID} G={this.props.G} ctx={this.props.ctx} names={this.state.names} current={current} active={active} dealtCard={dealtCard} isInThreatStage={isInThreatStage} />
<Status
G={this.props.G}
ctx={this.props.ctx}
gameMode={this.props.G.gameMode}
playerID={this.props.playerID}
names={this.state.names}
current={current}
active={active}
dealtCard={dealtCard}
isInThreatStage={isInThreatStage}
/>
</div>
<Deck
cards={this.props.G.players[this.props.playerID]}
Expand All @@ -113,9 +142,34 @@ class Board extends React.Component {
/>
</div>
</div>
<Sidebar playerID={this.props.playerID} matchID={this.props.matchID} G={this.props.G} ctx={this.props.ctx} moves={this.props.moves} isInThreatStage={isInThreatStage} current={current} active={active} names={this.state.names} secret={this.props.credentials}/>
<Timer active={isInThreatStage} targetTime={this.props.G.turnFinishTargetTime} duration={this.props.G.turnDuration} key={isInThreatStage} />
<Threatbar playerID={this.props.playerID} model={this.state.model} names={this.state.names} G={this.props.G} ctx={this.props.ctx} moves={this.props.moves} active={active} isInThreatStage={isInThreatStage} />
<Sidebar
G={this.props.G}
ctx={this.props.ctx}
playerID={this.props.playerID}
matchID={this.props.matchID}
moves={this.props.moves}
isInThreatStage={isInThreatStage}
current={current}
active={active}
names={this.state.names}
secret={this.props.credentials}
/>
<Timer
active={isInThreatStage}
targetTime={this.props.G.turnFinishTargetTime}
duration={parseInt(this.props.G.turnDuration)}
key={isInThreatStage}
/>
<Threatbar
G={this.props.G}
ctx={this.props.ctx}
playerID={this.props.playerID}
model={this.state.model}
names={this.state.names}
moves={this.props.moves}
active={active}
isInThreatStage={isInThreatStage}
/>
<LicenseAttribution gameMode={this.props.G.gameMode} />
</div>
);
Expand Down
9 changes: 9 additions & 0 deletions src/client/components/imagemodel/imagemodel.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
div.model {
height: 100%;
}

div.deselect {
position: absolute;
height: 100%;
width: 100%;
}
105 changes: 105 additions & 0 deletions src/client/components/imagemodel/imagemodel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import React from 'react';
import PropTypes from 'prop-types';
import './imagemodel.css';
import { API_PORT } from '../../../utils/constants';
import { MapInteractionCSS } from 'react-map-interaction';
import { asyncSetTimeout } from '../../../utils/utils';


class ImageModel extends React.Component {
static propTypes = {
playerID: PropTypes.any,
credentials: PropTypes.string,
matchID: PropTypes.string,
onSelect: PropTypes.func,
onDeselect: PropTypes.func
}

constructor(props) {
super(props);
this.auth = this.auth.bind(this);
this.updateImage = this.updateImage.bind(this);
this.onError = this.onError.bind(this);
this.handleSelect = this.handleSelect.bind(this);
this.handleDeselect = this.handleDeselect.bind(this);

// maybe this is better defined in constants (also used by downloadbutton.js)
this.apiBase = (process.env.NODE_ENV === 'production') ? '/api' : `${window.location.protocol}//${window.location.hostname}:${API_PORT}`;
this.state = {
imgSrc: undefined
};
}

auth() {
const user = this.props.playerID;
const pass = this.props.credentials;
return {'Authorization': 'Basic ' + Buffer.from(user + ':' + pass).toString('base64')};
}

async updateImage() {
const res = await fetch(
`${this.apiBase}/game/${this.props.matchID}/image`,
{ headers: this.auth() }
);
if (!res.ok) {
throw Error(res.statusText);
}

const blob = await res.blob();
const url = URL.createObjectURL(blob);

this.setState({
...this.state,
imgSrc: url
})
}

async onError() {
// Try again
try {
await asyncSetTimeout(this.updateImage, 5000)
} catch {
// If updateimage fails it won't then call this again
// Handle this here.
this.onError();
}
}

handleSelect(e) {
// Call callback if it's not a drag
if(!e.defaultPrevented) {
this.props.onSelect();
}
e.stopPropagation();
}

handleDeselect(e) {
if(!e.defaultPrevented) {
this.props.onDeselect();
}
}

async componentDidMount() {
try {
await this.updateImage();
} catch (err) {
console.error(err, err.stack);
}
}

render() {
return (
<div className="model" onClick={this.handleDeselect}>
{
this.state.imgSrc && (
<MapInteractionCSS>
<img src={this.state.imgSrc} alt="Architectural Model" onError={this.onError} onClick={this.handleSelect} />
</MapInteractionCSS>
)
}
</div>
);
}
}

export default ImageModel;
Loading

0 comments on commit d5e3ce6

Please sign in to comment.