1
0
mirror of https://github.com/danbee/chess synced 2025-03-04 08:39:06 +00:00
This commit is contained in:
Daniel Barber 2023-11-21 19:15:37 +00:00 committed by GitHub
commit 6d43c157ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 1286 additions and 1897 deletions

View File

@ -1,4 +1,4 @@
elixir 1.14.1
python 3.9.1
nodejs 16.14.0
erlang 24.2.1
erlang 26.1.1

View File

@ -57,6 +57,7 @@
border-radius: 4%;
margin: 0.5px;
position: relative;
flex-grow: 1;
// This is to ensure the squares can be clicked on in PhantomJS
// TODO: Figure out why we need this
@ -103,7 +104,7 @@
@each $colour in $colours {
@each $piece in $pieces {
&.square--#{$colour}.square--#{$piece}::before {
background-image: url("../static/images/#{$piece}_#{$colour}.svg");
background-image: url("/images/#{$piece}_#{$colour}.svg");
}
}
}

View File

@ -1,28 +1,22 @@
"use strict";
import "@babel/polyfill";
import "phoenix_html";
import React from "react";
import ReactDOM from "react-dom";
import { createStore } from "redux";
import { Socket } from "phoenix";
import { LiveSocket } from "phoenix_live_view";
import Game from "./components/game";
import OpponentFinder from "./components/opponent-finder";
import chessBoardReducer from "./reducers/chess-board";
const csrfToken = document
.querySelector("meta[name='csrf-token']")
.getAttribute("content");
const store = createStore(chessBoardReducer);
const liveSocket = new LiveSocket(
"/live",
Socket,
{params: {_csrf_token: csrfToken}}
);
const gameContainer = document.getElementById("game");
liveSocket.connect();
if (gameContainer != undefined) {
const gameId = gameContainer.getAttribute("data-game-id");
// liveSocket.enableDebug();
ReactDOM.render(<Game store={store} gameId={gameId} />, gameContainer);
}
const opponentFinderContainer = document.getElementById("opponent-finder");
if (opponentFinderContainer != undefined) {
ReactDOM.render(<OpponentFinder store={store} />, opponentFinderContainer);
}
window.liveSocket = liveSocket;

View File

@ -1,99 +0,0 @@
import React from "react";
import _ from "lodash";
import classNames from "classnames";
import API from "../services/api";
import { selectPiece } from "../store/actions";
class ChessBoardSquare extends React.Component {
constructor(props) {
super(props);
}
get squareCoords() {
return [this.props.file, this.props.rank];
}
get squareId() {
return `f${this.props.file}-r${this.props.rank}`;
}
get squareClass() {
if (this.props.piece == undefined) {
return classNames(
"square",
{ "square--available": this.isAvailableSquare() }
);
} else {
return classNames(
"square",
`square--${this.props.piece.type}`,
`square--${this.props.piece.colour}`,
{ "square--selected": this.isSelectedSquare() },
{ "square--available": this.isAvailableSquare() }
);
}
}
selectSquare() {
const { piece, store, channel } = this.props;
const { gameId, selectedSquare, player } = store.getState();
if (this.moveIsValid(selectedSquare)) {
channel.sendMove({
from: selectedSquare,
to: this.squareCoords,
});
} else if (selectedSquare != null) {
store.dispatch(selectPiece(null));
} else if (this.playerCanSelectPiece(player, piece)) {
store.dispatch(selectPiece(this.squareCoords));
channel.getAvailableMoves(this.squareCoords);
}
}
moveIsValid(selectedSquare) {
return selectedSquare != null &&
!this.isSelectedSquare() &&
this.isAvailableSquare();
}
playerCanSelectPiece(player, piece) {
const { store } = this.props;
const { turn } = store.getState();
return piece !== undefined &&
piece.colour == player &&
player == turn;
}
isSelectedSquare() {
const { store } = this.props;
if (store.getState().selectedSquare == null) {
return false;
} else {
return _.isEqual(this.squareCoords, store.getState().selectedSquare);
}
}
isAvailableSquare() {
const { store } = this.props;
const availableMoves = store.getState().availableMoves;
return _.find(availableMoves, (square) => {
return square.join() == this.squareCoords.join();
});
}
render() {
return <div
id={this.squareId}
className={this.squareClass}
onClick={this.selectSquare.bind(this)}
/>;
}
}
export default ChessBoardSquare;

View File

@ -1,114 +0,0 @@
import React from "react";
import _ from "lodash";
import { connect } from "react-redux";
import classNames from "classnames";
import ChessBoardSquare from "./chess-board-square";
import RankLabels from "./rank-labels";
import FileLabels from "./file-labels";
import GameState from "./game-state";
class ChessBoard extends React.Component {
componentWillMount() {
const { gameId, store } = this.props;
}
get turn() {
const { store } = this.props;
return store.getState().turn;
}
get board() {
const { store } = this.props;
return store.getState().board;
}
get player() {
const { store } = this.props;
return store.getState().player;
}
get gameState() {
const { store } = this.props;
return store.getState().state;
}
files(rank) {
switch (this.player) {
case "white":
return Object.keys(rank).sort();
case "black":
return Object.keys(rank)
.sort()
.reverse();
}
}
ranks() {
const board = this.board;
const player = this.player;
switch (player) {
case "white":
return Object.keys(board).reverse();
case "black":
return Object.keys(board);
}
}
renderSquares() {
const { store, channel } = this.props;
return _.map(this.ranks(), (rankId) => {
const rank = this.board[rankId];
return _.map(this.files(rank), (fileId) => {
return (
<ChessBoardSquare
file={fileId}
key={fileId}
rank={rankId}
piece={this.board[rankId][fileId]}
store={store}
channel={channel}
/>
);
});
});
}
get boardClass() {
const turn = this.turn;
const player = this.player;
return classNames(
"board",
`board--${turn}-to-move`,
`board--player-is-${player}`
);
}
render() {
return (
<div className={this.boardClass}>
<RankLabels />
<FileLabels />
<div className="board__body">
{this.renderSquares()}
</div>
<GameState gameState={this.gameState} />
</div>
);
}
}
function mapStateToProps(state) {
return {
board: state.board,
selectedSquare: state.selectedSquare,
};
}
export default connect(mapStateToProps)(ChessBoard);

View File

@ -1,22 +0,0 @@
import React from "react";
import _ from "lodash";
const FILE_LABELS = ["a", "b", "c", "d", "e", "f", "g", "h"];
const renderFileLabels = () => {
return _.map(FILE_LABELS, (fileLabel) => {
return (
<div key={fileLabel} className="board__label">{fileLabel}</div>
);
});
};
const FileLabels = () => {
return (
<div className="board__file-labels">
{renderFileLabels()}
</div>
);
};
export default FileLabels;

View File

@ -1,41 +0,0 @@
import React from "react";
import { connect } from "react-redux";
const renderStatus = status => {
if (status == "viewing") {
return (
<img
className="game-info__opponent-status"
src="/images/eye-open.svg"
alt="viewing"
/>
);
} else {
return (
<img
className="game-info__opponent-status"
src="/images/eye-closed.svg"
alt="offline"
/>
);
}
};
const GameInfo = props => {
return (
<div className="game-info">
<p>
Playing {props.opponent} {renderStatus(props.opponentStatus)}
</p>
</div>
);
};
const mapStateToProps = state => {
return {
opponent: state.opponent,
opponentStatus: state.opponentStatus,
};
};
export default connect(mapStateToProps)(GameInfo);

View File

@ -1,26 +0,0 @@
import React from "react";
import classNames from "classnames";
const GAME_STATES = {
checkmate: "Checkmate!",
stalemate: "Stalemate",
check: "Check",
};
const friendlyGameState = (state) => {
return GAME_STATES[state];
};
const gameStateClass = (state) => {
return classNames("game-state", `game-state--${state}`);
};
const GameState = (props) => {
return (
<div className={gameStateClass(props.gameState)}>
{friendlyGameState(props.gameState)}
</div>
);
};
export default GameState;

View File

@ -1,65 +0,0 @@
"use strict";
import "phoenix_html";
import React from "react";
import ReactDOM from "react-dom";
import Channel from "../services/channel";
import Notifications from "../services/notifications";
import { setGameId } from "../store/actions";
import Listeners from "../store/listeners";
import ChessBoard from "./chess-board";
import MoveList from "./move-list";
import GameInfo from "./game-info";
const notifications = new Notifications();
class Game extends React.Component {
componentWillMount() {
const { gameId, store } = this.props;
store.dispatch(setGameId(gameId));
this.listeners = new Listeners(store);
this.listeners.setListeners(notifications);
this.channel = new Channel(store, gameId);
}
componentWillUnmount() {
this.channel.leave();
}
get moves() {
const { store } = this.props;
return store.getState().moves;
}
get opponent() {
const { store } = this.props;
return store.getState().opponent;
}
render() {
const { store, gameId } = this.props;
return (
<div className="game-grid">
<ChessBoard
gameId={gameId}
store={store}
channel={this.channel}
/>
<GameInfo store={store} />
<MoveList store={store} />
</div>
);
}
}
export default Game;

View File

@ -1,78 +0,0 @@
import React from "react";
import _ from "lodash";
import { connect } from "react-redux";
import classNames from "classnames";
const pieceToNotation = (piece) => {
const pieces = {
pawn: "",
knight: "N",
bishop: "B",
rook: "R",
queen: "Q",
king: "K",
};
return pieces[piece.type];
};
const moveClass = (move) => {
return classNames("move-list__move", "move-list__move--" + move.piece.colour);
};
const renderMove = (move) => {
if (move != undefined) {
return (
<td className={moveClass(move)}>
{pieceToNotation(move.piece)}
{move.piece_captured ? "x" : ""}
{move.to}
</td>
);
}
};
const renderMoves = (moves) => {
let lineNumber = 1;
return _.map(moves, (move) => {
return (
<tr key={move[0].id}>
<th scope="row" className="move-list__line-number">{lineNumber++}.</th>
{renderMove(move[0])}
{renderMove(move[1])}
</tr>
);
});
};
const MoveList = (props) => {
return (
<div className="move-list">
<table className="table table--condensed">
<thead>
<tr>
<th className="move-list__line-number">
<span className="visually-hidden">Move no.</span>
</th>
<th className="move-list__header--white">White</th>
<th className="move-list__header--black">Black</th>
</tr>
</thead>
<tbody>
{renderMoves(props.moves)}
</tbody>
</table>
</div>
);
};
const mapStateToProps = (state) => {
return {
moves: state.moves,
};
};
export default connect(mapStateToProps)(MoveList);

View File

@ -1,168 +0,0 @@
"use strict";
import "phoenix_html";
import React from "react";
import ReactDOM from "react-dom";
import _ from "lodash";
import API from "../services/api";
class OpponentFinder extends React.Component {
constructor(props) {
super(props);
this.state = {
queryString: "",
foundOpponents: [],
selectedOpponent: "",
selectedOpponentId: "",
focusedOpponent: null,
};
this.opponentResults = [];
this.debouncedSearch = _.debounce(this.search.bind(this), 250);
}
componentDidUpdate() {
if (this.state.focusedOpponent !== null) {
this.opponentResults[this.state.focusedOpponent].focus();
}
}
search() {
if (this.state.queryString != "") {
API.findOpponent(this.state.queryString)
.then((response) => {
this.setState({ foundOpponents: response.data.opponents });
});
} else {
this.setState({ foundOpponents: [] });
}
}
handleChange(event) {
this.setState({ queryString: event.target.value });
this.debouncedSearch();
}
handleFocus(event) {
if (this.state.selectedOpponent) {
this.setState({ queryString: "" });
}
}
handleBlur(event) {
if (this.state.selectedOpponent) {
this.setState({ queryString: this.state.selectedOpponent.name });
}
}
handleKeyPress(event) {
if (this.isKeyingDown(event)) { this.focusNextOpponent(); }
if (this.isKeyingUp(event)) { this.focusPreviousOpponent(); }
}
isKeyingDown(event) {
return event.key === "ArrowDown" || (event.key === "Tab" && !event.shiftKey);
}
isKeyingUp(event) {
return event.key === "ArrowUp" || (event.key === "Tab" && event.shiftKey);
}
focusNextOpponent() {
if (this.state.focusedOpponent === null) {
this.setState({ focusedOpponent: 0 });
} else if (this.state.focusedOpponent < this.state.foundOpponents.length - 1) {
this.setState({ focusedOpponent: this.state.focusedOpponent + 1 });
}
}
focusPreviousOpponent() {
if (this.state.focusedOpponent > 0) {
this.setState({ focusedOpponent: this.state.focusedOpponent - 1 });
} else {
this.setState({ focusedOpponent: null });
this.queryStringInput.select();
}
}
selectOpponent(event) {
event.preventDefault();
const selectedOpponentId = event.target.attributes["data-id"].value;
const selectedOpponent = _.find(this.state.foundOpponents, (opponent) => {
return opponent.id == selectedOpponentId;
});
this.setState({
selectedOpponentId,
selectedOpponent,
foundOpponents: [],
queryString: selectedOpponent.name,
focusedOpponent: null,
});
}
renderOpponents() {
return _.map(this.state.foundOpponents, (opponent, index) => {
return (
<li key={opponent.id}>
<a
className="opponent-finder__result-item"
ref={(link) => { this.opponentResults[index] = link; }}
data-id={opponent.id}
href="#"
onClick={this.selectOpponent.bind(this)}
>{opponent.name}</a>
</li>
);
});
}
renderOpponentsResult() {
if (this.state.foundOpponents.length) {
return (
<ul className="opponent-finder__result">
{this.renderOpponents()}
</ul>
);
}
}
render() {
const { store, gameId } = this.props;
return (
<div
className="form-field opponent-finder"
onKeyUp={this.handleKeyPress.bind(this)}
>
<label htmlFor="query-string">Find opponent</label>
<input
id="query-string"
ref={(input) => { this.queryStringInput = input; }}
className="search-input"
name="q"
value={this.state.queryString}
onChange={this.handleChange.bind(this)}
onFocus={this.handleFocus.bind(this)}
onBlur={this.handleBlur.bind(this)}
type="text"
autoComplete="off"
/>
<input
name="game[opponent_id]"
type="hidden"
value={this.state.selectedOpponentId}
/>
{this.renderOpponentsResult()}
</div>
);
}
}
export default OpponentFinder;

