HÄXFEST 2015 !!! ! ! ! !

ZZZZZZZZZ
This commit is contained in:
Mikko Ahlroth 2015-11-29 06:34:42 +02:00
commit d031961a84
37 changed files with 1165 additions and 0 deletions

24
.gitignore vendored Normal file
View file

@ -0,0 +1,24 @@
# App artifacts
/_build
/db
/deps
/*.ez
# Generate on crash by the VM
erl_crash.dump
# Static artifacts
/node_modules
# Since we are building assets from web/static,
# we ignore priv/static. You may want to comment
# this depending on your deployment strategy.
/priv/static/
# The config/prod.secret.exs file by default contains sensitive
# data and you should not commit it into version control.
#
# Alternatively, you may comment the line below and commit the
# secrets file as long as you replace its contents by environment
# variables.
/config/prod.secret.exs

19
README.md Normal file
View file

@ -0,0 +1,19 @@
# Proxichat
To start your Phoenix app:
1. Install dependencies with `mix deps.get`
2. Create and migrate your database with `mix ecto.create && mix ecto.migrate`
3. Start Phoenix endpoint with `mix phoenix.server`
Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.
Ready to run in production? Please [check our deployment guides](http://www.phoenixframework.org/docs/deployment).
## Learn more
* Official website: http://www.phoenixframework.org/
* Guides: http://phoenixframework.org/docs/overview
* Docs: http://hexdocs.pm/phoenix
* Mailing list: http://groups.google.com/group/phoenix-talk
* Source: https://github.com/phoenixframework/phoenix

69
brunch-config.js Normal file
View file

@ -0,0 +1,69 @@
exports.config = {
// See http://brunch.io/#documentation for docs.
files: {
javascripts: {
joinTo: "js/app.js"
// To use a separate vendor.js bundle, specify two files path
// https://github.com/brunch/brunch/blob/stable/docs/config.md#files
// joinTo: {
// "js/app.js": /^(web\/static\/js)/,
// "js/vendor.js": /^(web\/static\/vendor)|(deps)/
// }
//
// To change the order of concatenation of files, explicitly mention here
// https://github.com/brunch/brunch/tree/master/docs#concatenation
// order: {
// before: [
// "web/static/vendor/js/jquery-2.1.1.js",
// "web/static/vendor/js/bootstrap.min.js"
// ]
// }
},
stylesheets: {
joinTo: "css/app.css"
},
templates: {
joinTo: "js/app.js"
}
},
conventions: {
// This option sets where we should place non-css and non-js assets in.
// By default, we set this to "/web/static/assets". Files in this directory
// will be copied to `paths.public`, which is "priv/static" by default.
assets: /^(web\/static\/assets)/
},
// Phoenix paths configuration
paths: {
// Dependencies and current project directories to watch
watched: [
"deps/phoenix/web/static",
"deps/phoenix_html/web/static",
"web/static",
"test/static"
],
// Where to compile files to
public: "priv/static"
},
// Configure your plugins
plugins: {
babel: {
// Do not use ES6 compiler in vendor code
ignore: [/web\/static\/vendor/]
}
},
modules: {
autoRequire: {
"js/app.js": ["web/static/js/app"]
}
},
npm: {
enabled: true
}
};

33
lib/proxichat.ex Normal file
View file

@ -0,0 +1,33 @@
defmodule Proxichat do
use Application
# See http://elixir-lang.org/docs/stable/elixir/Application.html
# for more information on OTP Applications
def start(_type, _args) do
import Supervisor.Spec, warn: false
children = [
# Start the endpoint when the application starts
supervisor(Proxichat.Endpoint, []),
# Start the Ecto repository
worker(Proxichat.Repo, []),
worker(Proxichat.ChannelAuthenticator, [[name: :proxichat_channel_auth_worker]])
# Here you could define other workers and supervisors as children
# worker(Proxichat.Worker, [arg1, arg2, arg3]),
]
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: Proxichat.Supervisor]
Supervisor.start_link(children, opts)
end
# Tell Phoenix to update the endpoint configuration
# whenever the application is updated.
def config_change(changed, _new, removed) do
Proxichat.Endpoint.config_change(changed, removed)
:ok
end
end

View file

@ -0,0 +1,63 @@
defmodule Proxichat.ChannelAuthenticator do
@moduledoc """
This module stores the list of current users in an ETS database and can be used to check if given user IDs are valid.
"""
use GenServer
@user_table :proxichat_users
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, :ok, opts)
end
def init(:ok) do
init_db
{:ok, nil}
end
def init_db() do
if :ets.info(@user_table) == :undefined do
:ets.new @user_table, [:named_table, :set, :public, read_concurrency: true]
end
end
def check_user(user_id) do
case get_user user_id do
nil -> create_user
user -> user
end
end
def get_user(user_id) do
case :ets.lookup @user_table, user_id do
[{_, user}] -> user.id
_ -> nil
end
end
def create_user() do
id = get_new_user_id
user = %{id: id}
update_user user
id
end
def user_set_network(user_id, network_id) do
get_user user_id
|> Map.put(:network, network_id)
|> update_user
end
def user_check_network(user_id, network_id) do
user = get_user user_id
Map.get(user, :network, nil) == network_id
end
def update_user(user) do
:ets.insert @user_table, {user.id, user}
end
defp get_new_user_id() do
UUID.uuid4
end
end

39
lib/proxichat/endpoint.ex Normal file
View file

@ -0,0 +1,39 @@
defmodule Proxichat.Endpoint do
use Phoenix.Endpoint, otp_app: :proxichat
socket "/socket", Proxichat.UserSocket
# Serve at "/" the static files from "priv/static" directory.
#
# You should set gzip to true if you are running phoenix.digest
# when deploying your static files in production.
plug Plug.Static,
at: "/", from: :proxichat, gzip: false,
only: ~w(css fonts images js favicon.ico robots.txt)
# Code reloading can be explicitly enabled under the
# :code_reloader configuration of your endpoint.
if code_reloading? do
socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
plug Phoenix.LiveReloader
plug Phoenix.CodeReloader
end
plug Plug.RequestId
plug Plug.Logger
plug Plug.Parsers,
parsers: [:urlencoded, :multipart, :json],
pass: ["*/*"],
json_decoder: Poison
plug Plug.MethodOverride
plug Plug.Head
plug Plug.Session,
store: :cookie,
key: "_proxichat_key",
signing_salt: "GRoJxS7B"
plug Proxichat.Router
end