View File

@ -1,22 +0,0 @@
import React from "react";
import _ from "lodash";
const RANK_LABELS = [1, 2, 3, 4, 5, 6, 7, 8];
const renderRankLabels = () => {
return _.map(RANK_LABELS, (rankLabel) => {
return (
<div key={rankLabel} className="board__label">{rankLabel}</div>
);
});
};
const RankLabels = () => {
return (
<div className="board__rank-labels">
{renderRankLabels()}
</div>
);
};
export default RankLabels;

View File

@ -1,56 +0,0 @@
import Immutable from "immutable";
import defaultState from "../store/default-state";
const chessBoardReducer = (state = defaultState, action) => {
switch (action.type) {
case "SET_USER_ID":
return Immutable.fromJS(state)
.set("userId", action.user_id)
.toJS();
case "SET_PLAYERS":
return Immutable.fromJS(state)
.set("player", action.player)
.set("playerId", action.player_id)
.set("opponent", action.opponent)
.set("opponentId", action.opponent_id)
.toJS();
case "SET_GAME":
return Immutable.fromJS(state)
.set("board", action.board)
.set("turn", action.turn)
.set("state", action.state)
.set("selectedSquare", null)
.set("availableMoves", [])
.set("moves", action.moves)
.toJS();
case "SET_AVAILABLE_MOVES":
return Immutable.fromJS(state)
.set("availableMoves", action.availableMoves)
.toJS();
case "SET_GAME_ID":
return Immutable.fromJS(state)
.set("gameId", action.gameId)
.toJS();
case "SELECT_PIECE":
return Immutable.fromJS(state)
.set("selectedSquare", action.coords)
.set("availableMoves", [])
.toJS();
case "SET_OPPONENT_STATUS":
return Immutable.fromJS(state)
.set("opponentStatus", action.opponentStatus)
.toJS();
default:
return state;
}
};
export default chessBoardReducer;

View File

@ -1,9 +0,0 @@
import axios from "axios";
const API = {
findOpponent: (queryString) => {
return axios.get("/api/opponents", { params: { q: queryString } });
},
};
export default API;

View File

@ -1,85 +0,0 @@
import _ from "lodash";
import socket from "./socket";
import Presences from "./presences";
import {
setUserId,
setPlayers,
setGame,
setAvailableMoves,
setOpponentStatus,
} from "../store/actions";
class Channel {
constructor(store, gameId) {
this.store = store;
this.channel = socket.channel(`game:${gameId}`, {});
this.presences = new Presences();
this.join();
this.subscribe();
}
get opponentId() {
return this.store.getState().opponentId;
}
join() {
this.channel
.join()
.receive("error", resp => {
console.log("Unable to join", resp);
});
}
leave() {
this.channel.leave();
}
subscribe() {
this.channel.on("game:update", this.updateGame.bind(this));
this.channel.on("presence_state", data => {
this.presences.syncState(data);
this.setOpponentStatus();
});
this.channel.on("presence_diff", data => {
this.presences.syncDiff(data);
this.setOpponentStatus();
});
}
updateGame(data) {
if (data.player != undefined) {
this.store.dispatch(setUserId(data.user_id));
this.store.dispatch(setPlayers(data));
}
this.store.dispatch(setGame(data));
}
setOpponentStatus() {
this.store.dispatch(
setOpponentStatus(
this.presences.opponentOnline(this.opponentId) ? "viewing" : "offline"
)
);
}
getAvailableMoves(square) {
this.channel
.push("game:get_available_moves", { square })
.receive("ok", data => {
this.store.dispatch(setAvailableMoves(data.moves));
});
}
sendMove(move) {
this.channel
.push("game:move", move)
.receive("error", resp => {
alert(resp.message);
});
}
}
export default Channel;

View File

@ -1,26 +0,0 @@
class Notifications {
constructor() {
if (this.notifications_available) {
Notification.requestPermission();
}
}
notifyTurn(player) {
this.notify({
body: "Your opponent has moved.",
icon: `/images/king_${player}.svg`,
});
}
notify(options) {
if (this.notifications_available && !document.hasFocus()) {
new Notification("Chess", options);
}
}
get notifications_available() {
window.Notification != undefined;
}
}
export default Notifications;

View File

@ -1,24 +0,0 @@
import _ from "lodash";
import { Presence } from "phoenix";
class Presences {
constructor() {
this.presences = {};
}
syncState(data) {
this.presences = Presence.syncState(this.presences, data);
}
syncDiff(data) {
this.presences = Presence.syncDiff(this.presences, data);
}
opponentOnline(opponentId) {
return _.find(this.presences, (value, id) => {
return parseInt(id) == opponentId;
});
}
}
export default Presences;

View File

@ -1,58 +0,0 @@
// NOTE: The contents of this file will only be executed if
// you uncomment its entry in "web/static/js/app.js".
// To use Phoenix channels, the first step is to import Socket
// and connect at the socket path in "lib/my_app/endpoint.ex":
import { Socket } from "phoenix";
let socket = new Socket("/socket", { params: { token: window.userToken } });
// When you connect, you'll often need to authenticate the client.
// For example, imagine you have an authentication plug, `MyAuth`,
// which authenticates the session and assigns a `:current_user`.
// If the current user exists you can assign the user's token in
// the connection for use in the layout.
//
// In your "web/router.ex":
//
// pipeline :browser do
// ...
// plug MyAuth
// plug :put_user_token
// end
//
// defp put_user_token(conn, _) do
// if current_user = conn.assigns[:current_user] do
// token = Phoenix.Token.sign(conn, "user socket", current_user.id)
// assign(conn, :user_token, token)
// else
// conn
// end
// end
//
// Now you need to pass this token to JavaScript. You can do so
// inside a script tag in "web/templates/layout/app.html.eex":
//
// <script>window.userToken = "<%= assigns[:user_token] %>";</script>
//
// You will need to verify the user token in the "connect/2" function
// in "web/channels/user_socket.ex":
//
// def connect(%{"token" => token}, socket) do
// # max_age: 1209600 is equivalent to two weeks in seconds
// case Phoenix.Token.verify(socket, "user socket", token, max_age: 1209600) do
// {:ok, user_id} ->
// {:ok, assign(socket, :user, user_id)}
// {:error, reason} ->
// :error
// end
// end
//
// Finally, pass the token on connect as below. Or remove it
// from connect if you don't care about authentication.
if (window.userToken) {
socket.connect();
}
export default socket;

View File

@ -1,62 +0,0 @@
const SET_USER_ID = "SET_USER_ID";
const SET_PLAYERS = "SET_PLAYERS";
const SET_GAME = "SET_GAME";
const SET_AVAILABLE_MOVES = "SET_AVAILABLE_MOVES";
const SET_GAME_ID = "SET_GAME_ID";
const SELECT_PIECE = "SELECT_PIECE";
const SET_OPPONENT_STATUS = "SET_OPPONENT_STATUS";
export const setUserId = (user_id) => {
return {
type: SET_USER_ID,
user_id,
};
};
export const setPlayers = (data) => {
return {
type: SET_PLAYERS,
player: data.player,
player_id: data.player_id,
opponent: data.opponent,
opponent_id: data.opponent_id,
};
};
export const setGame = (data) => {
return {
type: SET_GAME,
board: data.board,
turn: data.turn,
moves: data.moves,
state: data.state,
};
};
export const setAvailableMoves = (availableMoves) => {
return {
type: SET_AVAILABLE_MOVES,
availableMoves,
};
};
export const setGameId = (gameId) => {
return {
type: SET_GAME_ID,
gameId,
};
};
export const selectPiece = (coords) => {
return {
type: SELECT_PIECE,
coords,
};
};
export const setOpponentStatus = (opponentStatus) => {
return {
type: SET_OPPONENT_STATUS,
opponentStatus,
};
};

View File

@ -1,30 +0,0 @@
const defaultState = {
selectedSquare: null,
playerId: null,
opponentId: null,
player: null,
opponent: null,
turn: null,
state: null,
opponentStatus: "offline",
availableMoves: [],
moves: [],
board: {
8: { a: null, b: null, c: null, d: null, e: null, f: null, g: null, h: null },
7: { a: null, b: null, c: null, d: null, e: null, f: null, g: null, h: null },
6: { a: null, b: null, c: null, d: null, e: null, f: null, g: null, h: null },
5: { a: null, b: null, c: null, d: null, e: null, f: null, g: null, h: null },
4: { a: null, b: null, c: null, d: null, e: null, f: null, g: null, h: null },
3: { a: null, b: null, c: null, d: null, e: null, f: null, g: null, h: null },
2: { a: null, b: null, c: null, d: null, e: null, f: null, g: null, h: null },
1: { a: null, b: null, c: null, d: null, e: null, f: null, g: null, h: null },
},
};
export default defaultState;

View File

@ -1,27 +0,0 @@
import watch from "redux-watch";
class Listeners {
constructor(store) {
this.store = store;
}
setListeners(notifications) {
this.notifications = notifications;
let watcher = watch(this.store.getState, "turn");
this.store.subscribe(
watcher(this.notifyTurn.bind(this))
);
}
notifyTurn(newVal, oldVal) {
const player = this.store.getState().player;
if (oldVal != null && newVal == player) {
this.notifications.notifyTurn(player);
}
}
};
export default Listeners;

578
assets/package-lock.json generated Normal file
View File

@ -0,0 +1,578 @@
{
"name": "assets",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"license": "MIT",
"dependencies": {
"esbuild": "^0.19",
"phoenix": "file:../deps/phoenix",
"phoenix_html": "file:../deps/phoenix_html",
"phoenix_live_view": "file:../deps/phoenix_live_view"
},
"devDependencies": {}
},
"../deps/phoenix": {
"version": "1.7.9",
"license": "MIT"
},
"../deps/phoenix_html": {
"version": "3.3.3"
},
"../deps/phoenix_live_view": {
"version": "0.20.1",
"license": "MIT"
},
"node_modules/@esbuild/android-arm": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.4.tgz",
"integrity": "sha512-uBIbiYMeSsy2U0XQoOGVVcpIktjLMEKa7ryz2RLr7L/vTnANNEsPVAh4xOv7ondGz6ac1zVb0F8Jx20rQikffQ==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.4.tgz",
"integrity": "sha512-mRsi2vJsk4Bx/AFsNBqOH2fqedxn5L/moT58xgg51DjX1la64Z3Npicut2VbhvDFO26qjWtPMsVxCd80YTFVeg==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.4.tgz",
"integrity": "sha512-4iPufZ1TMOD3oBlGFqHXBpa3KFT46aLl6Vy7gwed0ZSYgHaZ/mihbYb4t7Z9etjkC9Al3ZYIoOaHrU60gcMy7g==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.4.tgz",
"integrity": "sha512-Lviw8EzxsVQKpbS+rSt6/6zjn9ashUZ7Tbuvc2YENgRl0yZTktGlachZ9KMJUsVjZEGFVu336kl5lBgDN6PmpA==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.4.tgz",
"integrity": "sha512-YHbSFlLgDwglFn0lAO3Zsdrife9jcQXQhgRp77YiTDja23FrC2uwnhXMNkAucthsf+Psr7sTwYEryxz6FPAVqw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.4.tgz",
"integrity": "sha512-vz59ijyrTG22Hshaj620e5yhs2dU1WJy723ofc+KUgxVCM6zxQESmWdMuVmUzxtGqtj5heHyB44PjV/HKsEmuQ==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.4.tgz",
"integrity": "sha512-3sRbQ6W5kAiVQRBWREGJNd1YE7OgzS0AmOGjDmX/qZZecq8NFlQsQH0IfXjjmD0XtUYqr64e0EKNFjMUlPL3Cw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.4.tgz",
"integrity": "sha512-z/4ArqOo9EImzTi4b6Vq+pthLnepFzJ92BnofU1jgNlcVb+UqynVFdoXMCFreTK7FdhqAzH0vmdwW5373Hm9pg==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.4.tgz",
"integrity": "sha512-ZWmWORaPbsPwmyu7eIEATFlaqm0QGt+joRE9sKcnVUG3oBbr/KYdNE2TnkzdQwX6EDRdg/x8Q4EZQTXoClUqqA==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.4.tgz",
"integrity": "sha512-EGc4vYM7i1GRUIMqRZNCTzJh25MHePYsnQfKDexD8uPTCm9mK56NIL04LUfX2aaJ+C9vyEp2fJ7jbqFEYgO9lQ==",
"cpu": [
"ia32"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.4.tgz",
"integrity": "sha512-WVhIKO26kmm8lPmNrUikxSpXcgd6HDog0cx12BUfA2PkmURHSgx9G6vA19lrlQOMw+UjMZ+l3PpbtzffCxFDRg==",
"cpu": [
"loong64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.4.tgz",
"integrity": "sha512-keYY+Hlj5w86hNp5JJPuZNbvW4jql7c1eXdBUHIJGTeN/+0QFutU3GrS+c27L+NTmzi73yhtojHk+lr2+502Mw==",
"cpu": [
"mips64el"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.4.tgz",
"integrity": "sha512-tQ92n0WMXyEsCH4m32S21fND8VxNiVazUbU4IUGVXQpWiaAxOBvtOtbEt3cXIV3GEBydYsY8pyeRMJx9kn3rvw==",
"cpu": [
"ppc64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.4.tgz",
"integrity": "sha512-tRRBey6fG9tqGH6V75xH3lFPpj9E8BH+N+zjSUCnFOX93kEzqS0WdyJHkta/mmJHn7MBaa++9P4ARiU4ykjhig==",
"cpu": [
"riscv64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.4.tgz",
"integrity": "sha512-152aLpQqKZYhThiJ+uAM4PcuLCAOxDsCekIbnGzPKVBRUDlgaaAfaUl5NYkB1hgY6WN4sPkejxKlANgVcGl9Qg==",
"cpu": [
"s390x"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.4.tgz",
"integrity": "sha512-Mi4aNA3rz1BNFtB7aGadMD0MavmzuuXNTaYL6/uiYIs08U7YMPETpgNn5oue3ICr+inKwItOwSsJDYkrE9ekVg==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.4.tgz",
"integrity": "sha512-9+Wxx1i5N/CYo505CTT7T+ix4lVzEdz0uCoYGxM5JDVlP2YdDC1Bdz+Khv6IbqmisT0Si928eAxbmGkcbiuM/A==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.4.tgz",
"integrity": "sha512-MFsHleM5/rWRW9EivFssop+OulYVUoVcqkyOkjiynKBCGBj9Lihl7kh9IzrreDyXa4sNkquei5/DTP4uCk25xw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.4.tgz",
"integrity": "sha512-6Xq8SpK46yLvrGxjp6HftkDwPP49puU4OF0hEL4dTxqCbfx09LyrbUj/D7tmIRMj5D5FCUPksBbxyQhp8tmHzw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.4.tgz",
"integrity": "sha512-PkIl7Jq4mP6ke7QKwyg4fD4Xvn8PXisagV/+HntWoDEdmerB2LTukRZg728Yd1Fj+LuEX75t/hKXE2Ppk8Hh1w==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.4.tgz",
"integrity": "sha512-ga676Hnvw7/ycdKB53qPusvsKdwrWzEyJ+AtItHGoARszIqvjffTwaaW3b2L6l90i7MO9i+dlAW415INuRhSGg==",
"cpu": [
"ia32"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.4.tgz",
"integrity": "sha512-HP0GDNla1T3ZL8Ko/SHAS2GgtjOg+VmWnnYLhuTksr++EnduYB0f3Y2LzHsUwb2iQ13JGoY6G3R8h6Du/WG6uA==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.4.tgz",
"integrity": "sha512-x7jL0tbRRpv4QUyuDMjONtWFciygUxWaUM1kMX2zWxI0X2YWOt7MSA0g4UdeSiHM8fcYVzpQhKYOycZwxTdZkA==",
"hasInstallScript": true,
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=12"
},
"optionalDependencies": {
"@esbuild/android-arm": "0.19.4",
"@esbuild/android-arm64": "0.19.4",
"@esbuild/android-x64": "0.19.4",
"@esbuild/darwin-arm64": "0.19.4",
"@esbuild/darwin-x64": "0.19.4",
"@esbuild/freebsd-arm64": "0.19.4",
"@esbuild/freebsd-x64": "0.19.4",
"@esbuild/linux-arm": "0.19.4",
"@esbuild/linux-arm64": "0.19.4",
"@esbuild/linux-ia32": "0.19.4",
"@esbuild/linux-loong64": "0.19.4",
"@esbuild/linux-mips64el": "0.19.4",
"@esbuild/linux-ppc64": "0.19.4",
"@esbuild/linux-riscv64": "0.19.4",
"@esbuild/linux-s390x": "0.19.4",
"@esbuild/linux-x64": "0.19.4",
"@esbuild/netbsd-x64": "0.19.4",
"@esbuild/openbsd-x64": "0.19.4",
"@esbuild/sunos-x64": "0.19.4",
"@esbuild/win32-arm64": "0.19.4",
"@esbuild/win32-ia32": "0.19.4",
"@esbuild/win32-x64": "0.19.4"
}
},
"node_modules/phoenix": {
"resolved": "../deps/phoenix",
"link": true
},
"node_modules/phoenix_html": {
"resolved": "../deps/phoenix_html",
"link": true
},
"node_modules/phoenix_live_view": {
"resolved": "../deps/phoenix_live_view",
"link": true
}
},
"dependencies": {
"@esbuild/android-arm": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.4.tgz",
"integrity": "sha512-uBIbiYMeSsy2U0XQoOGVVcpIktjLMEKa7ryz2RLr7L/vTnANNEsPVAh4xOv7ondGz6ac1zVb0F8Jx20rQikffQ==",
"optional": true
},
"@esbuild/android-arm64": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.4.tgz",
"integrity": "sha512-mRsi2vJsk4Bx/AFsNBqOH2fqedxn5L/moT58xgg51DjX1la64Z3Npicut2VbhvDFO26qjWtPMsVxCd80YTFVeg==",
"optional": true
},
"@esbuild/android-x64": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.4.tgz",
"integrity": "sha512-4iPufZ1TMOD3oBlGFqHXBpa3KFT46aLl6Vy7gwed0ZSYgHaZ/mihbYb4t7Z9etjkC9Al3ZYIoOaHrU60gcMy7g==",
"optional": true
},
"@esbuild/darwin-arm64": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.4.tgz",
"integrity": "sha512-Lviw8EzxsVQKpbS+rSt6/6zjn9ashUZ7Tbuvc2YENgRl0yZTktGlachZ9KMJUsVjZEGFVu336kl5lBgDN6PmpA==",
"optional": true
},
"@esbuild/darwin-x64": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.4.tgz",
"integrity": "sha512-YHbSFlLgDwglFn0lAO3Zsdrife9jcQXQhgRp77YiTDja23FrC2uwnhXMNkAucthsf+Psr7sTwYEryxz6FPAVqw==",
"optional": true
},
"@esbuild/freebsd-arm64": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.4.tgz",
"integrity": "sha512-vz59ijyrTG22Hshaj620e5yhs2dU1WJy723ofc+KUgxVCM6zxQESmWdMuVmUzxtGqtj5heHyB44PjV/HKsEmuQ==",
"optional": true
},
"@esbuild/freebsd-x64": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.4.tgz",
"integrity": "sha512-3sRbQ6W5kAiVQRBWREGJNd1YE7OgzS0AmOGjDmX/qZZecq8NFlQsQH0IfXjjmD0XtUYqr64e0EKNFjMUlPL3Cw==",
"optional": true
},
"@esbuild/linux-arm": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.4.tgz",
"integrity": "sha512-z/4ArqOo9EImzTi4b6Vq+pthLnepFzJ92BnofU1jgNlcVb+UqynVFdoXMCFreTK7FdhqAzH0vmdwW5373Hm9pg==",
"optional": true
},
"@esbuild/linux-arm64": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.4.tgz",
"integrity": "sha512-ZWmWORaPbsPwmyu7eIEATFlaqm0QGt+joRE9sKcnVUG3oBbr/KYdNE2TnkzdQwX6EDRdg/x8Q4EZQTXoClUqqA==",
"optional": true
},
"@esbuild/linux-ia32": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.4.tgz",
"integrity": "sha512-EGc4vYM7i1GRUIMqRZNCTzJh25MHePYsnQfKDexD8uPTCm9mK56NIL04LUfX2aaJ+C9vyEp2fJ7jbqFEYgO9lQ==",
"optional": true
},
"@esbuild/linux-loong64": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.4.tgz",
"integrity": "sha512-WVhIKO26kmm8lPmNrUikxSpXcgd6HDog0cx12BUfA2PkmURHSgx9G6vA19lrlQOMw+UjMZ+l3PpbtzffCxFDRg==",
"optional": true
},
"@esbuild/linux-mips64el": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.4.tgz",
"integrity": "sha512-keYY+Hlj5w86hNp5JJPuZNbvW4jql7c1eXdBUHIJGTeN/+0QFutU3GrS+c27L+NTmzi73yhtojHk+lr2+502Mw==",
"optional": true
},
"@esbuild/linux-ppc64": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.4.tgz",
"integrity": "sha512-tQ92n0WMXyEsCH4m32S21fND8VxNiVazUbU4IUGVXQpWiaAxOBvtOtbEt3cXIV3GEBydYsY8pyeRMJx9kn3rvw==",
"optional": true
},
"@esbuild/linux-riscv64": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.4.tgz",
"integrity": "sha512-tRRBey6fG9tqGH6V75xH3lFPpj9E8BH+N+zjSUCnFOX93kEzqS0WdyJHkta/mmJHn7MBaa++9P4ARiU4ykjhig==",
"optional": true
},
"@esbuild/linux-s390x": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.4.tgz",
"integrity": "sha512-152aLpQqKZYhThiJ+uAM4PcuLCAOxDsCekIbnGzPKVBRUDlgaaAfaUl5NYkB1hgY6WN4sPkejxKlANgVcGl9Qg==",
"optional": true
},
"@esbuild/linux-x64": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.4.tgz",
"integrity": "sha512-Mi4aNA3rz1BNFtB7aGadMD0MavmzuuXNTaYL6/uiYIs08U7YMPETpgNn5oue3ICr+inKwItOwSsJDYkrE9ekVg==",
"optional": true
},
"@esbuild/netbsd-x64": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.4.tgz",
"integrity": "sha512-9+Wxx1i5N/CYo505CTT7T+ix4lVzEdz0uCoYGxM5JDVlP2YdDC1Bdz+Khv6IbqmisT0Si928eAxbmGkcbiuM/A==",
"optional": true
},
"@esbuild/openbsd-x64": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.4.tgz",
"integrity": "sha512-MFsHleM5/rWRW9EivFssop+OulYVUoVcqkyOkjiynKBCGBj9Lihl7kh9IzrreDyXa4sNkquei5/DTP4uCk25xw==",
"optional": true
},
"@esbuild/sunos-x64": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.4.tgz",
"integrity": "sha512-6Xq8SpK46yLvrGxjp6HftkDwPP49puU4OF0hEL4dTxqCbfx09LyrbUj/D7tmIRMj5D5FCUPksBbxyQhp8tmHzw==",
"optional": true
},
"@esbuild/win32-arm64": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.4.tgz",
"integrity": "sha512-PkIl7Jq4mP6ke7QKwyg4fD4Xvn8PXisagV/+HntWoDEdmerB2LTukRZg728Yd1Fj+LuEX75t/hKXE2Ppk8Hh1w==",
"optional": true
},
"@esbuild/win32-ia32": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.4.tgz",
"integrity": "sha512-ga676Hnvw7/ycdKB53qPusvsKdwrWzEyJ+AtItHGoARszIqvjffTwaaW3b2L6l90i7MO9i+dlAW415INuRhSGg==",
"optional": true
},
"@esbuild/win32-x64": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.4.tgz",
"integrity": "sha512-HP0GDNla1T3ZL8Ko/SHAS2GgtjOg+VmWnnYLhuTksr++EnduYB0f3Y2LzHsUwb2iQ13JGoY6G3R8h6Du/WG6uA==",
"optional": true
},
"esbuild": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.4.tgz",
"integrity": "sha512-x7jL0tbRRpv4QUyuDMjONtWFciygUxWaUM1kMX2zWxI0X2YWOt7MSA0g4UdeSiHM8fcYVzpQhKYOycZwxTdZkA==",
"requires": {
"@esbuild/android-arm": "0.19.4",
"@esbuild/android-arm64": "0.19.4",
"@esbuild/android-x64": "0.19.4",
"@esbuild/darwin-arm64": "0.19.4",
"@esbuild/darwin-x64": "0.19.4",
"@esbuild/freebsd-arm64": "0.19.4",
"@esbuild/freebsd-x64": "0.19.4",
"@esbuild/linux-arm": "0.19.4",
"@esbuild/linux-arm64": "0.19.4",
"@esbuild/linux-ia32": "0.19.4",
"@esbuild/linux-loong64": "0.19.4",
"@esbuild/linux-mips64el": "0.19.4",
"@esbuild/linux-ppc64": "0.19.4",
"@esbuild/linux-riscv64": "0.19.4",
"@esbuild/linux-s390x": "0.19.4",
"@esbuild/linux-x64": "0.19.4",
"@esbuild/netbsd-x64": "0.19.4",
"@esbuild/openbsd-x64": "0.19.4",
"@esbuild/sunos-x64": "0.19.4",
"@esbuild/win32-arm64": "0.19.4",
"@esbuild/win32-ia32": "0.19.4",
"@esbuild/win32-x64": "0.19.4"
}
},
"phoenix": {
"version": "file:../deps/phoenix"
},
"phoenix_html": {
"version": "file:../deps/phoenix_html"
},
"phoenix_live_view": {
"version": "file:../deps/phoenix_live_view"
}
}
}

View File

@ -6,24 +6,10 @@
"deploy": "cd .. && mix assets.deploy && rm -f _build/esbuild"
},
"dependencies": {
"@babel/polyfill": "^7.12.1",
"axios": "^0.21.0",
"classnames": "^2.3.1",
"esbuild": "^0.14.21",
"gettext.js": "^1.0.0",
"immutable": "^3.8.2",
"lodash": "^4.17.21",
"phoenix": "file:../deps/phoenix",
"phoenix_html": "file:../deps/phoenix_html",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-redux": "^7.2.4",
"redux": "^4.1.0",
"redux-watch": "^1.2.0",
"url-loader": "^4.1.1"
"phoenix_live_view": "file:../deps/phoenix_live_view"
},
"devDependencies": {
"css-loader": "^5.2.6",
"mini-css-extract-plugin": "^1.6.0"
}
}

View File