View file

@ -0,0 +1,10 @@
defmodule Proxichat.NetworkUtils do
@accuracy 1
def get_network(lat, lon) do
[lat + 0.0, lon + 0.0]
|> Enum.map(fn coord -> Float.round coord, @accuracy end)
|> Enum.map(fn coord -> Float.to_string coord, decimals: @accuracy end)
|> Enum.join("x")
end
end

3
lib/proxichat/repo.ex Normal file
View file

@ -0,0 +1,3 @@
defmodule Proxichat.Repo do
use Ecto.Repo, otp_app: :proxichat
end

53
mix.exs Normal file
View file

@ -0,0 +1,53 @@
defmodule Proxichat.Mixfile do
use Mix.Project
def project do
[app: :proxichat,
version: "0.0.1",
elixir: "~> 1.0",
elixirc_paths: elixirc_paths(Mix.env),
compilers: [:phoenix] ++ Mix.compilers,
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
aliases: aliases,
deps: deps]
end
# Configuration for the OTP application.
#
# Type `mix help compile.app` for more information.
def application do
[mod: {Proxichat, []},
applications: [:phoenix, :phoenix_html, :cowboy, :logger,
:phoenix_ecto, :postgrex]]
end
# Specifies which paths to compile per environment.
defp elixirc_paths(:test), do: ["lib", "web", "test/support"]
defp elixirc_paths(_), do: ["lib", "web"]
# Specifies your project dependencies.
#
# Type `mix help deps` for examples and options.
defp deps do
[{:phoenix, "~> 1.0.3"},
{:phoenix_ecto, "~> 1.1"},
{:postgrex, ">= 0.0.0"},
{:phoenix_html, "~> 2.1"},
{:phoenix_live_reload, "~> 1.0", only: :dev},
{:cowboy, "~> 1.0"},
{:uuid, "~> 1.1"}
]
end
# Aliases are shortcut or tasks specific to the current project.
# For example, to create, migrate and run the seeds file at once:
#
# $ mix ecto.setup
#
# See the documentation for `Mix` for more info on aliases.
defp aliases do
["ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
"ecto.reset": ["ecto.drop", "ecto.setup"]]
end
end