@ -1,645 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@babel/polyfill@^7.12.1":
version "7.12.1"
resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.12.1.tgz#1f2d6371d1261bbd961f3c5d5909150e12d0bd96"
integrity sha512-X0pi0V6gxLi6lFZpGmeNa4zxtwEmCs42isWLNjZZDE0Y8yVfgu0T2OAHlzBbdYlqbW/YXVvoBHpATEM+goCj8g==
dependencies:
core-js "^2.6.5"
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.12.1", "@babel/runtime@^7.9.2":
version "7.14.6"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.6.tgz#535203bc0892efc7dec60bdc27b2ecf6e409062d"
integrity sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==
dependencies:
regenerator-runtime "^0.13.4"
"@types/hoist-non-react-statics@^3.3.0":
version "3.3.1"
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
dependencies:
"@types/react" "*"
hoist-non-react-statics "^3.3.0"
"@types/json-schema@^7.0.6":
version "7.0.7"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad"
integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==
"@types/prop-types@*":
version "15.7.3"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
"@types/react-redux@^7.1.16":
version "7.1.16"
resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.16.tgz#0fbd04c2500c12105494c83d4a3e45c084e3cb21"
integrity sha512-f/FKzIrZwZk7YEO9E1yoxIuDNRiDducxkFlkw/GNMGEnK9n4K8wJzlJBghpSuOVDgEUHoDkDF7Gi9lHNQR4siw==
dependencies:
"@types/hoist-non-react-statics" "^3.3.0"
"@types/react" "*"
hoist-non-react-statics "^3.3.0"
redux "^4.0.0"
"@types/react@*":
version "17.0.11"
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.11.tgz#67fcd0ddbf5a0b083a0f94e926c7d63f3b836451"
integrity sha512-yFRQbD+whVonItSk7ZzP/L+gPTJVBkL/7shLEF+i9GC/1cV3JmUxEQz6+9ylhUpWSDuqo1N9qEvqS6vTj4USUA==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/scheduler@*":
version "0.16.1"
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.1.tgz#18845205e86ff0038517aab7a18a62a6b9f71275"
integrity sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA==
ajv-keywords@^3.5.2:
version "3.5.2"
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
ajv@^6.12.5:
version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
dependencies:
fast-deep-equal "^3.1.1"
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
ansi-styles@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.0.0.tgz#cb102df1c56f5123eab8b67cd7b98027a0279178"
integrity sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg=
axios@^0.21.0:
version "0.21.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
dependencies:
follow-redirects "^1.10.0"
big.js@^5.2.2:
version "5.2.2"
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
chalk@~0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.4.0.tgz#5199a3ddcd0c1efe23bc08c1b027b06176e0c64f"
integrity sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=
dependencies:
ansi-styles "~1.0.0"
has-color "~0.1.0"
strip-ansi "~0.1.0"
classnames@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e"
integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==
colorette@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94"
integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==
core-js@^2.6.5:
version "2.6.12"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec"
integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
css-loader@^5.2.6:
version "5.2.6"
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.2.6.tgz#c3c82ab77fea1f360e587d871a6811f4450cc8d1"
integrity sha512-0wyN5vXMQZu6BvjbrPdUJvkCzGEO24HC7IS7nW4llc6BBFC+zwR9CKtYGv63Puzsg10L/o12inMY5/2ByzfD6w==
dependencies:
icss-utils "^5.1.0"
loader-utils "^2.0.0"
postcss "^8.2.15"
postcss-modules-extract-imports "^3.0.0"
postcss-modules-local-by-default "^4.0.0"
postcss-modules-scope "^3.0.0"
postcss-modules-values "^4.0.0"
postcss-value-parser "^4.1.0"
schema-utils "^3.0.0"
semver "^7.3.5"
cssesc@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
csstype@^3.0.2:
version "3.0.8"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.8.tgz#d2266a792729fb227cd216fb572f43728e1ad340"
integrity sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw==
emojis-list@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78"
integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==
encoding@^0.1.11:
version "0.1.13"
resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9"
integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==
dependencies:
iconv-lite "^0.6.2"
esbuild-android-arm64@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.21.tgz#8842d0c3b7c81fbe2dc46ddb416ffd6eb822184b"
integrity sha512-Bqgld1TY0wZv8TqiQmVxQFgYzz8ZmyzT7clXBDZFkOOdRybzsnj8AZuK1pwcLVA7Ya6XncHgJqIao7NFd3s0RQ==
esbuild-darwin-64@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.21.tgz#ec7df02ad88ecf7f8fc23a3ed7917e07dea0c9c9"
integrity sha512-j+Eg+e13djzyYINVvAbOo2/zvZ2DivuJJTaBrJnJHSD7kUNuGHRkHoSfFjbI80KHkn091w350wdmXDNSgRjfYQ==
esbuild-darwin-arm64@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.21.tgz#0c2a977edec1ef54097ee56a911518c820d4e5e4"
integrity sha512-nDNTKWDPI0RuoPj5BhcSB2z5EmZJJAyRtZLIjyXSqSpAyoB8eyAKXl4lB8U2P78Fnh4Lh1le/fmpewXE04JhBQ==
esbuild-freebsd-64@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.21.tgz#f5b5fc1d031286c3a0949d1bda7db774b7d0404e"
integrity sha512-zIurkCHXhxELiDZtLGiexi8t8onQc2LtuE+S7457H/pP0g0MLRKMrsn/IN4LDkNe6lvBjuoZZi2OfelOHn831g==
esbuild-freebsd-arm64@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.21.tgz#a05cab908013e4992b31a675850b8c44eb468c0c"
integrity sha512-wdxMmkJfbwcN+q85MpeUEamVZ40FNsBa9mPq8tAszDn8TRT2HoJvVRADPIIBa9SWWwlDChIMjkDKAnS3KS/sPA==
esbuild-linux-32@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.21.tgz#638d244cc58b951f447addb4bade628d126ef84b"
integrity sha512-fmxvyzOPPh2xiEHojpCeIQP6pXcoKsWbz3ryDDIKLOsk4xp3GbpHIEAWP0xTeuhEbendmvBDVKbAVv3PnODXLg==
esbuild-linux-64@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.21.tgz#8eb634abee928be7e35b985fafbfef2f2e31397f"
integrity sha512-edZyNOv1ql+kpmlzdqzzDjRQYls+tSyi4QFi+PdBhATJFUqHsnNELWA9vMSzAaInPOEaVUTA5Ml28XFChcy4DA==
esbuild-linux-arm64@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.21.tgz#e05599ea6253b58394157da162d856f3ead62f9e"
integrity sha512-t5qxRkq4zdQC0zXpzSB2bTtfLgOvR0C6BXYaRE/6/k8/4SrkZcTZBeNu+xGvwCU4b5dU9ST9pwIWkK6T1grS8g==
esbuild-linux-arm@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.21.tgz#1ae1078231cf689d3ba894a32d3723c0be9b91fd"
integrity sha512-aSU5pUueK6afqmLQsbU+QcFBT62L+4G9hHMJDHWfxgid6hzhSmfRH9U/f+ymvxsSTr/HFRU4y7ox8ZyhlVl98w==
esbuild-linux-mips64le@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.21.tgz#f05be62d126764e99b37edcac5bb49b78c7a8890"
integrity sha512-jLZLQGCNlUsmIHtGqNvBs3zN+7a4D9ckf0JZ+jQTwHdZJ1SgV9mAjbB980OFo66LoY+WeM7t3WEnq3FjI1zw4A==
esbuild-linux-ppc64le@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.21.tgz#592c98d82dad7982268ef8deed858c4566f07ab1"
integrity sha512-4TWxpK391en2UBUw6GSrukToTDu6lL9vkm3Ll40HrI08WG3qcnJu7bl8e1+GzelDsiw1QmfAY/nNvJ6iaHRpCQ==
esbuild-linux-riscv64@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.21.tgz#0db7bd6f10d8f9afea973a7d6bf87b449b864b7b"
integrity sha512-fElngqOaOfTsF+u+oetDLHsPG74vB2ZaGZUqmGefAJn3a5z9Z2pNa4WpVbbKgHpaAAy5tWM1m1sbGohj6Ki6+Q==
esbuild-linux-s390x@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.21.tgz#254a9354d34c9d1b41a3e21d2ec9269cbbb2c5df"
integrity sha512-brleZ6R5fYv0qQ7ZBwenQmP6i9TdvJCB092c/3D3pTLQHBGHJb5zWgKxOeS7bdHzmLy6a6W7GbFk6QKpjyD6QA==
esbuild-netbsd-64@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.21.tgz#4cb783d060b02bf3b897a9a12cce2b3b547726f8"
integrity sha512-nCEgsLCQ8RoFWVV8pVI+kX66ICwbPP/M9vEa0NJGIEB/Vs5sVGMqkf67oln90XNSkbc0bPBDuo4G6FxlF7PN8g==
esbuild-openbsd-64@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.21.tgz#f886b93feefddbe573528fa4b421c9c6e2bc969b"
integrity sha512-h9zLMyVD0T73MDTVYIb/qUTokwI6EJH9O6wESuTNq6+XpMSr6C5aYZ4fvFKdNELW+Xsod+yDS2hV2JTUAbFrLA==
esbuild-sunos-64@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.21.tgz#3829e4d57d4cb6950837fe90b0b67cdfb37cf13a"
integrity sha512-Kl+7Cot32qd9oqpLdB1tEGXEkjBlijrIxMJ0+vlDFaqsODutif25on0IZlFxEBtL2Gosd4p5WCV1U7UskNQfXA==
esbuild-windows-32@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.21.tgz#b858a22d1a82e53cdc59310cd56294133f7a95e7"
integrity sha512-V7vnTq67xPBUCk/9UtlolmQ798Ecjdr1ZoI1vcSgw7M82aSSt0eZdP6bh5KAFZU8pxDcx3qoHyWQfHYr11f22A==
esbuild-windows-64@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.21.tgz#7bb5a027d5720cf9caf18a4bedd11327208f1f12"
integrity sha512-kDgHjKOHwjfJDCyRGELzVxiP/RBJBTA+wyspf78MTTJQkyPuxH2vChReNdWc+dU2S4gIZFHMdP1Qrl/k22ZmaA==
esbuild-windows-arm64@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.21.tgz#25df54521ad602c826b262ea2e7cc1fe80f5c2f5"
integrity sha512-8Sbo0zpzgwWrwjQYLmHF78f7E2xg5Ve63bjB2ng3V2aManilnnTGaliq2snYg+NOX60+hEvJHRdVnuIAHW0lVw==
esbuild@^0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.21.tgz#b3e05f900f1c4394f596d60d63d9816468f0f671"
integrity sha512-7WEoNMBJdLN993dr9h0CpFHPRc3yFZD+EAVY9lg6syJJ12gc5fHq8d75QRExuhnMkT2DaRiIKFThRvDWP+fO+A==
optionalDependencies:
esbuild-android-arm64 "0.14.21"
esbuild-darwin-64 "0.14.21"
esbuild-darwin-arm64 "0.14.21"
esbuild-freebsd-64 "0.14.21"
esbuild-freebsd-arm64 "0.14.21"
esbuild-linux-32 "0.14.21"
esbuild-linux-64 "0.14.21"
esbuild-linux-arm "0.14.21"
esbuild-linux-arm64 "0.14.21"
esbuild-linux-mips64le "0.14.21"
esbuild-linux-ppc64le "0.14.21"
esbuild-linux-riscv64 "0.14.21"
esbuild-linux-s390x "0.14.21"
esbuild-netbsd-64 "0.14.21"
esbuild-openbsd-64 "0.14.21"
esbuild-sunos-64 "0.14.21"
esbuild-windows-32 "0.14.21"
esbuild-windows-64 "0.14.21"
esbuild-windows-arm64 "0.14.21"
fast-deep-equal@^3.1.1:
version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
fast-json-stable-stringify@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
follow-redirects@^1.10.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.1.tgz#d9114ded0a1cfdd334e164e6662ad02bfd91ff43"
integrity sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==
gettext-parser@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/gettext-parser/-/gettext-parser-1.1.0.tgz#2c5a6638d893934b9b55037d0ad82cb7004b2679"
integrity sha1-LFpmONiTk0ubVQN9CtgstwBLJnk=
dependencies:
encoding "^0.1.11"
gettext.js@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/gettext.js/-/gettext.js-1.0.0.tgz#7fefb01512c134759c51166ab4d3db26a585ae1a"
integrity sha512-cpnxNL5C9SlD7ms/NSCuGsQdaVQmwCYn9MILWpYjSPMAkX4aD/5/qC+QgH4GCRY0OMEcSiVBsqgWMEoTcETggQ==
dependencies:
po2json "^0.4.0"
has-color@~0.1.0:
version "0.1.7"
resolved "https://registry.yarnpkg.com/has-color/-/has-color-0.1.7.tgz#67144a5260c34fc3cca677d041daf52fe7b78b2f"
integrity sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=
hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
dependencies:
react-is "^16.7.0"
iconv-lite@^0.6.2:
version "0.6.3"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
dependencies:
safer-buffer ">= 2.1.2 < 3.0.0"
icss-utils@^5.0.0, icss-utils@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae"
integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==
immutable@^3.8.2:
version "3.8.2"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3"
integrity sha1-wkOZUUVbs5kT2vKBN28VMOEErfM=
"js-tokens@^3.0.0 || ^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
json-schema-traverse@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
json5@^2.1.2:
version "2.2.0"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3"
integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==
dependencies:
minimist "^1.2.5"
loader-utils@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0"
integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==
dependencies:
big.js "^5.2.2"
emojis-list "^3.0.0"
json5 "^2.1.2"
lodash@^4.17.21:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
loose-envify@^1.1.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
dependencies:
js-tokens "^3.0.0 || ^4.0.0"
lru-cache@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
dependencies:
yallist "^4.0.0"
mime-db@1.48.0:
version "1.48.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.48.0.tgz#e35b31045dd7eada3aaad537ed88a33afbef2d1d"
integrity sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==
mime-types@^2.1.27:
version "2.1.31"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.31.tgz#a00d76b74317c61f9c2db2218b8e9f8e9c5c9e6b"
integrity sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==
dependencies:
mime-db "1.48.0"
mini-css-extract-plugin@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-1.6.0.tgz#b4db2525af2624899ed64a23b0016e0036411893"
integrity sha512-nPFKI7NSy6uONUo9yn2hIfb9vyYvkFu95qki0e21DQ9uaqNKDP15DGpK0KnV6wDroWxPHtExrdEwx/yDQ8nVRw==
dependencies:
loader-utils "^2.0.0"
schema-utils "^3.0.0"
webpack-sources "^1.1.0"
minimist@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
nanoid@^3.1.23:
version "3.1.23"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.23.tgz#f744086ce7c2bc47ee0a8472574d5c78e4183a81"
integrity sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==
nomnom@1.8.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/nomnom/-/nomnom-1.8.1.tgz#2151f722472ba79e50a76fc125bb8c8f2e4dc2a7"
integrity sha1-IVH3Ikcrp55Qp2/BJbuMjy5Nwqc=
dependencies:
chalk "~0.4.0"
underscore "~1.6.0"
object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
object-path@^0.11.5:
version "0.11.5"
resolved "https://registry.yarnpkg.com/object-path/-/object-path-0.11.5.tgz#d4e3cf19601a5140a55a16ad712019a9c50b577a"
integrity sha512-jgSbThcoR/s+XumvGMTMf81QVBmah+/Q7K7YduKeKVWL7N111unR2d6pZZarSk6kY/caeNxUDyxOvMWyzoU2eg==
"phoenix@file:../deps/phoenix":
version "1.6.6"
"phoenix_html@file:../deps/phoenix_html":
version "3.2.0"
po2json@^0.4.0:
version "0.4.5"
resolved "https://registry.yarnpkg.com/po2json/-/po2json-0.4.5.tgz#47bb2952da32d58a1be2f256a598eebc0b745118"
integrity sha1-R7spUtoy1Yob4vJWpZjuvAt0URg=
dependencies:
gettext-parser "1.1.0"
nomnom "1.8.1"
postcss-modules-extract-imports@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d"
integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==
postcss-modules-local-by-default@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c"
integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==
dependencies:
icss-utils "^5.0.0"
postcss-selector-parser "^6.0.2"
postcss-value-parser "^4.1.0"
postcss-modules-scope@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06"
integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==
dependencies:
postcss-selector-parser "^6.0.4"
postcss-modules-values@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c"
integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==
dependencies:
icss-utils "^5.0.0"
postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4:
version "6.0.6"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz#2c5bba8174ac2f6981ab631a42ab0ee54af332ea"
integrity sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==
dependencies:
cssesc "^3.0.0"
util-deprecate "^1.0.2"
postcss-value-parser@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb"
integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==
postcss@^8.2.15:
version "8.3.5"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.5.tgz#982216b113412bc20a86289e91eb994952a5b709"
integrity sha512-NxTuJocUhYGsMiMFHDUkmjSKT3EdH4/WbGF6GCi1NDGk+vbcUTun4fpbOqaPtD8IIsztA2ilZm2DhYCuyN58gA==
dependencies:
colorette "^1.2.2"
nanoid "^3.1.23"
source-map-js "^0.6.2"
prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
dependencies:
loose-envify "^1.4.0"
object-assign "^4.1.1"
react-is "^16.8.1"
punycode@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
react-dom@^17.0.2:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"
integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
scheduler "^0.20.2"
react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-redux@^7.2.4:
version "7.2.4"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.4.tgz#1ebb474032b72d806de2e0519cd07761e222e225"
integrity sha512-hOQ5eOSkEJEXdpIKbnRyl04LhaWabkDPV+Ix97wqQX3T3d2NQ8DUblNXXtNMavc7DpswyQM6xfaN4HQDKNY2JA==
dependencies:
"@babel/runtime" "^7.12.1"
"@types/react-redux" "^7.1.16"
hoist-non-react-statics "^3.3.2"
loose-envify "^1.4.0"
prop-types "^15.7.2"
react-is "^16.13.1"
react@^17.0.2:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
redux-watch@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/redux-watch/-/redux-watch-1.2.0.tgz#b3a745e15855ef72db0e06a60a4b2fe3676d79a7"
integrity sha512-Ws4Q+e5zFGMyy1H709c1Ws8apSd6MqoJRIzBDHbI4nikome/IZWVTYXdQNz+VJxPjyX/h2E+lYEo41fXgjCF8g==
dependencies:
object-path "^0.11.5"
redux@^4.0.0, redux@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.0.tgz#eb049679f2f523c379f1aff345c8612f294c88d4"
integrity sha512-uI2dQN43zqLWCt6B/BMGRMY6db7TTY4qeHHfGeKb3EOhmOKjU3KdWvNLJyqaHRksv/ErdNH7cFZWg9jXtewy4g==
dependencies:
"@babel/runtime" "^7.9.2"
regenerator-runtime@^0.13.4:
version "0.13.7"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"
integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==
"safer-buffer@>= 2.1.2 < 3.0.0":
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
scheduler@^0.20.2:
version "0.20.2"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91"
integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
schema-utils@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.0.0.tgz#67502f6aa2b66a2d4032b4279a2944978a0913ef"
integrity sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==
dependencies:
"@types/json-schema" "^7.0.6"
ajv "^6.12.5"
ajv-keywords "^3.5.2"
semver@^7.3.5:
version "7.3.5"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
dependencies:
lru-cache "^6.0.0"
source-list-map@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==
source-map-js@^0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e"
integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==
source-map@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
strip-ansi@~0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.1.1.tgz#39e8a98d044d150660abe4a6808acf70bb7bc991"
integrity sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE=
underscore@~1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.6.0.tgz#8b38b10cacdef63337b8b24e4ff86d45aea529a8"
integrity sha1-izixDKze9jM3uLJOT/htRa6lKag=
uri-js@^4.2.2:
version "4.4.1"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
dependencies:
punycode "^2.1.0"
url-loader@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-4.1.1.tgz#28505e905cae158cf07c92ca622d7f237e70a4e2"
integrity sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==
dependencies:
loader-utils "^2.0.0"
mime-types "^2.1.27"
schema-utils "^3.0.0"
util-deprecate@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
webpack-sources@^1.1.0:
version "1.4.3"
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933"
integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==
dependencies:
source-list-map "^2.0.0"
source-map "~0.6.1"
yallist@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==