15
mix.lock Normal file
View file

@ -0,0 +1,15 @@
%{"cowboy": {:hex, :cowboy, "1.0.4"},
"cowlib": {:hex, :cowlib, "1.0.2"},
"decimal": {:hex, :decimal, "1.1.0"},
"ecto": {:hex, :ecto, "1.0.6"},
"fs": {:hex, :fs, "0.9.2"},
"phoenix": {:hex, :phoenix, "1.0.3"},
"phoenix_ecto": {:hex, :phoenix_ecto, "1.2.0"},
"phoenix_html": {:hex, :phoenix_html, "2.2.0"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.0.1"},
"plug": {:hex, :plug, "1.0.3"},
"poison": {:hex, :poison, "1.5.0"},
"poolboy": {:hex, :poolboy, "1.5.1"},
"postgrex": {:hex, :postgrex, "0.9.1"},
"ranch": {:hex, :ranch, "1.2.0"},
"uuid": {:hex, :uuid, "1.1.1"}}

12
package.json Normal file
View file

@ -0,0 +1,12 @@
{
"repository": {
},
"dependencies": {
"brunch": "^1.8.5",
"babel-brunch": "^5.1.1",
"clean-css-brunch": ">= 1.0 < 1.8",
"css-brunch": ">= 1.0 < 1.8",
"javascript-brunch": ">= 1.0 < 1.8",
"uglify-js-brunch": ">= 1.0 < 1.8"
}
}

11
priv/repo/seeds.exs Normal file
View file

@ -0,0 +1,11 @@
# Script for populating the database. You can run it as:
#
# mix run priv/repo/seeds.exs
#
# Inside the script, you can read and write to any of your
# repositories directly:
#
# Proxichat.Repo.insert!(%SomeModel{})
#
# We recommend using the bang functions (`insert!`, `update!`
# and so on) as they will fail if something goes wrong.

View file

@ -0,0 +1,8 @@
defmodule Proxichat.PageControllerTest do
use Proxichat.ConnCase
test "GET /" do
conn = get conn(), "/"
assert html_response(conn, 200) =~ "Welcome to Phoenix!"
end
end

View file

@ -0,0 +1,40 @@
defmodule Proxichat.ChannelCase do
@moduledoc """
This module defines the test case to be used by
channel tests.
Such tests rely on `Phoenix.ChannelTest` and also
imports other functionality to make it easier
to build and query models.
Finally, if the test case interacts with the database,
it cannot be async. For this reason, every test runs
inside a transaction which is reset at the beginning
of the test unless the test case is marked as async.
"""
use ExUnit.CaseTemplate
using do
quote do
# Import conveniences for testing with channels
use Phoenix.ChannelTest
alias Proxichat.Repo
import Ecto.Model
import Ecto.Query, only: [from: 2]
# The default endpoint for testing
@endpoint Proxichat.Endpoint
end
end
setup tags do
unless tags[:async] do
Ecto.Adapters.SQL.restart_test_transaction(Proxichat.Repo, [])
end
:ok
end
end

41
test/support/conn_case.ex Normal file
View file

@ -0,0 +1,41 @@
defmodule Proxichat.ConnCase do
@moduledoc """
This module defines the test case to be used by
tests that require setting up a connection.
Such tests rely on `Phoenix.ConnTest` and also
imports other functionality to make it easier
to build and query models.
Finally, if the test case interacts with the database,
it cannot be async. For this reason, every test runs
inside a transaction which is reset at the beginning
of the test unless the test case is marked as async.
"""
use ExUnit.CaseTemplate
using do
quote do
# Import conveniences for testing with connections
use Phoenix.ConnTest
alias Proxichat.Repo
import Ecto.Model
import Ecto.Query, only: [from: 2]
import Proxichat.Router.Helpers
# The default endpoint for testing
@endpoint Proxichat.Endpoint
end
end
setup tags do
unless tags[:async] do
Ecto.Adapters.SQL.restart_test_transaction(Proxichat.Repo, [])
end
:ok
end
end

View file

@ -0,0 +1,59 @@
defmodule Proxichat.ModelCase do
@moduledoc """
This module defines the test case to be used by
model tests.
You may define functions here to be used as helpers in
your model tests. See `errors_on/2`'s definition as reference.
Finally, if the test case interacts with the database,
it cannot be async. For this reason, every test runs
inside a transaction which is reset at the beginning
of the test unless the test case is marked as async.
"""
use ExUnit.CaseTemplate
using do
quote do
alias Proxichat.Repo
import Ecto.Model
import Ecto.Query, only: [from: 2]
import Proxichat.ModelCase
end
end
setup tags do
unless tags[:async] do
Ecto.Adapters.SQL.restart_test_transaction(Proxichat.Repo, [])
end
:ok
end
@doc """
Helper for returning list of errors in model when passed certain data.
## Examples
Given a User model that lists `:name` as a required field and validates
`:password` to be safe, it would return:
iex> errors_on(%User{}, %{password: "password"})
[password: "is unsafe", name: "is blank"]
You could then write your assertion like:
assert {:password, "is unsafe"} in errors_on(%User{}, %{password: "password"})
You can also create the changeset manually and retrieve the errors
field directly:
iex> changeset = User.changeset(%User{}, password: "password")
iex> {:password, "is unsafe"} in changeset.errors
true
"""
def errors_on(model, data) do
model.__struct__.changeset(model, data).errors
end
end

6
test/test_helper.exs Normal file
View file

@ -0,0 +1,6 @@
ExUnit.start
Mix.Task.run "ecto.create", ["--quiet"]
Mix.Task.run "ecto.migrate", ["--quiet"]
Ecto.Adapters.SQL.begin_test_transaction(Proxichat.Repo)

View file

@ -0,0 +1,21 @@
defmodule Proxichat.ErrorViewTest do
use Proxichat.ConnCase, async: true
# Bring render/3 and render_to_string/3 for testing custom views
import Phoenix.View
test "renders 404.html" do
assert render_to_string(Proxichat.ErrorView, "404.html", []) ==
"Page not found"
end
test "render 500.html" do
assert render_to_string(Proxichat.ErrorView, "500.html", []) ==
"Server internal error"
end
test "render any other" do
assert render_to_string(Proxichat.ErrorView, "505.html", []) ==
"Server internal error"
end
end

View file

@ -0,0 +1,3 @@
defmodule Proxichat.LayoutViewTest do
use Proxichat.ConnCase, async: true
end

View file

@ -0,0 +1,3 @@
defmodule Proxichat.PageViewTest do
use Proxichat.ConnCase, async: true
end

View file

@ -0,0 +1,29 @@
defmodule Proxichat.MetaChannel do
use Phoenix.Channel
alias Phoenix.Token
alias Proxichat.Endpoint
alias Proxichat.ChannelAuthenticator
alias Proxichat.NetworkUtils
def join("meta:lobby", %{"lat" => lat, "lon" => lon}, socket) do
id = ChannelAuthenticator.create_user
network = NetworkUtils.get_network lat, lon
Endpoint.broadcast! "networks:#{network}", "here_i_am", %{user_id: id, lat: lat, lon: lon}
{:ok, %{user_id: id, goto: network}, socket}
end
def join("meta:" <> user_id, message, socket) do
case Token.verify socket, "user", message.token do
{:ok, ^user_id} -> {:ok, %{msg: "yo"}, socket}
_ -> {:error, %{reason: "NOOOOOOOO"}}
end
end
def handle_in("new_loc", %{"body" => %{"lat" => lat, "lon" => lon}, "user_id" => id}, socket) do
new_network = NetworkUtils.get_network lat, lon
Endpoint.broadcast! "networks:#{new_network}", "here_i_am", %{user_id: id, lat: lat, lon: lon}
push socket, "goto", %{goto: new_network}
{:noreply, socket}
end
end

View file

@ -0,0 +1,25 @@
defmodule Proxichat.NetworkChannel do
use Phoenix.Channel
alias Proxichat.ChannelAuthenticator
def join("networks:" <> network_id, %{"user_id" => id, "lat" => lat, "lon" => lon}, socket) do
send self, {:after_join, id, {lat, lon}}
{:ok, %{body: "Welcome to network #{network_id}, traveller! You shall henceforth be known as #{id}."}, socket}
end
def handle_in("new_msg", %{"body" => body, "user_id" => id}, socket) do
broadcast! socket, "new_msg", %{body: body, user_id: id}
{:noreply, socket}
end
def handle_in("kthxbye", %{"body" => _, "user_id" => id}, socket) do
broadcast! socket, "goodbye", %{user_id: id}
{:reply, :ok, socket}
end
def handle_info({:after_join, id, {lat, lon}}, socket) do
broadcast! socket, "fresh_blood", %{user_id: id, lat: lat, lon: lon}
{:noreply, socket}
end
end

View file

@ -0,0 +1,38 @@
defmodule Proxichat.UserSocket do
use Phoenix.Socket
## Channels
channel "meta:*", Proxichat.MetaChannel
channel "networks:*", Proxichat.NetworkChannel
## Transports
transport :websocket, Phoenix.Transports.WebSocket
# transport :longpoll, Phoenix.Transports.LongPoll
# Socket params are passed from the client and can
# be used to verify and authenticate a user. After
# verification, you can put default assigns into
# the socket that will be set for all channels, ie
#
# {:ok, assign(socket, :user_id, verified_user_id)}
#
# To deny connection, return `:error`.
#
# See `Phoenix.Token` documentation for examples in
# performing token verification on connect.
def connect(_params, socket) do
{:ok, socket}
end
# Socket id's are topics that allow you to identify all sockets for a given user:
#
# def id(socket), do: "users_socket:#{socket.assigns.user_id}"
#
# Would allow you to broadcast a "disconnect" event and terminate
# all active sockets and channels for a given user:
#
# Proxichat.Endpoint.broadcast("users_socket:" <> user.id, "disconnect", %{})
#
# Returning `nil` makes this socket anonymous.
def id(_socket), do: nil
end

View file

@ -0,0 +1,7 @@
defmodule Proxichat.PageController do
use Proxichat.Web, :controller
def index(conn, _params) do
render conn, "index.html"
end
end

26
web/router.ex Normal file
View file

@ -0,0 +1,26 @@
defmodule Proxichat.Router do
use Proxichat.Web, :router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
end
pipeline :api do
plug :accepts, ["json"]
end
scope "/", Proxichat do
pipe_through :browser # Use the default browser stack
get "/", PageController, :index
end
# Other scopes may use custom stacks.
# scope "/api", Proxichat do
# pipe_through :api
# end
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

View file

@ -0,0 +1,5 @@
# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
#
# To ban all spiders from the entire site uncomment the next two lines:
# User-agent: *
# Disallow: /

89
web/static/css/app.css Normal file

File diff suppressed because one or more lines are too long

21
web/static/js/app.js Normal file
View file

@ -0,0 +1,21 @@
// Brunch automatically concatenates all files in your
// watched paths. Those paths can be configured at
// config.paths.watched in "brunch-config.js".
//
// However, those files will only be executed if
// explicitly imported. The only exception are files
// in vendor, which are never wrapped in imports and
// therefore are always executed.
// Import dependencies
//
// If you no longer want to use a dependency, remember
// to also remove its path from "config.paths.watched".
import "deps/phoenix_html/web/static/js/phoenix_html"
// Import local files
//
// Local files can be imported directly using relative
// paths "./socket" or full ones "web/static/js/socket".
import socket from "./socket"

256
web/static/js/socket.js Normal file
View file

@ -0,0 +1,256 @@
// 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 "deps/phoenix/web/static/js/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.
socket.connect();
let start_loc = [61.2, 24.6];
let current_loc = start_loc;
let user_id = null;
let network = null;
let network_channel = null;
let text_input = document.getElementById('chat_text_field');
let input_form = document.getElementById('chat_form');
let chat_log = document.getElementById('chat_log');
let other_locs = {};
input_form.addEventListener('submit', e => {
network_out(text_input.value);
text_input.value = '';
e.preventDefault();
});
// Now that you are connected, you can join channels with a topic:
let lobby_channel = socket.channel('meta:lobby', {
lat: start_loc[0],
lon: start_loc[1]
});
lobby_channel.join()
.receive('ok', resp => {
console.log('Joined successfully', resp);
user_id = resp.user_id;
window.userToken = resp.token;
join_network(resp.goto);
})
.receive('error', resp => {
console.log('Unable to join', resp);
});
lobby_channel.on('goto', msg => {
change_network(msg.goto);
});
function connect_network() {
console.log('Connecting to ' + network + '…');
network_channel = socket.channel('networks:' + network, {
user_id: user_id,
lat: current_loc[0],
lon: current_loc[1]
});
network_channel.join()
.receive('ok', resp => {
print_log('Joined network ' + network + '.');
text_input.disabled = false;
network_in(resp);
})
.receive('error', resp => {
throw new Error('Could not join network!');
});
network_channel.on('new_msg', network_in);
network_channel.on('here_i_am', network_loc_in);
network_channel.on('fresh_blood', network_join);
network_channel.on('goodbye', network_part);
}
function network_in(msg) {
let prefix = null;
if (msg.user_id) {
prefix = '<' + msg.user_id + '> ';
}
print_log(msg.body, prefix)
}
function network_loc_in(data) {
// I don't hate you
if (data.user_id == user_id) return;
handle_loc_in(data.user_id, data.lat, data.lon);
}
function handle_loc_in(id, lat, lon) {
clear_user_loc(id)
let marker = new google.maps.Marker({
map: map,
position: new google.maps.LatLng(lat, lon),
draggable: false,
icon: {url: '/images/pissed_off.png'}
});
other_locs[id] = {
marker: marker
};
}
function clear_user_loc(id) {
if (id in other_locs) {
other_locs[id].marker.setMap(null);
}
}
function clear_all_user_loc() {
for (var id in other_locs) {
if (other_locs.hasOwnProperty(id)) {
clear_user_loc(id);
}
}
}
function network_join(data) {
if (data.user_id === user_id) return;
handle_loc_in(data.user_id, data.lat, data.lon);
print_log(data.user_id + ' has joined the network!');
}
function network_part(data) {
if (data.user_id === user_id) {
clear_all_user_loc();
print_log('You left the network ' + network + '. You\'re better off without those suckers anyway.');
}
else {
clear_user_loc(data.user_id);
print_log(data.user_id + ' has left… 😭');
}
}
function network_out(msg) {
if (msg === '') return;
send_to('new_msg', network_channel, msg);
}
function update_loc(lat, lon) {
send_to('new_loc', lobby_channel, {
lat: lat,
lon: lon
});
}
function send_to(type, channel, data) {
return channel.push(type, {body: data, user_id: user_id});
}
function print_log(msg, prefix) {
prefix = prefix || '*** ';
let keep_pos = false;
if (chat_log.scrollHeight <= (chat_log.scrollTop + chat_log.offsetHeight)) {
keep_pos = true;
}
chat_log.value += '\n' + prefix + msg;
if (keep_pos) {
chat_log.scrollTop = chat_log.scrollHeight;
}
}
function change_network(new_network) {
if (new_network === network) return;
text_input.disabled = true;
send_to('kthxbye', network_channel, '').receive('ok', resp => {
network_channel.leave().receive('ok', resp => {
join_network(new_network);
});
});
}
function join_network(new_network) {
network = new_network;
connect_network();
}
let map = null;
let marker = null;
function init_map() {
var myOptions = {
zoom: 12,
center: new google.maps.LatLng(start_loc[0], start_loc[1]),
mapTypeId: google.maps.MapTypeId.ROADMAP
};
map = new google.maps.Map(document.getElementById('gmap_canvas'), myOptions);
marker = new google.maps.Marker({
map: map,
position: new google.maps.LatLng(start_loc[0], start_loc[1]),
draggable: true
});
google.maps.event.addListener(marker, 'dragend', evt => {
current_loc = [evt.latLng.lat(), evt.latLng.lng()];
update_loc(current_loc[0], current_loc[1]);
});
}
google.maps.event.addDomListener(window, 'load', init_map);
export default socket

View file

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<title>Proxichat</title>
<link rel="stylesheet" href="<%= static_path(@conn, "/css/app.css") %>">
</head>
<body>
<div class="container" role="main">
<%= @inner %>
</div> <!-- /container -->
<script src="<%= static_path(@conn, "/js/app.js") %>"></script>
</body>
</html>

View file

@ -0,0 +1,16 @@
<script type="text/javascript" src="//maps.google.com/maps/api/js?sensor=false"></script>
<div style="overflow:hidden;height:300px;width:100%;">
<div id="gmap_canvas" style="height:300px;width:100%;"></div>
<style>
#gmap_canvas img {
max-width: none !important;
background: none !important
}
</style>
<a class="google-map-code" href="http://www.themecircle.net" id="get-map-data"></a>
</div>
<form id="chat_form" action="" method="post">
<textarea id="chat_log" placeholder="Start chatting!" readonly></textarea>
<input id="chat_text_field" type="text" placeholder="Type here to chat, press enter to send" autocomplete="off" disabled="disabled" />
</form>