3
bin/server Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh
mix phx.server

View File

@ -16,6 +16,4 @@ mix ecto.migrate
# Grab JS dependencies from NPM
echo "Installing js dependencies"
cd assets
yarn install
cd ..
npm install --prefix assets

View File

@ -16,7 +16,8 @@ config :chess, ChessWeb.Endpoint,
url: [host: "localhost"],
secret_key_base: "iiTDTKorCWTFoeBgAkr35XZp22cNIM2RsmnHiHdzKAuSHXUGXx42z7lawAwiu1B1",
render_errors: [view: ChessWeb.ErrorView, accepts: ~w(html json)],
pubsub_server: Chess.PubSub
pubsub_server: Chess.PubSub,
live_view: [signing_salt: "R3JjvjiRi64kjFCHqCJCfk7EY8hohfadgLqO/VUFRO8="]
# Configures Elixir's Logger
config :logger, :console,
@ -35,19 +36,19 @@ config :formulator,
# Configure esbuild (the version is required)
config :esbuild,
version: "0.17.5",
version: "0.19.4",
default: [
args:
~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/* --loader:.js=jsx),
~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/js --external:/fonts/* --external:/images/*),
cd: Path.expand("../assets", __DIR__),
env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
]
# Configure dart_sass
config :dart_sass,
version: "1.58.0",
version: "1.69.3",
default: [
args: ~w(css/app.scss ../priv/static/assets/app.css),
args: ~w(css/app.scss ../priv/static/css/app.css),
cd: Path.expand("../assets", __DIR__)
]

View File

@ -1,18 +1,18 @@
import Config
# For development, we disable any cache and enable
# debugging and code reloading.
#
# The watchers configuration can be used to run external
# watchers to your application. For example, we use it
# with brunch.io to recompile .js and .css sources.
config :chess, ChessWeb.Endpoint,
http: [port: 4000],
debug_errors: true,
code_reloader: true,
check_origin: false
check_origin: false,
watchers: [
esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline
--watch)]},
sass:
{DartSass, :install_and_run,
[:default, ~w(--embed-source-map --source-map-urls=absolute --watch)]}
]
# Watch static and templates for browser reloading.
config :chess, ChessWeb.Endpoint,
live_reload: [
patterns: [
@ -23,11 +23,8 @@ config :chess, ChessWeb.Endpoint,
]
]
# Do not include metadata nor timestamps in development logs
config :logger, :console, format: "[$level] $message\n"
# Set a higher stacktrace during development. Avoid configuring such
# in production as building large stacktraces may be expensive.
config :phoenix, :stacktrace_depth, 20
config :chess, Chess.Mailer, adapter: Bamboo.LocalAdapter

View File

@ -1,14 +1,7 @@
defmodule Chess.Board do
@moduledoc false
def transform(board) do
Enum.map(0..7, fn rank ->
Enum.map(0..7, fn file ->
board
|> piece({file, rank})
end)
end)
end
require Logger
def search(board, %{"type" => type, "colour" => colour}) do
board
@ -31,8 +24,8 @@ defmodule Chess.Board do
end
def move_piece(board, %{
"from" => [from_file, from_rank],
"to" => [to_file, to_rank]
from: {from_file, from_rank},
to: {to_file, to_rank}
}) do
{piece, board} = Map.pop(board, to_index({from_file, from_rank}))
{piece_captured, board} = Map.pop(board, to_index({to_file, to_rank}))
@ -42,8 +35,8 @@ defmodule Chess.Board do
if castling_move?(piece, from_file, to_file) do
board
|> castling_move(%{
"from" => [from_file, from_rank],
"to" => [to_file, to_rank]
from: {from_file, from_rank},
to: {to_file, to_rank}
})
|> Map.get(:board)
else
@ -65,17 +58,17 @@ defmodule Chess.Board do
def castling_move?(_, _, _), do: false
def castling_move(board, %{"from" => [4, rank], "to" => [2, _rank]}) do
def castling_move(board, %{from: {4, rank}, to: {2, _rank}}) do
move_piece(board, %{
"from" => [0, rank],
"to" => [3, rank]
from: {0, rank},
to: {3, rank}
})
end
def castling_move(board, %{"from" => [4, rank], "to" => [6, _rank]}) do
def castling_move(board, %{from: {4, rank}, to: {6, _rank}}) do
move_piece(board, %{
"from" => [7, rank],
"to" => [5, rank]
from: {7, rank},
to: {5, rank}
})
end

View File

@ -19,8 +19,8 @@ defmodule Chess.Emails do
end
def opponent_moved_email(socket, game) do
user = Repo.get(User, socket.assigns.user_id)
opponent = opponent(game, socket.assigns.user_id)
user = Repo.get(User, socket.assigns.user.id)
opponent = opponent(game, socket.assigns.user.id)
new_email()
|> to(opponent)

View File

@ -68,7 +68,7 @@ defmodule Chess.GameState do
|> Moves.available({file, rank})
|> Enum.all?(fn {to_file, to_rank} ->
board
|> Board.move_piece(%{"from" => [file, rank], "to" => [to_file, to_rank]})
|> Board.move_piece(%{from: {file, rank}, to: {to_file, to_rank}})
|> Map.get(:board)
|> king_in_check?(piece["colour"])
end)

View File

@ -14,6 +14,8 @@ defmodule Chess.Moves do
alias Chess.Moves.Pieces.Queen
alias Chess.Moves.Pieces.King
require Logger
def make_move(game, move_params) do
params =
game.board

View File

@ -79,7 +79,7 @@ defmodule Chess.Moves.Pieces.King.Castling do
[{2, rank}, {3, rank}]
|> Enum.any?(fn {to_file, to_rank} ->
board
|> Board.move_piece(%{"from" => [4, rank], "to" => [to_file, to_rank]})
|> Board.move_piece(%{from: {4, rank}, to: {to_file, to_rank}})
|> Map.get(:board)
|> GameState.king_in_check?(colour)
end)
@ -89,7 +89,7 @@ defmodule Chess.Moves.Pieces.King.Castling do
[{5, rank}, {6, rank}]
|> Enum.any?(fn {to_file, to_rank} ->
board
|> Board.move_piece(%{"from" => [4, rank], "to" => [to_file, to_rank]})
|> Board.move_piece(%{from: {4, rank}, to: {to_file, to_rank}})
|> Map.get(:board)
|> GameState.king_in_check?(colour)
end)

View File

@ -19,6 +19,7 @@ defmodule ChessWeb do
def controller do
quote do
use Phoenix.Controller, namespace: ChessWeb
import Phoenix.LiveView.Controller
alias Chess.Repo
import Ecto
@ -39,6 +40,8 @@ defmodule ChessWeb do
import Phoenix.Controller,
only: [get_csrf_token: 0, get_flash: 2, view_module: 1]
import Phoenix.Component
# Use all HTML functionality (forms, tags, etc)
use Phoenix.HTML
@ -50,9 +53,45 @@ defmodule ChessWeb do
end
end
def live_view do
quote do
use Phoenix.LiveView,
layout: {ChessWeb.LayoutView, :live}
unquote(view_helpers())
end
end
def live_component do
quote do
use Phoenix.LiveComponent
unquote(view_helpers())
end
end
defp view_helpers do
quote do
# Use all HTML functionality (forms, tags, etc)
use Phoenix.HTML
# Import LiveView helpers (live_render, live_component, live_patch, etc)
import Phoenix.LiveView.Helpers
# Import basic rendering functionality (render, render_layout, etc)
import Phoenix.View
import ChessWeb.ErrorHelpers
import ChessWeb.Gettext
alias ChessWeb.Router.Helpers, as: Routes
end
end
def router do
quote do
use Phoenix.Router
import Phoenix.LiveView.Router
end
end

View File

@ -5,7 +5,6 @@ defmodule ChessWeb.GameChannel do
import ChessWeb.GameView, only: [player: 2, opponent: 2]
alias Chess.Board
alias Chess.Emails
alias Chess.Mailer
alias Chess.MoveList
@ -29,7 +28,7 @@ defmodule ChessWeb.GameChannel do
opponent_id: opponent(game, socket.assigns.user_id).id,
player: player(game, socket.assigns.user_id),
opponent: opponent(game, socket.assigns.user_id).name,
board: Board.transform(game.board),
board: game.board,
turn: game.turn,
state: game.state,
moves: MoveList.transform(game.moves)
@ -135,7 +134,7 @@ defmodule ChessWeb.GameChannel do
|> Queries.game_with_moves(socket.assigns.game_id)
payload = %{
board: Board.transform(game.board),
board: game.board,
turn: game.turn,
state: game.state,
moves: MoveList.transform(game.moves)

View File

@ -1,73 +1,4 @@
defmodule ChessWeb.Presence do
@moduledoc """
Provides presence tracking to channels and processes.
See the [`Phoenix.Presence`](http://hexdocs.pm/phoenix/Phoenix.Presence.html)
docs for more details.
## Usage
Presences can be tracked in your channel after joining:
defmodule Chess.MyChannel do
use ChessWeb, :channel
alias Chess.Presence
def join("some:topic", _params, socket) do
send(self, :after_join)
{:ok, assign(socket, :user_id, ...)}
end
def handle_info(:after_join, socket) do
push socket, "presence_state", Presence.list(socket)
{:ok, _} = Presence.track(socket, socket.assigns.user_id, %{
online_at: inspect(System.system_time(:second))
})
{:noreply, socket}
end
end
In the example above, `Presence.track` is used to register this
channel's process as a presence for the socket's user ID, with
a map of metadata. Next, the current presence list for
the socket's topic is pushed to the client as a `"presence_state"` event.
Finally, a diff of presence join and leave events will be sent to the
client as they happen in real-time with the "presence_diff" event.
See `Phoenix.Presence.list/2` for details on the presence datastructure.
## Fetching Presence Information
The `fetch/2` callback is triggered when using `list/1`
and serves as a mechanism to fetch presence information a single time,
before broadcasting the information to all channel subscribers.
This prevents N query problems and gives you a single place to group
isolated data fetching to extend presence metadata.
The function receives a topic and map of presences and must return a
map of data matching the Presence datastructure:
%{"123" => %{metas: [%{status: "away", phx_ref: ...}],
"456" => %{metas: [%{status: "online", phx_ref: ...}]}
The `:metas` key must be kept, but you can extend the map of information
to include any additional information. For example:
def fetch(_topic, entries) do
users = entries |> Map.keys() |> Accounts.get_users_map(entries)
# => %{"123" => %{name: "User 123"}, "456" => %{name: nil}}
for {key, %{metas: metas}} <- entries, into: %{} do
{key, %{metas: metas, user: users[key]}}
end
end
The function above fetches all users from the database who
have registered presences for the given topic. The fetched
information is then extended with a `:user` key of the user's
information, while maintaining the required `:metas` field from the
original presence data.
"""
use Phoenix.Presence,
otp_app: :chess,
pubsub_server: Chess.PubSub

View File

@ -1,11 +1,18 @@
defmodule ChessWeb.Endpoint do
use Phoenix.Endpoint, otp_app: :chess
@session_options [
store: :cookie,
key: "_chess_key",
signing_salt: "9LqUhZTU",
same_site: "Lax"
]
if sandbox = Application.compile_env(:chess, :sandbox) do
plug(Phoenix.Ecto.SQL.Sandbox, sandbox: sandbox)
end
socket("/socket", ChessWeb.UserSocket)
socket("/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]])
# Serve at "/" the static files from "priv/static" directory.
#
@ -45,11 +52,7 @@ defmodule ChessWeb.Endpoint do
# The session will be stored in the cookie and signed,
# this means its contents can be read but not tampered with.
# Set :encryption_salt if you would also like to encrypt it.
plug(Plug.Session,
store: :cookie,
key: "_chess_key",
signing_salt: "9LqUhZTU"
)
plug(Plug.Session, @session_options)
plug(ChessWeb.Router)
end

View File

@ -6,7 +6,7 @@ defmodule ChessWeb.Router do
pipeline :browser do
plug(:accepts, ["html"])
plug(:fetch_session)
plug(:fetch_flash)
plug(:fetch_live_flash)
plug(:protect_from_forgery)
plug(:put_secure_browser_headers)
end
@ -42,13 +42,6 @@ defmodule ChessWeb.Router do
resources("/password", PasswordController, only: [:edit, :update], singleton: true)
end
# Other scopes may use custom stacks.
scope "/api", as: :api do
pipe_through([:api, :auth, :ensure_auth])
resources("/opponents", ChessWeb.Api.OpponentsController, only: [:index])
end
if Mix.env() == :dev do
forward("/sent_emails", Bamboo.SentEmailViewerPlug)
end

View File

@ -0,0 +1,44 @@
<div class="board board--<%= @game.turn %>-to-move board--player-is-<%= player_colour(@user, @game) %>">
<div class="board__rank-labels">
<div class="board__label">1</div>
<div class="board__label">2</div>
<div class="board__label">3</div>
<div class="board__label">4</div>
<div class="board__label">5</div>
<div class="board__label">6</div>
<div class="board__label">7</div>
<div class="board__label">8</div>
</div>
<div class="board__file-labels">
<div class="board__label">a</div>
<div class="board__label">b</div>
<div class="board__label">c</div>
<div class="board__label">d</div>
<div class="board__label">e</div>
<div class="board__label">f</div>
<div class="board__label">g</div>
<div class="board__label">h</div>
</div>
<% rank_range = if white?(@user, @game), do: 7..0, else: 0..7 %>
<% file_range = if black?(@user, @game), do: 7..0, else: 0..7 %>
<div class="board__body">
<%= for rank <- rank_range do %>
<%= for file <- file_range do %>
<%= render ChessWeb.SquareView,
"square.html",
rank: rank,
file: file,
piece: piece(@board, {file, rank}),
selected: {file, rank} == @selected,
available: {file, rank} in @available %>
<% end %>
<% end %>
</div>
<div class="game-state game-state--<%= @game.state %>">
<%= state_text(@game.state) %>
</div>
</div>

View File

@ -0,0 +1,41 @@
<div class="game-info">
<p>
Playing <%= opponent(@game, @user.id).name %>
<%= if Map.has_key?(@presence, opponent(@game, @user.id).id) do %>
<img class="game-info__opponent-status" src="/images/eye-open.svg" alt="online">
<% else %>
<img class="game-info__opponent-status" src="/images/eye-closed.svg" alt="offline">
<% end %>
</p>
</div>
<div class="move-list">
<table class="table table--condensed">
<thead>
<tr>
<th class="move-list__line-number"><span class="visually-hidden">Move no.</span></th>
<th class="move-list__header--white">White</th>
<th class="move-list__header--black">Black</th>
</tr>
</thead>
<tbody>
<%= for {move_pair, i} <- @game.moves |>
Enum.chunk_every(2) |> Enum.with_index do %>
<tr>
<th scope="row" class="move-list__line-number"><%= i + 1 %>.</th>
<%= case move_pair do %>
<% [white_move, black_move] -> %>
<td class="move-list__move move-list__move--<%=
white_move.piece["colour"] %>"><%= move_text(white_move) %></td>
<td class="move-list__move move-list__move--<%=
black_move.piece["colour"] %>"><%= move_text(black_move) %></td>
<% [white_move] -> %>
<td class="move-list__move move-list__move--<%=
white_move.piece["colour"] %>"><%= move_text(white_move) %></td>
<td></td>
<% end %>
</tr>
<% end %>
</tbody>
</table>
</div>

View File

@ -3,10 +3,10 @@
<table class="games-list">
<tbody>
<%= for game <- @games do %>
<tr class="<%= turn_class(@conn, game) %>">
<tr class="<%= turn_class(current_user(@conn), game) %>">
<td>
<span class="games-list__player-indicator">
<img src="images/pawn_<%= player_colour(@conn, game) %>.svg">
<img src="images/pawn_<%= player_colour(current_user(@conn), game) %>.svg">
</span>
<%= link gettext(
"Game with %{name}",
@ -16,10 +16,10 @@
class: "btn btn-default btn-xs" %>
</td>
<td>
<%= state(@conn, game) %>
<%= state(current_user(@conn), game) %>
</td>
<td>
<%= won_lost(@conn, game) %>
<%= won_lost(current_user(@conn), game) %>
</td>
<td class="text-right">
<%= link gettext("Delete"),

View File

@ -4,7 +4,12 @@
<%= form_for @changeset, game_path(@conn, :create), [class: "create-game"],
fn form -> %>
<div class="form-group">
<div id="opponent-finder"></div>
<%= live_render(
@conn,
ChessWeb.OpponentFinderLive,
session: %{"user_id" => current_user(@conn).id}
) %>
<%= error_tag form, :opponent_id %>
</div>

View File

@ -0,0 +1,32 @@
<div class="form-group">
<div class="form-field opponent-finder">
<label for="query-string">Find opponent</label>
<%= tag :input,
id: "query-string",
class: "search-input",
name: "q",
type: "text",
autocomplete: "off",
"phx-change": "search",
"phx-debounce": "250",
value: @q,
placeholder: Map.get(@selected, :name, "")
%>
<%= tag :input,
name: "game[opponent_id]",
type: "hidden",
value: Map.get(@selected, :id, nil)
%>
<%= if Enum.any?(@opponents) do %>
<ul class="opponent-finder__result">
<%= for opponent <- @opponents do %>
<li><a class="opponent-finder__result-item"
phx-click="select" phx-value-id="<%= opponent.id
%>" href="#"><%= opponent.name %></a></li>
<% end %>
</ul>
<% end %>
</div>
</div>

View File

@ -1,2 +1,15 @@
<div id="game" data-game-id="<%= @game.id %>">
<div data-game-id="<%= @game.id %>">
<div class="game-grid">
<%= live_render(
@conn,
ChessWeb.BoardLive,
session: %{"user_id" => current_user(@conn).id, "game_id" => @game.id}
) %>
<%= live_render(
@conn,
ChessWeb.GameInfoLive,
session: %{"user_id" => current_user(@conn).id, "game_id" => @game.id}
) %>
</div>
</div>

View File

@ -4,6 +4,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<meta name="csrf-token" content="<%= Plug.CSRFProtection.get_csrf_token() %>" />
<title><%= gettext "64squares" %></title>
<link href="https://fonts.googleapis.com/css?family=Enriqueta:400,700" rel="stylesheet">

View File

@ -1,3 +1,2 @@
<script>window.userToken = "<%= @token %>";</script>
<script src="<%= static_path(@conn, "/js/vendor.js") %>"></script>
<script src="<%= static_path(@conn, "/js/app.js") %>"></script>

View File

@ -1,7 +1,7 @@
<div class="form">
<h2><%= gettext "Password" %></h2>
<%= form_for @changeset, password_path(@conn, :update), [class: "update-password"], fn f -> %>
<%= form_for @changeset, password_path(@conn, :update), [class: "update-password", novalidate: true], fn f -> %>
<%= if @changeset.action do %>
<div class="alert alert-danger">
<p>

View File

@ -1,7 +1,7 @@
<div class="form">
<h2><%= gettext "Profile" %></h2>
<%= form_for @changeset, profile_path(@conn, :update), [class: "update-profile"], fn f -> %>
<%= form_for @changeset, profile_path(@conn, :update), [class: "update-profile", novalidate: true], fn f -> %>
<%= if @changeset.action do %>
<div class="alert alert-danger">
<p>

View File

@ -0,0 +1,7 @@
<div
id="f<%= @file %>-r<%= @rank %>"
phx-click="click"
phx-value-rank="<%= @rank %>"
phx-value-file="<%= @file %>"
class="<%= classes(@file, @rank, @piece, @selected, @available) %>"
></div>

View File

@ -2,12 +2,23 @@ defmodule ChessWeb.GameView do
use ChessWeb, :view
alias Chess.GameState
alias Chess.Repo
import Phoenix.Component
import Chess.Auth, only: [current_user: 1]
def won_lost(conn, game) do
@pieces %{
pawn: "",
knight: "N",
bishop: "B",
rook: "R",
queen: "Q",
king: "K"
}
def won_lost(user, game) do
if game_over?(game) && game.state == "checkmate" do
(your_turn?(conn, game) &&
(your_turn?(user, game) &&
gettext("You lost")) ||
gettext("You won")
end
@ -17,12 +28,12 @@ defmodule ChessWeb.GameView do
GameState.game_over?(game)
end
def state(conn, game) do
def state(user, game) do
cond do
GameState.game_over?(game) ->
states()[game.state]
state_text(game.state)
your_turn?(conn, game) ->
your_turn?(user, game) ->
gettext("Your turn")
true ->
@ -30,18 +41,44 @@ defmodule ChessWeb.GameView do
end
end
def turn_class(conn, game) do
if your_turn?(conn, game) && !GameState.game_over?(game) do
def turn_class(user, game) do
if your_turn?(user, game) && !GameState.game_over?(game) do
"games-list__your-turn"
end
end
def your_turn?(conn, game) do
player_colour(conn, game) == game.turn
def white?(user, game) do
player_colour(user, game) == "white"
end
def player_colour(conn, game) do
(current_user(conn).id == game.user_id && "white") || "black"
def black?(user, game) do
player_colour(user, game) == "black"
end
def your_turn?(user, game) do
user
|> player_colour(game) == game.turn
end
def player_colour(user, game) do
(user.id == game.user_id && "white") || "black"
end
def piece(board, {file, rank}) do
Chess.Board.piece(board, {file, rank})
end
def files(user, game) do
ranks(user, game)
|> Enum.reverse()
end
def ranks(user, game) do
if game.user_id == user.id do
7..0
else
0..7
end
end
def player(game, user_id) do
@ -52,6 +89,14 @@ defmodule ChessWeb.GameView do
end
end
def opponent_id(game, user_id) do
if game.user_id == user_id do
game.opponent_id
else
game.user_id
end
end
def opponent(game, user_id) do
if game.user_id == user_id do
game.opponent
@ -60,11 +105,26 @@ defmodule ChessWeb.GameView do
end
end
defp states do
%{
"checkmate" => gettext("Checkmate!"),
"stalemate" => gettext("Stalemate"),
"check" => gettext("Check")
}
def move_text(move) do
move = Chess.Store.Move.transform(move)
piece_type = move.piece["type"] |> String.to_atom()
[
@pieces[piece_type],
move.to
]
|> Enum.join()
end
def state_text(state) do
Map.get(
%{
"checkmate" => gettext("Checkmate!"),
"stalemate" => gettext("Stalemate"),
"check" => gettext("Check")
},
state
)
end
end

View File

@ -0,0 +1,161 @@
defmodule ChessWeb.BoardLive do
use Phoenix.LiveView, container: {:div, class: "board__container"}
alias Chess.Emails
alias Chess.Mailer
alias Chess.Store.User
alias Chess.Store.Game
alias Chess.Store.Move
alias Chess.Repo
alias Chess.Moves
alias ChessWeb.GameView
alias ChessWeb.Presence
import Ecto.Query
require Logger
def render(assigns) do
Phoenix.View.render(GameView, "board.html", assigns)
end
def mount(_params, %{"user_id" => user_id, "game_id" => game_id}, socket) do
ChessWeb.Endpoint.subscribe("game:#{game_id}")
user = Repo.get!(User, user_id)
game =
Game.for_user(user)
|> Repo.get!(game_id)
|> Repo.preload([:user, :opponent])
{:ok, assign(socket, default_assigns(game, user))}
end
def handle_event("click", %{"rank" => rank, "file" => file}, socket) do
{
:noreply,
socket |> handle_click(String.to_integer(file), String.to_integer(rank))
}
end
defp default_assigns(game, user) do
%{
board: game.board,
game: game,
user: user,
selected: nil,
available: []
}
end
def handle_info(%{event: "move", payload: state}, socket) do
Logger.info("Handling move from board")
{:noreply, assign(socket, state)}
end
def handle_info(%{event: "presence_diff", payload: _params}, socket) do
{:noreply, socket}
end
defp handle_click(socket, file, rank) do
game = socket.assigns.game
board = game.board
user = socket.assigns.user
colour = GameView.player_colour(user, game)
assigns =
if colour == game.turn do
case socket.assigns do
%{selected: nil} ->
handle_selection(board, colour, file, rank)
_ ->
handle_move(socket, file, rank)
end
else
[]
end
assign(socket, assigns)
end
defp handle_selection(board, colour, file, rank) do
case GameView.piece(board, {file, rank}) do
%{"colour" => ^colour} ->
[
{:selected, {file, rank}},
{:available, Moves.available(board, {file, rank})}
]
_ ->
[]
end
end
defp handle_move(socket, file, rank) do
%{game: game, available: available, selected: selected} = socket.assigns
if {file, rank} in available do
game
|> Moves.make_move(%{from: selected, to: {file, rank}})
|> case do
{:ok, %{game: game}} ->
game
|> Repo.reload()
|> Repo.preload([:user, :opponent])
|> Repo.preload(
moves:
from(
move in Move,
order_by: [asc: move.inserted_at]
)
)
|> broadcast_move(game.board)
email_move(socket, game)
[
{:selected, nil},
{:available, []},
{:board, game.board},
{:game, game}
]
{:error, _, _, _} ->
[]
end
else
[{:selected, nil}, {:available, []}]
end
end
defp email_move(socket, game) do
opponent_id =
GameView.opponent_id(game, socket.assigns.user.id)
|> Integer.to_string()
"game:#{game.id}"
|> Presence.list()
|> case do
%{^opponent_id => _} ->
nil
_ ->
socket
|> Emails.opponent_moved_email(game)
|> Mailer.deliver_later()
end
end
defp broadcast_move(game, board) do
ChessWeb.Endpoint.broadcast_from(
self(),
"game:#{game.id}",
"move",
%{game: game, board: board}
)
end
end

View File

@ -0,0 +1,72 @@
defmodule ChessWeb.GameInfoLive do
use Phoenix.LiveView
alias Chess.Store.User
alias Chess.Store.Game
alias Chess.Store.Move
alias Chess.Repo
alias ChessWeb.Presence
import Ecto.Query
require Logger
def render(assigns) do
Phoenix.View.render(ChessWeb.GameView, "game_info.html", assigns)
end
def mount(_params, %{"game_id" => game_id, "user_id" => user_id}, socket) do
topic = "game:#{game_id}"
ChessWeb.Endpoint.subscribe(topic)
user = Repo.get!(User, user_id)
game =
Game.for_user(user)
|> preload(:user)
|> preload(:opponent)
|> preload(
moves:
^from(
move in Move,
order_by: [asc: move.inserted_at]
)
)
|> Repo.get!(game_id)
Presence.track(self(), topic, :user, %{id: user_id})
{:ok, assign(socket, game: game, user: user, presence: presence_list(topic))}
end
def handle_info(%{event: "move", payload: state}, socket) do
{:noreply, assign(socket, state)}
end
def handle_info(
%{event: "presence_diff", payload: %{joins: joins, leaves: leaves}},
socket
) do
{:noreply, socket |> handle_joins(joins) |> handle_leaves(leaves)}
end
defp presence_list(topic) do
Presence.list(topic)
|> Map.get("user")
|> Map.get(:metas)
|> Map.new(fn meta -> {meta.id, meta} end)
end
defp handle_joins(socket, joins) do
Enum.reduce(joins, socket, fn {"user", %{metas: [meta | _]}}, socket ->
assign(socket, :presence, Map.put(socket.assigns.presence, meta.id, meta))
end)
end
defp handle_leaves(socket, leaves) do
Enum.reduce(leaves, socket, fn {"user", %{metas: [meta | _]}}, socket ->
assign(socket, :presence, Map.delete(socket.assigns.presence, meta.id))
end)
end
end

View File

@ -0,0 +1,51 @@
defmodule ChessWeb.OpponentFinderLive do
use Phoenix.LiveView
alias Chess.Store.User
alias Chess.Repo
alias Chess.Repo.Queries
alias ChessWeb.GameView
def render(assigns) do
Phoenix.View.render(GameView, "opponent_finder.html", assigns)
end
def mount(_params, %{"user_id" => user_id}, socket) do
ChessWeb.Endpoint.subscribe("opponent_finder:#{user_id}")
user = Repo.get!(User, user_id)
{:ok, assign(socket, default_assigns(user))}
end
def handle_event("search", %{"q" => q}, socket) do
opponents =
case q do
"" ->
[]
query_string ->
socket.assigns.user
|> Queries.opponents(query_string)
|> Repo.all()
end
{:noreply, assign(socket, %{opponents: opponents})}
end
def handle_event("select", %{"id" => id}, socket) do
opponent = Repo.get!(User, id)
{:noreply, assign(socket, %{q: "", opponents: [], selected: opponent})}
end
def default_assigns(user) do
%{
q: "",
user: user,
opponents: [],
selected: %{}
}
end
end

View File

@ -0,0 +1,39 @@
defmodule ChessWeb.SquareView do
use ChessWeb, :view
def classes(_file, _rank, piece, selected, available) do
square_class()
|> add_piece_classes(piece)
|> add_selected_class(selected)
|> add_available_class(available)
|> Enum.join(" ")
end
defp square_class do
["square"]
end
defp add_piece_classes(classes, piece) do
if piece != nil do
classes ++ ["square--#{piece["type"]}", "square--#{piece["colour"]}"]
else
classes
end
end
defp add_selected_class(classes, selected) do
if selected do
classes ++ ["square--selected"]
else
classes
end
end
defp add_available_class(classes, available) do
if available do
classes ++ ["square--available"]
else
classes
end
end
end

13
mix.exs
View File

@ -7,7 +7,7 @@ defmodule Chess.Mixfile do
version: "0.2.0",
elixir: "~> 1.14.1",
elixirc_paths: elixirc_paths(Mix.env()),
compilers: [:phoenix] ++ Mix.compilers(),
compilers: [] ++ Mix.compilers(),
build_embedded: Mix.env() == :prod,
start_permanent: Mix.env() == :prod,
aliases: aliases(),
@ -39,19 +39,20 @@ defmodule Chess.Mixfile do
{:comeonin, "~> 5.0"},
{:cowboy, "~> 2.0"},
{:credo, "~> 1.0", only: [:dev, :test]},
{:dart_sass, "~> 0.5", runtime: Mix.env() == :dev},
{:dart_sass, "~> 0.7", runtime: Mix.env() == :dev},
{:ecto_sql, "~> 3.0"},
{:floki, "~> 0.34", only: :test},
{:esbuild, "~> 0.6", runtime: Mix.env() == :dev},
{:esbuild, "~> 0.7", runtime: Mix.env() == :dev},
{:formulator, "~> 0.4.0"},
{:gettext, "~> 0.22.0"},
{:guardian, "~> 2.0"},
{:jason, "~> 1.0"},
{:phoenix, "~> 1.6.0"},
{:phoenix, "~> 1.7.0"},
{:phoenix_view, "~> 2.0"},
{:phoenix_ecto, "~> 4.0"},
{:phoenix_html, "~> 3.2.0"},
{:phoenix_html, "~> 3.3.0"},
{:phoenix_live_reload, "~> 1.0", only: :dev},
{:phoenix_live_view, "~> 0.18"},
{:phoenix_live_view, "~> 0.20"},
{:phoenix_pubsub, "~> 2.0"},
{:plug_cowboy, "~> 2.0"},
{:postgrex, "~> 0.16.0"},

View File

@ -1,61 +1,63 @@
%{
"argon2_elixir": {:hex, :argon2_elixir, "3.0.0", "fd4405f593e77b525a5c667282172dd32772d7c4fa58cdecdaae79d2713b6c5f", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "8b753b270af557d51ba13fcdebc0f0ab27a2a6792df72fd5a6cf9cfaffcedc57"},
"argon2_elixir": {:hex, :argon2_elixir, "3.2.1", "f47740bf9f2a39ffef79ba48eb25dea2ee37bcc7eadf91d49615591d1a6fce1a", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "a813b78217394530b5fcf4c8070feee43df03ffef938d044019169c766315690"},
"bamboo": {:hex, :bamboo, "2.3.0", "d2392a2cabe91edf488553d3c70638b532e8db7b76b84b0a39e3dfe492ffd6fc", [:mix], [{:hackney, ">= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.4 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "dd0037e68e108fd04d0e8773921512c940e35d981e097b5793543e3b2f9cd3f6"},
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
"castore": {:hex, :castore, "0.1.22", "4127549e411bedd012ca3a308dede574f43819fe9394254ca55ab4895abfa1a2", [:mix], [], "hexpm", "c17576df47eb5aa1ee40cc4134316a99f5cad3e215d5c77b8dd3cfef12a22cac"},
"certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"},
"bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"},
"castore": {:hex, :castore, "1.0.4", "ff4d0fb2e6411c0479b1d965a814ea6d00e51eb2f58697446e9c41a97d940b28", [:mix], [], "hexpm", "9418c1b8144e11656f0be99943db4caf04612e3eaecefb5dae9a2a87565584f8"},
"certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
"comeonin": {:hex, :comeonin, "5.3.3", "2c564dac95a35650e9b6acfe6d2952083d8a08e4a89b93a481acb552b325892e", [:mix], [], "hexpm", "3e38c9c2cb080828116597ca8807bb482618a315bfafd98c90bc22a821cc84df"},
"comeonin": {:hex, :comeonin, "5.4.0", "246a56ca3f41d404380fc6465650ddaa532c7f98be4bda1b4656b3a37cc13abe", [:mix], [], "hexpm", "796393a9e50d01999d56b7b8420ab0481a7538d0caf80919da493b4a6e51faf1"},
"connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
"cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"},
"cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"},
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
"cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"},
"credo": {:hex, :credo, "1.6.3", "0a9f8925dbc8f940031b789f4623fc9a0eea99d3eed600fe831e403eb96c6a83", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "1167cde00e6661d740fc54da2ee268e35d3982f027399b64d3e2e83af57a1180"},
"dart_sass": {:hex, :dart_sass, "0.5.1", "d45f20a8e324313689fb83287d4702352793ce8c9644bc254155d12656ade8b6", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "24f8a1c67e8b5267c51a33cbe6c0b5ebf12c2c83ace88b5ac04947d676b4ec81"},
"db_connection": {:hex, :db_connection, "2.4.3", "3b9aac9f27347ec65b271847e6baeb4443d8474289bd18c1d6f4de655b70c94d", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c127c15b0fa6cfb32eed07465e05da6c815b032508d4ed7c116122871df73c12"},
"decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"},
"ecto": {:hex, :ecto, "3.9.4", "3ee68e25dbe0c36f980f1ba5dd41ee0d3eb0873bccae8aeaf1a2647242bffa35", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "de5f988c142a3aa4ec18b85a4ec34a2390b65b24f02385c1144252ff6ff8ee75"},
"ecto_sql": {:hex, :ecto_sql, "3.9.2", "34227501abe92dba10d9c3495ab6770e75e79b836d114c41108a4bf2ce200ad5", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1eb5eeb4358fdbcd42eac11c1fbd87e3affd7904e639d77903c1358b2abd3f70"},
"elixir_make": {:hex, :elixir_make, "0.7.3", "c37fdae1b52d2cc51069713a58c2314877c1ad40800a57efb213f77b078a460d", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "24ada3e3996adbed1fa024ca14995ef2ba3d0d17b678b0f3f2b1f66e6ce2b274"},
"esbuild": {:hex, :esbuild, "0.6.0", "9ba6ead054abd43cb3d7b14946a0cdd1493698ccd8e054e0e5d6286d7f0f509c", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "30f9a05d4a5bab0d3e37398f312f80864e1ee1a081ca09149d06d474318fd040"},
"expo": {:hex, :expo, "0.3.0", "13127c1d5f653b2927f2616a4c9ace5ae372efd67c7c2693b87fd0fdc30c6feb", [:mix], [], "hexpm", "fb3cd4bf012a77bc1608915497dae2ff684a06f0fa633c7afa90c4d72b881823"},
"cowlib": {:hex, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"},
"credo": {:hex, :credo, "1.7.1", "6e26bbcc9e22eefbff7e43188e69924e78818e2fe6282487d0703652bc20fd62", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "e9871c6095a4c0381c89b6aa98bc6260a8ba6addccf7f6a53da8849c748a58a2"},
"dart_sass": {:hex, :dart_sass, "0.7.0", "7979e056cb74fd6843e1c72db763cffc7726a9192a657735b7d24c0d9c26a1ce", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "4a8e70bca41aa00846398abdf5ad8a64d7907a0f7bf40145cd2e40d5971629f2"},
"db_connection": {:hex, :db_connection, "2.5.0", "bb6d4f30d35ded97b29fe80d8bd6f928a1912ca1ff110831edcd238a1973652c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c92d5ba26cd69ead1ff7582dbb860adeedfff39774105a4f1c92cbb654b55aa2"},
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
"ecto": {:hex, :ecto, "3.10.3", "eb2ae2eecd210b4eb8bece1217b297ad4ff824b4384c0e3fdd28aaf96edd6135", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "44bec74e2364d491d70f7e42cd0d690922659d329f6465e89feb8a34e8cd3433"},
"ecto_sql": {:hex, :ecto_sql, "3.10.2", "6b98b46534b5c2f8b8b5f03f126e75e2a73c64f3c071149d32987a5378b0fdbd", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "68c018debca57cb9235e3889affdaec7a10616a4e3a80c99fa1d01fdafaa9007"},
"elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"},
"esbuild": {:hex, :esbuild, "0.7.1", "fa0947e8c3c3c2f86c9bf7e791a0a385007ccd42b86885e8e893bdb6631f5169", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "66661cdf70b1378ee4dc16573fcee67750b59761b2605a0207c267ab9d19f13c"},
"expo": {:hex, :expo, "0.4.1", "1c61d18a5df197dfda38861673d392e642649a9cef7694d2f97a587b2cfb319b", [:mix], [], "hexpm", "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47"},
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
"floki": {:hex, :floki, "0.34.0", "002d0cc194b48794d74711731db004fafeb328fe676976f160685262d43706a8", [:mix], [], "hexpm", "9c3a9f43f40dde00332a589bd9d389b90c1f518aef500364d00636acc5ebc99c"},
"floki": {:hex, :floki, "0.34.3", "5e2dcaec5d7c228ce5b1d3501502e308b2d79eb655e4191751a1fe491c37feac", [:mix], [], "hexpm", "9577440eea5b97924b4bf3c7ea55f7b8b6dce589f9b28b096cc294a8dc342341"},
"formulator": {:hex, :formulator, "0.4.0", "43094c3a63e1ee077a01e79425f823d46e031cf7bc9f74045edcda695601efed", [:mix], [{:gettext, ">= 0.11.0", [hex: :gettext, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.4 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}], "hexpm", "15c34113d6a797bb03d089a0b976b0cf918d64e24d2096986b61735fffaf1980"},
"gettext": {:hex, :gettext, "0.22.0", "a25d71ec21b1848957d9207b81fd61cb25161688d282d58bdafef74c2270bdc4", [:mix], [{:expo, "~> 0.3.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "cb0675141576f73720c8e49b4f0fd3f2c69f0cd8c218202724d4aebab8c70ace"},
"guardian": {:hex, :guardian, "2.3.1", "2b2d78dc399a7df182d739ddc0e566d88723299bfac20be36255e2d052fd215d", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bbe241f9ca1b09fad916ad42d6049d2600bbc688aba5b3c4a6c82592a54274c3"},
"hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"},
"httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
"jose": {:hex, :jose, "1.11.5", "3bc2d75ffa5e2c941ca93e5696b54978323191988eb8d225c2e663ddfefd515e", [:mix, :rebar3], [], "hexpm", "dcd3b215bafe02ea7c5b23dafd3eb8062a5cd8f2d904fd9caa323d37034ab384"},
"gettext": {:hex, :gettext, "0.22.3", "c8273e78db4a0bb6fba7e9f0fd881112f349a3117f7f7c598fa18c66c888e524", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "935f23447713954a6866f1bb28c3a878c4c011e802bcd68a726f5e558e4b64bd"},
"guardian": {:hex, :guardian, "2.3.2", "78003504b987f2b189d76ccf9496ceaa6a454bb2763627702233f31eb7212881", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "b189ff38cd46a22a8a824866a6867ca8722942347f13c33f7d23126af8821b52"},
"hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"},
"httpoison": {:hex, :httpoison, "2.1.0", "655fd9a7b0b95ee3e9a3b535cf7ac8e08ef5229bab187fa86ac4208b122d934b", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "fc455cb4306b43827def4f57299b2d5ac8ac331cb23f517e734a4b78210a160c"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
"jose": {:hex, :jose, "1.11.6", "613fda82552128aa6fb804682e3a616f4bc15565a048dabd05b1ebd5827ed965", [:mix, :rebar3], [], "hexpm", "6275cb75504f9c1e60eeacb771adfeee4905a9e182103aa59b53fed651ff9738"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"},
"mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
"parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
"phoenix": {:hex, :phoenix, "1.6.15", "0a1d96bbc10747fd83525370d691953cdb6f3ccbac61aa01b4acb012474b047d", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d70ab9fbf6b394755ea88b644d34d79d8b146e490973151f248cacd122d20672"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.4.0", "0672ed4e4808b3fbed494dded89958e22fb882de47a97634c0b13e7b0b5f7720", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "09864e558ed31ee00bd48fcc1d4fc58ae9678c9e81649075431e69dbabb43cc1"},
"phoenix_html": {:hex, :phoenix_html, "3.2.0", "1c1219d4b6cb22ac72f12f73dc5fad6c7563104d083f711c3fcd8551a1f4ae11", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "36ec97ba56d25c0136ef1992c37957e4246b649d620958a1f9fa86165f8bc54f"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.3.3", "3a53772a6118d5679bf50fc1670505a290e32a1d195df9e069d8c53ab040c054", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "766796676e5f558dbae5d1bdb066849673e956005e3730dfd5affd7a6da4abac"},
"phoenix_live_view": {:hex, :phoenix_live_view, "0.18.11", "c50eac83dae6b5488859180422dfb27b2c609de87f4aa5b9c926ecd0501cd44f", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "76c99a0ffb47cd95bf06a917e74f282a603f3e77b00375f3c2dd95110971b102"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"},
"phoenix_template": {:hex, :phoenix_template, "1.0.1", "85f79e3ad1b0180abb43f9725973e3b8c2c3354a87245f91431eec60553ed3ef", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "157dc078f6226334c91cb32c1865bf3911686f8bcd6bcff86736f6253e6993ee"},
"parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},
"phoenix": {:hex, :phoenix, "1.7.9", "9a2b873e2cb3955efdd18ad050f1818af097fa3f5fc3a6aaba666da36bdd3f02", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "83e32da028272b4bfd076c61a964e6d2b9d988378df2f1276a0ed21b13b5e997"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.4.2", "b21bd01fdeffcfe2fab49e4942aa938b6d3e89e93a480d4aee58085560a0bc0d", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "70242edd4601d50b69273b057ecf7b684644c19ee750989fd555625ae4ce8f5d"},
"phoenix_html": {:hex, :phoenix_html, "3.3.3", "380b8fb45912b5638d2f1d925a3771b4516b9a78587249cabe394e0a5d579dc9", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "923ebe6fec6e2e3b3e569dfbdc6560de932cd54b000ada0208b5f45024bdd76c"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.4.1", "2aff698f5e47369decde4357ba91fc9c37c6487a512b41732818f2204a8ef1d3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "9bffb834e7ddf08467fe54ae58b5785507aaba6255568ae22b4d46e2bb3615ab"},
"phoenix_live_view": {:hex, :phoenix_live_view, "0.20.1", "92a37acf07afca67ac98bd326532ba8f44ad7d4bdf3e4361b03f7f02594e5ae9", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "be494fd1215052729298b0e97d5c2ce8e719c00854b82cd8cf15c1cd7fcf6294"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"},
"phoenix_template": {:hex, :phoenix_template, "1.0.3", "32de561eefcefa951aead30a1f94f1b5f0379bc9e340bb5c667f65f1edfa4326", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "16f4b6588a4152f3cc057b9d0c0ba7e82ee23afa65543da535313ad8d25d8e2c"},
"phoenix_view": {:hex, :phoenix_view, "2.0.2", "6bd4d2fd595ef80d33b439ede6a19326b78f0f1d8d62b9a318e3d9c1af351098", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "a929e7230ea5c7ee0e149ffcf44ce7cf7f4b6d2bfe1752dd7c084cdff152d36f"},
"plug": {:hex, :plug, "1.14.0", "ba4f558468f69cbd9f6b356d25443d0b796fbdc887e03fa89001384a9cac638f", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bf020432c7d4feb7b3af16a0c2701455cbbbb95e5b6866132cb09eb0c29adc14"},
"plug_cowboy": {:hex, :plug_cowboy, "2.6.0", "d1cf12ff96a1ca4f52207c5271a6c351a4733f413803488d75b70ccf44aebec2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "073cf20b753ce6682ed72905cd62a2d4bd9bad1bf9f7feb02a1b8e525bd94fa6"},
"plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"},
"plug": {:hex, :plug, "1.15.1", "b7efd81c1a1286f13efb3f769de343236bd8b7d23b4a9f40d3002fc39ad8f74c", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "459497bd94d041d98d948054ec6c0b76feacd28eec38b219ca04c0de13c79d30"},
"plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"},
"plug_crypto": {:hex, :plug_crypto, "2.0.0", "77515cc10af06645abbfb5e6ad7a3e9714f805ae118fa1a70205f80d2d70fe73", [:mix], [], "hexpm", "53695bae57cc4e54566d993eb01074e4d894b65a3766f1c43e2c61a1b0f45ea9"},
"poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm", "ba8836feea4b394bb718a161fc59a288fe0109b5006d6bdf97b6badfcf6f0f25"},
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
"postgrex": {:hex, :postgrex, "0.16.5", "fcc4035cc90e23933c5d69a9cd686e329469446ef7abba2cf70f08e2c4b69810", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "edead639dc6e882618c01d8fc891214c481ab9a3788dfe38dd5e37fd1d5fb2e8"},
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
"secure_random": {:hex, :secure_random, "0.5.1", "c5532b37c89d175c328f5196a0c2a5680b15ebce3e654da37129a9fe40ebf51b", [:mix], [], "hexpm", "1b9754f15e3940a143baafd19da12293f100044df69ea12db5d72878312ae6ab"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
"tesla": {:hex, :tesla, "1.5.0", "7ee3616be87024a2b7231ae14474310c9b999c3abb1f4f8dbc70f86bd9678eef", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "1d0385e41fbd76af3961809088aef15dec4c2fdaab97b1c93c6484cb3695a122"},
"tesla": {:hex, :tesla, "1.7.0", "a62dda2f80d4f8a925eb7b8c5b78c461e0eb996672719fe1a63b26321a5f8b4e", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "2e64f01ebfdb026209b47bc651a0e65203fcff4ae79c11efb73c4852b00dc313"},
"timex": {:hex, :timex, "3.6.3", "58ce6c9eda8ed47fc80c24dde09d481465838d3bcfc230949287fc1b0b0041c1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "6d69f4f95fcf5684102a9cb3cf92c5ba6545bd60ed8d8a6a93cd2a4a4fb0d9ec"},
"timex_ecto": {:hex, :timex_ecto, "3.4.0", "7871043345626a591bfa3e313aa271df4a4eda79f51eb69e83f326f0b8f3181c", [:mix], [{:ecto, "~> 2.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:timex, "~> 3.6", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm", "4237fa971c8fb9baeeb901b3ae4efee663e0da335e291228f215d99cf8a64799"},
"tzdata": {:hex, :tzdata, "1.0.5", "69f1ee029a49afa04ad77801febaf69385f3d3e3d1e4b56b9469025677b89a28", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "55519aa2a99e5d2095c1e61cc74c9be69688f8ab75c27da724eb8279ff402a5a"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
"wallaby": {:hex, :wallaby, "0.30.1", "81342a34080867ab359aca23de4d1d8c6bbdeb35d8ce2a8c42e42b758d539963", [:mix], [{:ecto_sql, ">= 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}, {:httpoison, "~> 0.12 or ~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_ecto, ">= 3.0.0", [hex: :phoenix_ecto, repo: "hexpm", optional: true]}, {:web_driver_client, "~> 0.2.0", [hex: :web_driver_client, repo: "hexpm", optional: false]}], "hexpm", "457251df6a94ff80816524136edbce6400cb1ee979586c90224ff634e9543d78"},
"wallaby": {:hex, :wallaby, "0.30.6", "7dc4c1213f3b52c4152581d126632bc7e06892336d3a0f582853efeeabd45a71", [:mix], [{:ecto_sql, ">= 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}, {:httpoison, "~> 0.12 or ~> 1.0 or ~> 2.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_ecto, ">= 3.0.0", [hex: :phoenix_ecto, repo: "hexpm", optional: true]}, {:web_driver_client, "~> 0.2.0", [hex: :web_driver_client, repo: "hexpm", optional: false]}], "hexpm", "50950c1d968549b54c20e16175c68c7fc0824138e2bb93feb11ef6add8eb23d4"},
"web_driver_client": {:hex, :web_driver_client, "0.2.0", "63b76cd9eb3b0716ec5467a0f8bead73d3d9612e63f7560d21357f03ad86e31a", [:mix], [{:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:tesla, "~> 1.3", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "83cc6092bc3e74926d1c8455f0ce927d5d1d36707b74d9a65e38c084aab0350f"},
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
"websock_adapter": {:hex, :websock_adapter, "0.5.4", "7af8408e7ed9d56578539594d1ee7d8461e2dd5c3f57b0f2a5352d610ddde757", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "d2c238c79c52cbe223fcdae22ca0bb5007a735b9e933870e241fce66afb4f4ab"},
}

View File

@ -72,7 +72,7 @@ defmodule Chess.BoardTest do
"3,0" => %{"type" => "queen", "colour" => "white"}
}
%{board: new_board} = Board.move_piece(board, %{"from" => [3, 0], "to" => [5, 2]})
%{board: new_board} = Board.move_piece(board, %{from: {3, 0}, to: {5, 2}})
assert new_board == %{
"5,2" => %{"type" => "queen", "colour" => "white"}
@ -85,7 +85,7 @@ defmodule Chess.BoardTest do
"7,0" => %{"type" => "rook", "colour" => "white"}
}
%{board: new_board} = Board.move_piece(board, %{"from" => [4, 0], "to" => [6, 0]})
%{board: new_board} = Board.move_piece(board, %{from: {4, 0}, to: {6, 0}})
assert new_board == %{
"6,0" => %{"type" => "king", "colour" => "white"},
@ -99,7 +99,7 @@ defmodule Chess.BoardTest do
"0,0" => %{"type" => "rook", "colour" => "white"}
}
%{board: new_board} = Board.move_piece(board, %{"from" => [4, 0], "to" => [2, 0]})
%{board: new_board} = Board.move_piece(board, %{from: {4, 0}, to: {2, 0}})
assert new_board == %{
"2,0" => %{"type" => "king", "colour" => "white"},

View File

@ -90,7 +90,7 @@ defmodule Chess.Store.GameTest do
opponent_id: opponent.id
})
move_params = %{"from" => [4, 1], "to" => [4, 3]}
move_params = %{from: {4, 1}, to: {4, 3}}
changeset =
Game.move_changeset(

View File

@ -60,7 +60,7 @@ defmodule ChessWeb.GameControllerTest do
|> login(user)
|> get(game_path(conn, :show, game))
assert html_response(conn, 200) =~ "<div id=\"game\" data-game-id=\"#{game.id}\">"
assert html_response(conn, 200) =~ "<div data-game-id=\"#{game.id}\">"
end
test "does not show a game if the user is not a player", %{conn: conn} do