17
web/views/error_view.ex Normal file
View file

@ -0,0 +1,17 @@
defmodule Proxichat.ErrorView do
use Proxichat.Web, :view
def render("404.html", _assigns) do
"Page not found"
end
def render("500.html", _assigns) do
"Server internal error"
end
# In case no render clause matches or no
# template is found, let's render it as 500
def template_not_found(_template, assigns) do
render "500.html", assigns
end
end

3
web/views/layout_view.ex Normal file
View file

@ -0,0 +1,3 @@
defmodule Proxichat.LayoutView do
use Proxichat.Web, :view
end

3
web/views/page_view.ex Normal file
View file

@ -0,0 +1,3 @@
defmodule Proxichat.PageView do
use Proxichat.Web, :view
end

76
web/web.ex Normal file
View file

@ -0,0 +1,76 @@
defmodule Proxichat.Web do
@moduledoc """
A module that keeps using definitions for controllers,
views and so on.
This can be used in your application as:
use Proxichat.Web, :controller
use Proxichat.Web, :view
The definitions below will be executed for every view,
controller, etc, so keep them short and clean, focused
on imports, uses and aliases.
Do NOT define functions inside the quoted expressions
below.
"""
def model do
quote do
use Ecto.Model
import Ecto.Changeset
import Ecto.Query, only: [from: 1, from: 2]
end
end
def controller do
quote do
use Phoenix.Controller
alias Proxichat.Repo
import Ecto.Model
import Ecto.Query, only: [from: 1, from: 2]
import Proxichat.Router.Helpers
end
end
def view do
quote do
use Phoenix.View, root: "web/templates"
# Import convenience functions from controllers
import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1]
# Use all HTML functionality (forms, tags, etc)
use Phoenix.HTML
import Proxichat.Router.Helpers
end
end
def router do
quote do
use Phoenix.Router
end
end
def channel do
quote do
use Phoenix.Channel
alias Proxichat.Repo
import Ecto.Model
import Ecto.Query, only: [from: 1, from: 2]
end
end
@doc """
When used, dispatch to the appropriate controller/view/etc.
"""
defmacro __using__(which) when is_atom(which) do
apply(__MODULE__, which, [])
end
end