mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Remove example directory
This commit is contained in:
17
examples/.gitignore
vendored
17
examples/.gitignore
vendored
@@ -1,17 +0,0 @@
|
||||
# Each individual example should include 'local' in foo/.meteor/.gitignore.
|
||||
# However, we also include this top-level .gitignore so that the following
|
||||
# situation:
|
||||
# $ git checkout some-branch-with-a-new-example
|
||||
# $ (cd examples/unfinished/new-example; meteor)
|
||||
# $ git checkout devel
|
||||
# doesn't leave you with tons of files in
|
||||
# examples/unfinished/new-example/.meteor/local showing up in your 'git status'.
|
||||
|
||||
*/.meteor/local
|
||||
*/*/.meteor/local
|
||||
|
||||
# We don't want to check example project id files into the main meteor
|
||||
# repo... but we do want users who create apps from the examples to check
|
||||
# *their* project id files into their repos, so we put this ignore line
|
||||
# here rather than in the examples/FOO/.meteor/.gitignore.
|
||||
*/.meteor/.id
|
||||
@@ -1 +0,0 @@
|
||||
0.6.0
|
||||
@@ -1 +0,0 @@
|
||||
local
|
||||
@@ -1,8 +0,0 @@
|
||||
# Meteor packages used by this project, one per line.
|
||||
#
|
||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||
# but you can also edit it by hand.
|
||||
|
||||
standard-app-packages
|
||||
insecure
|
||||
preserve-inputs
|
||||
@@ -1 +0,0 @@
|
||||
0.7.0.1
|
||||
@@ -1,15 +0,0 @@
|
||||
<head>
|
||||
<title>Client Info</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{{> info}}
|
||||
</body>
|
||||
|
||||
<template name="info">
|
||||
<h1>Client Info</h1>
|
||||
|
||||
<p>Information about this client available on the server:</p>
|
||||
|
||||
<pre>{{info}}</pre>
|
||||
</template>
|
||||
@@ -1,19 +0,0 @@
|
||||
if (Meteor.isServer) {
|
||||
Meteor.publish("clientInfo", function () {
|
||||
var self = this;
|
||||
self.added("clientInfo", "info", {
|
||||
clientAddress: self.connection.clientAddress,
|
||||
httpHeaders: self.connection.httpHeaders
|
||||
});
|
||||
self.ready();
|
||||
});
|
||||
}
|
||||
|
||||
if (Meteor.isClient) {
|
||||
Meteor.subscribe("clientInfo");
|
||||
var ClientInfo = new Mongo.Collection("clientInfo");
|
||||
|
||||
Template.info.info = function () {
|
||||
return EJSON.stringify(ClientInfo.findOne("info"), {indent: true});
|
||||
};
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
0.6.0
|
||||
@@ -1 +0,0 @@
|
||||
local
|
||||
@@ -1,6 +0,0 @@
|
||||
# Meteor packages used by this project, one per line.
|
||||
#
|
||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||
# but you can also edit it by hand.
|
||||
|
||||
standard-app-packages
|
||||
@@ -1,13 +0,0 @@
|
||||
# Defer in Inactive Tab
|
||||
|
||||
Tests that `Meteor.defer` works in an inactive tab in iOS Safari.
|
||||
|
||||
(`setTimeout` and `setInterval` events aren't delivered to inactive
|
||||
tabs in iOS Safari until they become active again).
|
||||
|
||||
Sadly we have to run the test manually because scripts aren't allowed
|
||||
to open windows themselves except in response to user events.
|
||||
|
||||
This test will not run on Chrome for iOS because the storage event is
|
||||
not implemented in that browser. Also doesn't attempt to run on
|
||||
versions of IE that don't support `window.addEventListener`.
|
||||
@@ -1,52 +0,0 @@
|
||||
<head>
|
||||
<title>defer in inactive tab</title>
|
||||
<meta name="viewport" content="width=device-width">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{{> route}}
|
||||
</body>
|
||||
|
||||
<template name="route">
|
||||
{{#if isParent}}
|
||||
{{> parent}}
|
||||
{{else}}
|
||||
{{> child}}
|
||||
{{/if}}
|
||||
</template>
|
||||
|
||||
<template name="parent">
|
||||
<h1>Test Defer in Inactive Tab</h1>
|
||||
|
||||
<p>
|
||||
Step one: open second tab:
|
||||
<button id="openTab">Open Tab</button>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Step two: run test:
|
||||
<button id="runTest">Run Test</button>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
In a successful test the test status will immediately change to
|
||||
"test successful". (If you switch to the child tab yourself and
|
||||
that makes the test claim to be successful, that's actually an
|
||||
invalid test because you're letting the child tab become the
|
||||
active tab).
|
||||
</p>
|
||||
|
||||
<p style="padding: 1em; outline: 1px solid gray">
|
||||
Test status: <b>{{testStatus}}</b>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
After the test has run successfully you can close the child tab.
|
||||
</p>
|
||||
|
||||
</template>
|
||||
|
||||
<template name="child">
|
||||
<p>This is the child.</p>
|
||||
<p>Switch back to the first tab and run the test.</p>
|
||||
</template>
|
||||
@@ -1,57 +0,0 @@
|
||||
if (Meteor.isClient) {
|
||||
|
||||
var isParent = (window.location.pathname === '/');
|
||||
var isChild = ! isParent;
|
||||
|
||||
Template.route.isParent = function () {
|
||||
return isParent;
|
||||
};
|
||||
|
||||
Template.parent.testStatus = function () {
|
||||
return Session.get('testStatus');
|
||||
};
|
||||
|
||||
Template.parent.events({
|
||||
'click #openTab': function () {
|
||||
window.open('/child');
|
||||
},
|
||||
|
||||
'click #runTest': function () {
|
||||
if (localStorage.getItem('ping') === '!' ||
|
||||
localStorage.getItem('pong') === '!') {
|
||||
Session.set('testStatus', 'Test already run. Close the second tab (if open), refresh this page, and run again.');
|
||||
}
|
||||
else {
|
||||
localStorage.setItem('ping', '!');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (isParent) {
|
||||
Session.set('testStatus', '');
|
||||
|
||||
Meteor.startup(function () {
|
||||
localStorage.setItem('ping', null);
|
||||
localStorage.setItem('pong', null);
|
||||
});
|
||||
window.addEventListener('storage', function (event) {
|
||||
if (event.key === 'pong' && event.newValue === '!') {
|
||||
Session.set('testStatus', 'test successful');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (isChild) {
|
||||
window.addEventListener('storage', function (event) {
|
||||
if (event.key === 'ping' && event.newValue === '!') {
|
||||
// If we used setTimeout here in iOS Safari it wouldn't
|
||||
// work (unless we switched tabs) because setTimeout and
|
||||
// setInterval events don't fire in inactive tabs.
|
||||
Meteor.defer(function () {
|
||||
localStorage.setItem('pong', '!');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
local
|
||||
@@ -1,10 +0,0 @@
|
||||
# Meteor packages used by this project, one per line.
|
||||
#
|
||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||
# but you can also edit it by hand.
|
||||
|
||||
standard-app-packages
|
||||
autopublish
|
||||
insecure
|
||||
preserve-inputs
|
||||
ui
|
||||
@@ -1 +0,0 @@
|
||||
none
|
||||
@@ -1,18 +0,0 @@
|
||||
/* CSS declarations go here */
|
||||
|
||||
|
||||
* { margin: 0; padding: 0 }
|
||||
|
||||
#grid td {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.color0 { background: #eee; }
|
||||
.color1 { background: #d8f; }
|
||||
.color2 { background: #8c3; }
|
||||
.color3 { background: #39d; }
|
||||
.color4 { background: #d96; }
|
||||
.color5 { background: #a95; }
|
||||
@@ -1,7 +0,0 @@
|
||||
<head>
|
||||
<title>domrange-grid</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
</body>
|
||||
@@ -1,105 +0,0 @@
|
||||
if (Meteor.isClient) {
|
||||
Meteor.startup(function () {
|
||||
var N = 10;
|
||||
var numColors = 6;
|
||||
var colors = [];
|
||||
for(var z = 0; z < numColors; z++)
|
||||
colors[z] = z;
|
||||
|
||||
var guid = 1;
|
||||
|
||||
var table = $('<table id="grid"></table>');
|
||||
$(table).appendTo("body");
|
||||
var rows = [];
|
||||
var tableContent = new UI.DomRange;
|
||||
var makeCell = function (row) {
|
||||
var cells = row.cells;
|
||||
var tr = row.dom.elements()[0];
|
||||
var cell = {color: Random.choice(colors),
|
||||
guid: String(guid++)};
|
||||
cell.dom = new UI.DomRange(cell);
|
||||
cells.push(cell);
|
||||
cell.dom.add(cell.guid, $('<td class="color' +
|
||||
cell.color + '">' +
|
||||
cell.color + '</td>'));
|
||||
row.content.add(cell.guid, cell);
|
||||
};
|
||||
var makeRow = function () {
|
||||
var row = {cells: [], guid: String(guid++),
|
||||
content: new UI.DomRange};
|
||||
row.dom = new UI.DomRange(row);
|
||||
rows.push(row);
|
||||
tableContent.add(row.guid, row);
|
||||
var tr = $("<tr></tr>")[0];
|
||||
row.dom.add(tr);
|
||||
UI.DomRange.insert(row.content, tr);
|
||||
var cells = row.cells;
|
||||
for(var c = 0; c < N; c++)
|
||||
makeCell(row);
|
||||
};
|
||||
for (var r = 0; r < N; r++)
|
||||
makeRow();
|
||||
|
||||
UI.DomRange.insert(tableContent, table[0]);
|
||||
|
||||
$(document).on('keydown', function (evt) {
|
||||
var deltaN = 0;
|
||||
var deltaC = 0;
|
||||
if (evt.which === 38) {
|
||||
deltaN = 1; // up
|
||||
} else if (evt.which === 40) {
|
||||
deltaN = -1; // down
|
||||
} else if (evt.which === 37) {
|
||||
deltaC = -1; // left
|
||||
} else if (evt.which === 39) {
|
||||
deltaC = 1; // right
|
||||
} else if (evt.which === 32) {
|
||||
// spacebar
|
||||
var row0 = rows.shift();
|
||||
rows.push(row0);
|
||||
tableContent.moveBefore(row0.guid, null);
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
var row = rows[i];
|
||||
var cell0 = row.cells.shift();
|
||||
row.cells.push(cell0);
|
||||
row.content.moveBefore(cell0.guid, null);
|
||||
}
|
||||
}
|
||||
|
||||
if (deltaN === 1) {
|
||||
N += 1;
|
||||
for (var i = 0; i < N - 1; i++)
|
||||
// lengthen old rows
|
||||
makeCell(rows[i]);
|
||||
makeRow();
|
||||
} else if (deltaN === -1) {
|
||||
if (N === 0)
|
||||
return;
|
||||
N -= 1;
|
||||
tableContent.remove(rows[N].guid);
|
||||
rows.length = N;
|
||||
for (var i = 0; i < N; i++) {
|
||||
var row = rows[i];
|
||||
row.content.remove(row.cells[N].guid);
|
||||
rows[i].cells.length = N;
|
||||
}
|
||||
}
|
||||
|
||||
if (deltaC) {
|
||||
for (var r = 0; r < N; r++) {
|
||||
var row = rows[r];
|
||||
for (var c = 0; c < N; c++) {
|
||||
var cell = row.cells[c];
|
||||
var td = cell.dom.elements()[0];
|
||||
var color =
|
||||
(cell.color =
|
||||
(cell.color + deltaC + numColors)
|
||||
% numColors);
|
||||
td.innerHTML = color;
|
||||
td.className = 'color' + color;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
1
examples/other/login-demo/.meteor/.gitignore
vendored
1
examples/other/login-demo/.meteor/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
local
|
||||
@@ -1,8 +0,0 @@
|
||||
# Meteor packages used by this project, one per line.
|
||||
#
|
||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||
# but you can also edit it by hand.
|
||||
|
||||
preserve-inputs
|
||||
accounts-google
|
||||
standard-app-packages
|
||||
@@ -1 +0,0 @@
|
||||
none
|
||||
@@ -1,32 +0,0 @@
|
||||
|
||||
* { padding: 0; margin: 0; }
|
||||
|
||||
#main {
|
||||
margin: 50px;
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.msgDiv {
|
||||
margin: 30px;
|
||||
}
|
||||
|
||||
a {
|
||||
padding: 5px;
|
||||
background: #ddd;
|
||||
}
|
||||
|
||||
#readme {
|
||||
margin: 20px;
|
||||
border: 1px solid #ccc;
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 16px;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 16px;
|
||||
padding-left: 30px;
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
<head>
|
||||
<title>login-demo</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="main">
|
||||
{{> main}}
|
||||
</div>
|
||||
<div id="readme">
|
||||
<p>This is a minimal app where you need to log in to see the database. There's also a loading screen while logging in and until the initial data is loaded.</p>
|
||||
<p>There are three top-level screens corresponding to the three possible states of the app:</p>
|
||||
<ul>
|
||||
<li>Logging in / Loading — when <code>{{loggingIn}}</code> is true</li>
|
||||
<li>Logged in — when there is a <code>{{currentUser}}</code></li>
|
||||
<li>Logged out — otherwise</li>
|
||||
</ul>
|
||||
<p>If you reload the page while logged in, you'll start in the "logging in" state and see the "Loading..." message until the data loads. Because logging in doesn't complete until all subscriptions have been rerun and finished loading, and the app only serves data when you're logged in, the "logging in" state encompasses loading the initial data for all subscriptions and is the only loading screen we need.</p>
|
||||
<p>To configure this app for Google auth, the easiest way is to add the <code>accounts-ui</code> package, add <code>{{> loginButtons}}</code> to the end of the body, and use the configuration wizard.</p>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<template name="main">
|
||||
{{#if loggingIn}}
|
||||
<div class="loading">Loading...</div>
|
||||
{{else}}
|
||||
{{#if currentUser}}
|
||||
<div class="msgDiv">
|
||||
Signed in as: {{currentUser.services.google.email}}
|
||||
</div>
|
||||
<a href="#" id="logout">Sign out</a>
|
||||
{{else}}
|
||||
<a href="#" id="login">Sign In With Google</a>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
<div class="msgDiv">
|
||||
Client can see {{numGizmos}} gizmos.
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,57 +0,0 @@
|
||||
Gizmos = new Mongo.Collection("gizmos");
|
||||
|
||||
if (Meteor.isClient) {
|
||||
|
||||
var allGizmos = Meteor.subscribe("allGizmos");
|
||||
|
||||
Template.main.numGizmos = function () {
|
||||
return Gizmos.find().count();
|
||||
};
|
||||
|
||||
Template.main.events({
|
||||
'click #login': function (evt) {
|
||||
Meteor.loginWithGoogle(function (err) {
|
||||
if (err)
|
||||
Meteor._debug(err);
|
||||
});
|
||||
evt.preventDefault();
|
||||
},
|
||||
'click #logout': function (evt) {
|
||||
Meteor.logout(function (err) {
|
||||
if (err)
|
||||
Meteor._debug(err);
|
||||
});
|
||||
evt.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (Meteor.isServer) {
|
||||
|
||||
Meteor.startup(function () {
|
||||
// populate the Gizmos collection if it's empty on startup
|
||||
if (Gizmos.find().count() === 0) {
|
||||
for (var i = 0; i < 1000; i++)
|
||||
Gizmos.insert({ name: "Gizmo " + i });
|
||||
}
|
||||
});
|
||||
|
||||
Meteor.publish("allGizmos", function () {
|
||||
// Only publish the Gizmos if user is logged in.
|
||||
var user = this.userId && Meteor.users.findOne(this.userId);
|
||||
if (user) {
|
||||
// potentially put other conditions on user here...
|
||||
return Gizmos.find({});
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
Meteor.publish(null, function () {
|
||||
// If logged in, autopublish the current user's Google email
|
||||
// to the client (which isn't published by default).
|
||||
return this.userId &&
|
||||
Meteor.users.find(this.userId,
|
||||
{fields: {'services.google.email': 1}});
|
||||
});
|
||||
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
# This file contains information which helps Meteor properly upgrade your
|
||||
# app when you run 'meteor update'. You should check it into version control
|
||||
# with your project.
|
||||
|
||||
notices-for-0.9.0
|
||||
notices-for-0.9.1
|
||||
0.9.4-platform-file
|
||||
1
examples/other/parties/.meteor/.gitignore
vendored
1
examples/other/parties/.meteor/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
local
|
||||
@@ -1,14 +0,0 @@
|
||||
# Meteor packages used by this project, one per line.
|
||||
#
|
||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||
# but you can also edit it by hand.
|
||||
|
||||
standard-app-packages
|
||||
accounts-ui
|
||||
accounts-password
|
||||
d3
|
||||
bootstrap
|
||||
email
|
||||
accounts-facebook
|
||||
accounts-twitter
|
||||
audit-argument-checks
|
||||
@@ -1,2 +0,0 @@
|
||||
server
|
||||
browser
|
||||
@@ -1 +0,0 @@
|
||||
METEOR@0.9.4
|
||||
@@ -1,72 +0,0 @@
|
||||
accounts-base@1.1.2
|
||||
accounts-facebook@1.0.2
|
||||
accounts-oauth@1.1.2
|
||||
accounts-password@1.0.3
|
||||
accounts-twitter@1.0.2
|
||||
accounts-ui-unstyled@1.1.3
|
||||
accounts-ui@1.1.2
|
||||
application-configuration@1.0.3
|
||||
audit-argument-checks@1.0.1
|
||||
autoupdate@1.1.2
|
||||
base64@1.0.1
|
||||
binary-heap@1.0.1
|
||||
blaze-tools@1.0.1
|
||||
blaze@2.0.2
|
||||
boilerplate-generator@1.0.1
|
||||
bootstrap@1.0.1
|
||||
callback-hook@1.0.1
|
||||
check@1.0.2
|
||||
ctl-helper@1.0.4
|
||||
ctl@1.0.2
|
||||
d3@1.0.0
|
||||
ddp@1.0.10
|
||||
deps@1.0.5
|
||||
ejson@1.0.4
|
||||
email@1.0.4
|
||||
facebook@1.1.1
|
||||
fastclick@1.0.1
|
||||
follower-livedata@1.0.2
|
||||
geojson-utils@1.0.1
|
||||
html-tools@1.0.2
|
||||
htmljs@1.0.2
|
||||
http@1.0.7
|
||||
id-map@1.0.1
|
||||
jquery@1.0.1
|
||||
json@1.0.1
|
||||
less@1.0.10
|
||||
livedata@1.0.11
|
||||
localstorage@1.0.1
|
||||
logging@1.0.4
|
||||
meteor-platform@1.1.2
|
||||
meteor@1.1.2
|
||||
minifiers@1.1.1
|
||||
minimongo@1.0.4
|
||||
mobile-status-bar@1.0.1
|
||||
mongo@1.0.7
|
||||
npm-bcrypt@0.7.7
|
||||
oauth1@1.1.1
|
||||
oauth2@1.1.1
|
||||
oauth@1.1.1
|
||||
observe-sequence@1.0.3
|
||||
ordered-dict@1.0.1
|
||||
random@1.0.1
|
||||
reactive-dict@1.0.4
|
||||
reactive-var@1.0.3
|
||||
reload@1.1.1
|
||||
retry@1.0.1
|
||||
routepolicy@1.0.2
|
||||
service-configuration@1.0.2
|
||||
session@1.0.3
|
||||
sha@1.0.1
|
||||
spacebars-compiler@1.0.3
|
||||
spacebars@1.0.3
|
||||
srp@1.0.1
|
||||
standard-app-packages@1.0.3
|
||||
templating@1.0.8
|
||||
tracker@1.0.3
|
||||
twitter@1.1.1
|
||||
ui@1.0.4
|
||||
underscore@1.0.1
|
||||
url@1.0.1
|
||||
webapp-hashing@1.0.1
|
||||
webapp@1.1.3
|
||||
@@ -1,285 +0,0 @@
|
||||
// All Tomorrow's Parties -- client
|
||||
|
||||
Meteor.subscribe("directory");
|
||||
Meteor.subscribe("parties");
|
||||
|
||||
// If no party selected, or if the selected party was deleted, select one.
|
||||
Meteor.startup(function () {
|
||||
Tracker.autorun(function () {
|
||||
var selected = Session.get("selected");
|
||||
if (! selected || ! Parties.findOne(selected)) {
|
||||
var party = Parties.findOne();
|
||||
if (party)
|
||||
Session.set("selected", party._id);
|
||||
else
|
||||
Session.set("selected", null);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Party details sidebar
|
||||
|
||||
Template.details.helpers({
|
||||
party: function () {
|
||||
return Parties.findOne(Session.get("selected"));
|
||||
},
|
||||
anyParties: function () {
|
||||
return Parties.find().count() > 0;
|
||||
},
|
||||
creatorName: function () {
|
||||
var owner = Meteor.users.findOne(this.owner);
|
||||
if (owner._id === Meteor.userId())
|
||||
return "me";
|
||||
return displayName(owner);
|
||||
},
|
||||
canRemove: function () {
|
||||
return this.owner === Meteor.userId() && attending(this) === 0;
|
||||
},
|
||||
maybeChosen: function (what) {
|
||||
var myRsvp = _.find(this.rsvps, function (r) {
|
||||
return r.user === Meteor.userId();
|
||||
}) || {};
|
||||
|
||||
return what == myRsvp.rsvp ? "chosen btn-inverse" : "";
|
||||
}
|
||||
});
|
||||
|
||||
Template.details.events({
|
||||
'click .rsvp_yes': function () {
|
||||
Meteor.call("rsvp", Session.get("selected"), "yes");
|
||||
return false;
|
||||
},
|
||||
'click .rsvp_maybe': function () {
|
||||
Meteor.call("rsvp", Session.get("selected"), "maybe");
|
||||
return false;
|
||||
},
|
||||
'click .rsvp_no': function () {
|
||||
Meteor.call("rsvp", Session.get("selected"), "no");
|
||||
return false;
|
||||
},
|
||||
'click .invite': function () {
|
||||
openInviteDialog();
|
||||
return false;
|
||||
},
|
||||
'click .remove': function () {
|
||||
Parties.remove(this._id);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Party attendance widget
|
||||
|
||||
Template.attendance.helpers({
|
||||
rsvpName: function () {
|
||||
var user = Meteor.users.findOne(this.user);
|
||||
return displayName(user);
|
||||
},
|
||||
|
||||
outstandingInvitations: function () {
|
||||
var party = Parties.findOne(this._id);
|
||||
return Meteor.users.find({$and: [
|
||||
{_id: {$in: party.invited}}, // they're invited
|
||||
{_id: {$nin: _.pluck(party.rsvps, 'user')}} // but haven't RSVP'd
|
||||
]});
|
||||
},
|
||||
|
||||
invitationName: function () {
|
||||
return displayName(this);
|
||||
},
|
||||
|
||||
rsvpIs: function (what) {
|
||||
return this.rsvp === what;
|
||||
},
|
||||
|
||||
nobody: function () {
|
||||
return ! this.public && (this.rsvps.length + this.invited.length === 0);
|
||||
},
|
||||
|
||||
canInvite: function () {
|
||||
return ! this.public && this.owner === Meteor.userId();
|
||||
}
|
||||
});
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Map display
|
||||
|
||||
// Use jquery to get the position clicked relative to the map element.
|
||||
var coordsRelativeToElement = function (element, event) {
|
||||
var offset = $(element).offset();
|
||||
var x = event.pageX - offset.left;
|
||||
var y = event.pageY - offset.top;
|
||||
return { x: x, y: y };
|
||||
};
|
||||
|
||||
Template.map.events({
|
||||
'mousedown circle, mousedown text': function (event, template) {
|
||||
Session.set("selected", event.currentTarget.id);
|
||||
},
|
||||
'dblclick .map': function (event, template) {
|
||||
if (! Meteor.userId()) // must be logged in to create events
|
||||
return;
|
||||
var coords = coordsRelativeToElement(event.currentTarget, event);
|
||||
openCreateDialog(coords.x / 500, coords.y / 500);
|
||||
}
|
||||
});
|
||||
|
||||
Template.map.onRendered(function () {
|
||||
var self = this;
|
||||
self.node = self.find("svg");
|
||||
|
||||
if (! self.handle) {
|
||||
self.handle = Tracker.autorun(function () {
|
||||
var selected = Session.get('selected');
|
||||
var selectedParty = selected && Parties.findOne(selected);
|
||||
var radius = function (party) {
|
||||
return 10 + Math.sqrt(attending(party)) * 10;
|
||||
};
|
||||
|
||||
// Draw a circle for each party
|
||||
var updateCircles = function (group) {
|
||||
group.attr("id", function (party) { return party._id; })
|
||||
.attr("cx", function (party) { return party.x * 500; })
|
||||
.attr("cy", function (party) { return party.y * 500; })
|
||||
.attr("r", radius)
|
||||
.attr("class", function (party) {
|
||||
return party.public ? "public" : "private";
|
||||
})
|
||||
.style('opacity', function (party) {
|
||||
return selected === party._id ? 1 : 0.6;
|
||||
});
|
||||
};
|
||||
|
||||
var circles = d3.select(self.node).select(".circles").selectAll("circle")
|
||||
.data(Parties.find().fetch(), function (party) { return party._id; });
|
||||
|
||||
updateCircles(circles.enter().append("circle"));
|
||||
updateCircles(circles.transition().duration(250).ease("cubic-out"));
|
||||
circles.exit().transition().duration(250).attr("r", 0).remove();
|
||||
|
||||
// Label each with the current attendance count
|
||||
var updateLabels = function (group) {
|
||||
group.attr("id", function (party) { return party._id; })
|
||||
.text(function (party) {return attending(party) || '';})
|
||||
.attr("x", function (party) { return party.x * 500; })
|
||||
.attr("y", function (party) { return party.y * 500 + radius(party)/2 })
|
||||
.style('font-size', function (party) {
|
||||
return radius(party) * 1.25 + "px";
|
||||
});
|
||||
};
|
||||
|
||||
var labels = d3.select(self.node).select(".labels").selectAll("text")
|
||||
.data(Parties.find().fetch(), function (party) { return party._id; });
|
||||
|
||||
updateLabels(labels.enter().append("text"));
|
||||
updateLabels(labels.transition().duration(250).ease("cubic-out"));
|
||||
labels.exit().remove();
|
||||
|
||||
// Draw a dashed circle around the currently selected party, if any
|
||||
var callout = d3.select(self.node).select("circle.callout")
|
||||
.transition().duration(250).ease("cubic-out");
|
||||
if (selectedParty)
|
||||
callout.attr("cx", selectedParty.x * 500)
|
||||
.attr("cy", selectedParty.y * 500)
|
||||
.attr("r", radius(selectedParty) + 10)
|
||||
.attr("class", "callout")
|
||||
.attr("display", '');
|
||||
else
|
||||
callout.attr("display", 'none');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Template.map.onDestroyed = function () {
|
||||
this.handle && this.handle.stop();
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Create Party dialog
|
||||
|
||||
var openCreateDialog = function (x, y) {
|
||||
Session.set("createCoords", {x: x, y: y});
|
||||
Session.set("createError", null);
|
||||
Session.set("showCreateDialog", true);
|
||||
};
|
||||
|
||||
Template.page.helpers({
|
||||
showCreateDialog: function () {
|
||||
return Session.get("showCreateDialog");
|
||||
}
|
||||
});
|
||||
|
||||
Template.createDialog.events({
|
||||
'click .save': function (event, template) {
|
||||
var title = template.find(".title").value;
|
||||
var description = template.find(".description").value;
|
||||
var public = ! template.find(".private").checked;
|
||||
var coords = Session.get("createCoords");
|
||||
|
||||
if (title.length && description.length) {
|
||||
var id = createParty({
|
||||
title: title,
|
||||
description: description,
|
||||
x: coords.x,
|
||||
y: coords.y,
|
||||
public: public
|
||||
});
|
||||
|
||||
Session.set("selected", id);
|
||||
if (! public && Meteor.users.find().count() > 1)
|
||||
openInviteDialog();
|
||||
Session.set("showCreateDialog", false);
|
||||
} else {
|
||||
Session.set("createError",
|
||||
"It needs a title and a description, or why bother?");
|
||||
}
|
||||
},
|
||||
|
||||
'click .cancel': function () {
|
||||
Session.set("showCreateDialog", false);
|
||||
}
|
||||
});
|
||||
|
||||
Template.createDialog.helpers({
|
||||
error: function () {
|
||||
return Session.get("createError");
|
||||
}
|
||||
});
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Invite dialog
|
||||
|
||||
var openInviteDialog = function () {
|
||||
Session.set("showInviteDialog", true);
|
||||
};
|
||||
|
||||
Template.page.helpers({
|
||||
showInviteDialog: function () {
|
||||
return Session.get("showInviteDialog");
|
||||
}
|
||||
});
|
||||
|
||||
Template.inviteDialog.events({
|
||||
'click .invite': function (event, template) {
|
||||
Meteor.call('invite', Session.get("selected"), this._id);
|
||||
},
|
||||
'click .done': function (event, template) {
|
||||
Session.set("showInviteDialog", false);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
Template.inviteDialog.helpers({
|
||||
uninvited: function () {
|
||||
var party = Parties.findOne(Session.get("selected"));
|
||||
if (! party)
|
||||
return []; // party hasn't loaded yet
|
||||
return Meteor.users.find({$nor: [{_id: {$in: party.invited}},
|
||||
{_id: party.owner}]});
|
||||
},
|
||||
|
||||
displayName: function () {
|
||||
return displayName(this);
|
||||
}
|
||||
});
|
||||
@@ -1,81 +0,0 @@
|
||||
.header {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.details {
|
||||
margin-top: -18px;
|
||||
}
|
||||
|
||||
.mask {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
background-color: #000000;
|
||||
opacity: .4;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.invite-row .invite {
|
||||
margin: 10px 10px 10px 0;
|
||||
}
|
||||
|
||||
.rsvp-buttons {
|
||||
text-align: center;
|
||||
margin: 40px 0 40px 0;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 20px 0 20px 0;
|
||||
}
|
||||
|
||||
.attendance .who {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.attendance .invite {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
input.chosen {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.map {
|
||||
position: relative;
|
||||
background-image: url('/soma.png');
|
||||
background-position: -20px -20px;
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
}
|
||||
|
||||
.map circle.public {
|
||||
fill: #49AFCD;
|
||||
}
|
||||
|
||||
.map circle.private {
|
||||
fill: #DA4F49;
|
||||
}
|
||||
|
||||
.map text {
|
||||
text-anchor: middle;
|
||||
fill: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.map circle.callout {
|
||||
stroke-width: 5px;
|
||||
stroke-dasharray: 9, 5;
|
||||
stroke-opacity: .8;
|
||||
fill: none;
|
||||
stroke: red;
|
||||
}
|
||||
|
||||
.attribution {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
background-color: white;
|
||||
padding: 3px;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
@@ -1,216 +0,0 @@
|
||||
<head>
|
||||
<title>All Tomorrow's Parties</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{{> page}}
|
||||
</body>
|
||||
|
||||
<template name="page">
|
||||
{{#if showCreateDialog}}
|
||||
{{> createDialog}}
|
||||
{{/if}}
|
||||
|
||||
{{#if showInviteDialog}}
|
||||
{{> inviteDialog}}
|
||||
{{/if}}
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="span1"> </div>
|
||||
|
||||
<div class="span10">
|
||||
<div class="header row">
|
||||
<div class="span5">
|
||||
<h3 style="margin-bottom: 0px">All Tomorrow's Parties</h3>
|
||||
</div>
|
||||
<div class="span5">
|
||||
<div style="float: right">
|
||||
{{> loginButtons align="right"}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="span6">
|
||||
{{> map}}
|
||||
{{#if currentUser}}
|
||||
<div class="pagination-centered">
|
||||
<em><small>Double click the map to post a party!</small></em>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="span4">
|
||||
{{> details}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="span1"> </div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="map">
|
||||
<div class="map">
|
||||
<svg width="500" height="500">
|
||||
<circle class="callout" cx=-100 cy=-100></circle>
|
||||
<g class="circles"></g>
|
||||
<g class="labels"></g>
|
||||
</svg>
|
||||
<div>
|
||||
<small class="attribution muted">©
|
||||
<a href="http://www.openstreetmap.org/?lat=37.78212&lon=-122.40146&zoom=15&layers=M"
|
||||
target="_blank">OpenStreetMap</a> contributors</small>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="details">
|
||||
<div class="details">
|
||||
{{#if party}}
|
||||
{{#with party}}
|
||||
<h1>{{title}}</h1>
|
||||
|
||||
<div class="description">{{description}}</div>
|
||||
|
||||
{{> attendance}}
|
||||
|
||||
<div class="rsvp-buttons">
|
||||
{{#if currentUser}}
|
||||
<input type="button" value="I'm going!"
|
||||
class="btn btn-small rsvp_yes {{maybeChosen "yes"}}">
|
||||
<input type="button" value="Maybe"
|
||||
class="btn btn-small rsvp_maybe {{maybeChosen "maybe"}}">
|
||||
<input type="button" value="No"
|
||||
class="btn btn-small rsvp_no {{maybeChosen "no"}}">
|
||||
{{else}}
|
||||
<i>Sign in to RSVP for this party.</i>
|
||||
{{/if}}
|
||||
<p><small>Posted by {{creatorName}}</small></p>
|
||||
</div>
|
||||
|
||||
{{#if canRemove}}
|
||||
<div class="alert alert-info"><small>
|
||||
You posted this party and nobody is signed up to go, so if
|
||||
you like, you could
|
||||
<b><a href="#" class="remove">delete this listing</a></b>.
|
||||
</small></div>
|
||||
{{/if}}
|
||||
{{/with}}
|
||||
{{else}}
|
||||
<h1 class="muted pagination-centered">
|
||||
{{#if anyParties}}
|
||||
Click a party to select it
|
||||
{{else}}
|
||||
Sign in and double click the map to post a party
|
||||
{{/if}}
|
||||
</h1>
|
||||
{{/if}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="attendance">
|
||||
<div class="attendance well well-small">
|
||||
<div class="muted who"><b>Who</b></div>
|
||||
{{#if public}}
|
||||
<div>
|
||||
<b>Everyone</b>
|
||||
<span class="label label-inverse pull-right">Invited</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#each rsvps}}
|
||||
<div>
|
||||
{{rsvpName}}
|
||||
{{#if rsvpIs "yes"}}
|
||||
<span class="label label-success pull-right">Going</span>
|
||||
{{/if}}
|
||||
{{#if rsvpIs "maybe"}}
|
||||
<span class="label label-info pull-right">Maybe</span>
|
||||
{{/if}}
|
||||
{{#if rsvpIs "no"}}
|
||||
<span class="label label pull-right">No</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/each}}
|
||||
|
||||
{{#unless public}}
|
||||
{{#each outstandingInvitations}}
|
||||
<div>
|
||||
{{invitationName}}
|
||||
<span class="label label-inverse pull-right">Invited</span>
|
||||
</div>
|
||||
{{/each}}
|
||||
{{/unless}}
|
||||
|
||||
{{#if nobody}}
|
||||
<div>Nobody.</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if canInvite}}
|
||||
<div class="invite">
|
||||
<a href="#" class="btn btn-mini invite">Invite people</a>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="createDialog">
|
||||
<div class="mask"> </div>
|
||||
<div class="modal">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close cancel">×</button>
|
||||
<h3>Add party</h3>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
{{#if error}}
|
||||
<div class="alert alert-error">{{error}}</div>
|
||||
{{/if}}
|
||||
|
||||
<label>Title</label>
|
||||
<input type="text" class="title span5">
|
||||
|
||||
<label>Description</label>
|
||||
<textarea class="description span5"></textarea>
|
||||
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" class="private">
|
||||
Private party — invitees only
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<a href="#" class="btn cancel">Cancel</a>
|
||||
<a href="#" class="btn btn-primary save">Add party</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="inviteDialog">
|
||||
<div class="mask"> </div>
|
||||
<div class="modal">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close done">×</button>
|
||||
<h3>Invite people</h3>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
{{#each uninvited}}
|
||||
<div class="invite-row">
|
||||
<a href="#" class="btn invite">Invite</a>
|
||||
{{displayName}}
|
||||
</div>
|
||||
{{else}}
|
||||
Everyone on the site has already been invited.
|
||||
{{/each}}
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<a href="#" class="btn btn-primary done">Done</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,183 +0,0 @@
|
||||
// All Tomorrow's Parties -- data model
|
||||
// Loaded on both the client and the server
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Parties
|
||||
|
||||
/*
|
||||
Each party is represented by a document in the Parties collection:
|
||||
owner: user id
|
||||
x, y: Number (screen coordinates in the interval [0, 1])
|
||||
title, description: String
|
||||
public: Boolean
|
||||
invited: Array of user id's that are invited (only if !public)
|
||||
rsvps: Array of objects like {user: userId, rsvp: "yes"} (or "no"/"maybe")
|
||||
*/
|
||||
Parties = new Mongo.Collection("parties");
|
||||
|
||||
Parties.allow({
|
||||
insert: function (userId, party) {
|
||||
return false; // no cowboy inserts -- use createParty method
|
||||
},
|
||||
update: function (userId, party, fields, modifier) {
|
||||
if (userId !== party.owner)
|
||||
return false; // not the owner
|
||||
|
||||
var allowed = ["title", "description", "x", "y"];
|
||||
if (_.difference(fields, allowed).length)
|
||||
return false; // tried to write to forbidden field
|
||||
|
||||
// A good improvement would be to validate the type of the new
|
||||
// value of the field (and if a string, the length.) In the
|
||||
// future Meteor will have a schema system to makes that easier.
|
||||
return true;
|
||||
},
|
||||
remove: function (userId, party) {
|
||||
// You can only remove parties that you created and nobody is going to.
|
||||
return party.owner === userId && attending(party) === 0;
|
||||
}
|
||||
});
|
||||
|
||||
attending = function (party) {
|
||||
return (_.groupBy(party.rsvps, 'rsvp').yes || []).length;
|
||||
};
|
||||
|
||||
var NonEmptyString = Match.Where(function (x) {
|
||||
check(x, String);
|
||||
return x.length !== 0;
|
||||
});
|
||||
|
||||
var Coordinate = Match.Where(function (x) {
|
||||
check(x, Number);
|
||||
return x >= 0 && x <= 1;
|
||||
});
|
||||
|
||||
createParty = function (options) {
|
||||
var id = Random.id();
|
||||
Meteor.call('createParty', _.extend({ _id: id }, options));
|
||||
return id;
|
||||
};
|
||||
|
||||
Meteor.methods({
|
||||
// options should include: title, description, x, y, public
|
||||
createParty: function (options) {
|
||||
check(options, {
|
||||
title: NonEmptyString,
|
||||
description: NonEmptyString,
|
||||
x: Coordinate,
|
||||
y: Coordinate,
|
||||
public: Match.Optional(Boolean),
|
||||
_id: Match.Optional(NonEmptyString)
|
||||
});
|
||||
|
||||
if (options.title.length > 100)
|
||||
throw new Meteor.Error(413, "Title too long");
|
||||
if (options.description.length > 1000)
|
||||
throw new Meteor.Error(413, "Description too long");
|
||||
if (! this.userId)
|
||||
throw new Meteor.Error(403, "You must be logged in");
|
||||
|
||||
var id = options._id || Random.id();
|
||||
Parties.insert({
|
||||
_id: id,
|
||||
owner: this.userId,
|
||||
x: options.x,
|
||||
y: options.y,
|
||||
title: options.title,
|
||||
description: options.description,
|
||||
public: !! options.public,
|
||||
invited: [],
|
||||
rsvps: []
|
||||
});
|
||||
return id;
|
||||
},
|
||||
|
||||
invite: function (partyId, userId) {
|
||||
check(partyId, String);
|
||||
check(userId, String);
|
||||
var party = Parties.findOne(partyId);
|
||||
if (! party || party.owner !== this.userId)
|
||||
throw new Meteor.Error(404, "No such party");
|
||||
if (party.public)
|
||||
throw new Meteor.Error(400,
|
||||
"That party is public. No need to invite people.");
|
||||
if (userId !== party.owner && ! _.contains(party.invited, userId)) {
|
||||
Parties.update(partyId, { $addToSet: { invited: userId } });
|
||||
|
||||
var from = contactEmail(Meteor.users.findOne(this.userId));
|
||||
var to = contactEmail(Meteor.users.findOne(userId));
|
||||
if (Meteor.isServer && to) {
|
||||
// This code only runs on the server. If you didn't want clients
|
||||
// to be able to see it, you could move it to a separate file.
|
||||
Email.send({
|
||||
from: "noreply@example.com",
|
||||
to: to,
|
||||
replyTo: from || undefined,
|
||||
subject: "PARTY: " + party.title,
|
||||
text:
|
||||
"Hey, I just invited you to '" + party.title + "' on All Tomorrow's Parties." +
|
||||
"\n\nCome check it out: " + Meteor.absoluteUrl() + "\n"
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
rsvp: function (partyId, rsvp) {
|
||||
check(partyId, String);
|
||||
check(rsvp, String);
|
||||
if (! this.userId)
|
||||
throw new Meteor.Error(403, "You must be logged in to RSVP");
|
||||
if (! _.contains(['yes', 'no', 'maybe'], rsvp))
|
||||
throw new Meteor.Error(400, "Invalid RSVP");
|
||||
var party = Parties.findOne(partyId);
|
||||
if (! party)
|
||||
throw new Meteor.Error(404, "No such party");
|
||||
if (! party.public && party.owner !== this.userId &&
|
||||
!_.contains(party.invited, this.userId))
|
||||
// private, but let's not tell this to the user
|
||||
throw new Meteor.Error(403, "No such party");
|
||||
|
||||
var rsvpIndex = _.indexOf(_.pluck(party.rsvps, 'user'), this.userId);
|
||||
if (rsvpIndex !== -1) {
|
||||
// update existing rsvp entry
|
||||
|
||||
if (Meteor.isServer) {
|
||||
// update the appropriate rsvp entry with $
|
||||
Parties.update(
|
||||
{_id: partyId, "rsvps.user": this.userId},
|
||||
{$set: {"rsvps.$.rsvp": rsvp}});
|
||||
} else {
|
||||
// minimongo doesn't yet support $ in modifier. as a temporary
|
||||
// workaround, make a modifier that uses an index. this is
|
||||
// safe on the client since there's only one thread.
|
||||
var modifier = {$set: {}};
|
||||
modifier.$set["rsvps." + rsvpIndex + ".rsvp"] = rsvp;
|
||||
Parties.update(partyId, modifier);
|
||||
}
|
||||
|
||||
// Possible improvement: send email to the other people that are
|
||||
// coming to the party.
|
||||
} else {
|
||||
// add new rsvp entry
|
||||
Parties.update(partyId,
|
||||
{$push: {rsvps: {user: this.userId, rsvp: rsvp}}});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Users
|
||||
|
||||
displayName = function (user) {
|
||||
if (user.profile && user.profile.name)
|
||||
return user.profile.name;
|
||||
return user.emails[0].address;
|
||||
};
|
||||
|
||||
var contactEmail = function (user) {
|
||||
if (user.emails && user.emails.length)
|
||||
return user.emails[0].address;
|
||||
if (user.services && user.services.facebook && user.services.facebook.email)
|
||||
return user.services.facebook.email;
|
||||
return null;
|
||||
};
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 126 KiB |
@@ -1,10 +0,0 @@
|
||||
// All Tomorrow's Parties -- server
|
||||
|
||||
Meteor.publish("directory", function () {
|
||||
return Meteor.users.find({}, {fields: {emails: 1, profile: 1}});
|
||||
});
|
||||
|
||||
Meteor.publish("parties", function () {
|
||||
return Parties.find(
|
||||
{$or: [{"public": true}, {invited: this.userId}, {owner: this.userId}]});
|
||||
});
|
||||
1
examples/other/quiescence/.meteor/.gitignore
vendored
1
examples/other/quiescence/.meteor/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
local
|
||||
@@ -1,9 +0,0 @@
|
||||
# Meteor packages used by this project, one per line.
|
||||
#
|
||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||
# but you can also edit it by hand.
|
||||
|
||||
insecure
|
||||
preserve-inputs
|
||||
random
|
||||
standard-app-packages
|
||||
@@ -1 +0,0 @@
|
||||
0.6.0
|
||||
@@ -1,35 +0,0 @@
|
||||
<head>
|
||||
<title>quiescence</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{{> clock}}
|
||||
{{> updated}}
|
||||
{{> stream}}
|
||||
</body>
|
||||
|
||||
<template name="clock">
|
||||
<h1>Current time</h1>
|
||||
<p>{{time}}</p>
|
||||
</template>
|
||||
|
||||
<template name="updated">
|
||||
<h1>Update object</h1>
|
||||
<p>The magic number is {{magic}}. Click the button to set it to a random
|
||||
integer in the client stub. The server will add 0.5 the that integer.</p>
|
||||
<button id='update-button'>Update</button>
|
||||
</template>
|
||||
|
||||
<template name="stream">
|
||||
<h1>Stream in results</h1>
|
||||
<p>Click the button. Note that the results stream in instead of appearing all
|
||||
at once. Note that the clock continues to tick while the streaming method
|
||||
is running. Note that the "update" button above works while the streaming
|
||||
method is running (ie, the server's "add 0.5" is processed).</p>
|
||||
<button id='stream-button'>Stream</button>
|
||||
<ul>
|
||||
{{#each results}}
|
||||
<li>{{text}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</template>
|
||||
@@ -1,96 +0,0 @@
|
||||
Time = new Mongo.Collection("time");
|
||||
Results = new Mongo.Collection("results");
|
||||
Magic = new Mongo.Collection("magic");
|
||||
|
||||
if (Meteor.isServer) {
|
||||
Meteor.publish("time", function () {
|
||||
var self = this;
|
||||
var publishTime = function () {
|
||||
var when = + new Date;
|
||||
self.changed("time", "now", {timestamp: when});
|
||||
};
|
||||
self.added("time", "now", {});
|
||||
publishTime();
|
||||
self.ready();
|
||||
var interval = Meteor.setInterval(publishTime, 1000);
|
||||
self.onStop(function () {
|
||||
Meteor.clearInterval(interval);
|
||||
});
|
||||
});
|
||||
Meteor.publish("results", function () {
|
||||
return Results.find();
|
||||
});
|
||||
Meteor.publish("magic", function () {
|
||||
return Magic.find();
|
||||
});
|
||||
|
||||
Meteor.startup(function () {
|
||||
if (Magic.find().count() === 0) {
|
||||
Magic.insert({number: 42});
|
||||
}
|
||||
});
|
||||
|
||||
var Fiber = Npm.require('fibers');
|
||||
|
||||
var sleep = function (ms) {
|
||||
var fiber = Fiber.current;
|
||||
setTimeout(function() {
|
||||
fiber.run();
|
||||
}, ms);
|
||||
Fiber.yield();
|
||||
};
|
||||
|
||||
Meteor.methods({
|
||||
getResults: function () {
|
||||
this.unblock();
|
||||
Results.remove({});
|
||||
for (var i = 0; i < 5; ++i) {
|
||||
sleep(1000);
|
||||
Results.insert({i: i, text: 'result ' + i});
|
||||
}
|
||||
}});
|
||||
} else {
|
||||
Meteor.subscribe("time");
|
||||
Meteor.subscribe("results");
|
||||
Meteor.subscribe("magic");
|
||||
|
||||
Template.clock.time = function () {
|
||||
var now = Time.findOne('now');
|
||||
if (!now)
|
||||
return "(loading)";
|
||||
return new Date(now.timestamp).toTimeString();
|
||||
};
|
||||
|
||||
Template.updated.magic = function () {
|
||||
var singleton = Magic.findOne();
|
||||
if (!singleton)
|
||||
return "(loading)";
|
||||
return singleton.number;
|
||||
};
|
||||
Template.updated.events({
|
||||
'click #update-button': function () {
|
||||
var num = Math.round(Random.fraction()*100);
|
||||
Meteor.call('setMagic', num);
|
||||
}
|
||||
});
|
||||
|
||||
Template.stream.events({
|
||||
'click #stream-button': function () {
|
||||
Meteor.call('getResults');
|
||||
}
|
||||
});
|
||||
|
||||
Template.stream.results = function () {
|
||||
return Results.find({}, {sort: ['i']});
|
||||
};
|
||||
}
|
||||
|
||||
Meteor.methods({
|
||||
setMagic: function (num) {
|
||||
if (this.isSimulation) {
|
||||
Magic.update({}, {$set: {number: num}});
|
||||
} else {
|
||||
Magic.update({}, {$set: {number: num + 0.5}});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1 +0,0 @@
|
||||
local
|
||||
@@ -1,7 +0,0 @@
|
||||
# Meteor packages used by this project, one per line.
|
||||
#
|
||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||
# but you can also edit it by hand.
|
||||
|
||||
autopublish
|
||||
standard-app-packages
|
||||
@@ -1 +0,0 @@
|
||||
0.6.0
|
||||
7034
examples/other/template-demo/client/d3.v2.js
vendored
7034
examples/other/template-demo/client/d3.v2.js
vendored
File diff suppressed because it is too large
Load Diff
@@ -1,44 +0,0 @@
|
||||
body {
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, san-serif;
|
||||
width: 600px;
|
||||
margin: auto;
|
||||
padding: 25px 50px;
|
||||
border: 5px dashed #ccc;
|
||||
border-style: none dashed;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 50px;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.clearboth {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
@-webkit-keyframes spinForward {
|
||||
from {-webkit-transform: rotate(0deg);}
|
||||
to {-webkit-transform: rotate(360deg);}
|
||||
}
|
||||
|
||||
@-webkit-keyframes spinBackward {
|
||||
from {-webkit-transform: rotate(360deg);}
|
||||
to {-webkit-transform: rotate(0deg);}
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 100px;
|
||||
border: 2px solid black;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.circles {
|
||||
float: left;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.circles svg {
|
||||
border: 2px solid #333;
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
<head>
|
||||
<title>Advanced Template Demo</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{{> page}}
|
||||
</body>
|
||||
|
||||
<template name="page">
|
||||
<h1>Advanced Template Demo</h1>
|
||||
<p>
|
||||
This demo shows off the advanced features of Meteor's optional
|
||||
Spark-based templating system, including constant regions, node
|
||||
preservation, per-template state, and template lifecycle
|
||||
callbacks.
|
||||
</p>
|
||||
|
||||
{{> preserveDemo }}
|
||||
{{> constantDemo }}
|
||||
{{> stateDemo }}
|
||||
{{> d3Demo }}
|
||||
</template>
|
||||
|
||||
<template name="preserveDemo">
|
||||
<h2>Element preservation</h2>
|
||||
|
||||
<input type="button" value="X++" class="x">
|
||||
|
||||
<p>
|
||||
Elements can be <em>preserved</em>, meaning that they will not be
|
||||
disturbed even as their attributes, children, or siblings
|
||||
change. In this example, when you press the X++ button, the CSS
|
||||
animation continues uninterrupted.
|
||||
</p>
|
||||
|
||||
|
||||
X={{x}}<br>
|
||||
<div class="spinner" style="-webkit-animation: {{spinAnim}} 2s infinite linear">
|
||||
X={{x}}
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" class="spinforward" {{spinForwardChecked}}>
|
||||
Spin Forward
|
||||
</div>
|
||||
X={{x}}
|
||||
</template>
|
||||
|
||||
<template name="constantDemo">
|
||||
<h2>Constant regions</h2>
|
||||
|
||||
<div>
|
||||
<input type="button" value="X++" class="x"> <br>
|
||||
<input type="checkbox" class="remove" which="1" {{checked 1}}>
|
||||
Remove map 1<br>
|
||||
<input type="checkbox" class="remove" which="2" {{checked 2}}>
|
||||
Remove map 2
|
||||
</div>
|
||||
<br>
|
||||
|
||||
<p>
|
||||
Parts of a template can be marked as <em>constant</em>, meaning
|
||||
that Meteor will leave the entire region alone (even as its
|
||||
siblings change.) This is great for embedding non-Meteor
|
||||
widgets. Try scrolling the two Google Maps embeds below. When you
|
||||
press X++, the maps stay where they are.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Try using the checkboxes to remove either or both of the
|
||||
maps. When you remove a map, Spark tracks the <em>identity</em> of
|
||||
the constant regions so that it knows which DOM nodes to keep and
|
||||
which DOM nodes to throw away. In the case of the Handlebars
|
||||
package, the identity is based on the actual template call stack
|
||||
that rendered the constant region.
|
||||
</p>
|
||||
|
||||
X={{x}}<br>
|
||||
|
||||
{{#if show 1}}
|
||||
{{#constant}}
|
||||
<div style="float: left; padding-right: 20px;">
|
||||
<iframe width="290" height="290" frameborder="0" scrolling="no" marginheight="0" marginwidth="0" src="https://maps.google.com/maps?f=q&source=s_q&hl=en&geocode=&q=140+10th+Street,+San+Francisco,+CA&aq=0&oq=140+10th+s&sll=37.7577,-122.4376&sspn=0.166931,0.329247&ie=UTF8&hq=&hnear=140+10th+St,+San+Francisco,+California+94103&t=m&ll=37.774921,-122.415419&spn=0.013569,0.017252&z=14&iwloc=A&output=embed"></iframe><br /><small><a href="https://maps.google.com/maps?f=q&source=embed&hl=en&geocode=&q=140+10th+Street,+San+Francisco,+CA&aq=0&oq=140+10th+s&sll=37.7577,-122.4376&sspn=0.166931,0.329247&ie=UTF8&hq=&hnear=140+10th+St,+San+Francisco,+California+94103&t=m&ll=37.774921,-122.415419&spn=0.013569,0.017252&z=14&iwloc=A" style="color:#0000FF;text-align:left">View Larger Map</a></small>
|
||||
</div>
|
||||
{{/constant}}
|
||||
{{/if}}
|
||||
|
||||
{{#if show 2}}
|
||||
{{#constant}}
|
||||
<div>
|
||||
<iframe width="290" height="290" frameborder="0" scrolling="no" marginheight="0" marginwidth="0" src="https://maps.google.com/maps?f=q&source=s_q&hl=en&geocode=&q=880+Harrison+Street,+San+Francisco,+CA&aq=0&oq=880+harrison&sll=37.7577,-122.4376&sspn=0.166931,0.329247&ie=UTF8&hq=&hnear=880+Harrison+St,+San+Francisco,+California+94107&t=m&ll=37.779534,-122.411213&spn=0.013568,0.01708&z=14&iwloc=A&output=embed"></iframe><br /><small><a href="https://maps.google.com/maps?f=q&source=embed&hl=en&geocode=&q=880+Harrison+Street,+San+Francisco,+CA&aq=0&oq=880+harrison&sll=37.7577,-122.4376&sspn=0.166931,0.329247&ie=UTF8&hq=&hnear=880+Harrison+St,+San+Francisco,+California+94107&t=m&ll=37.779534,-122.411213&spn=0.013568,0.01708&z=14&iwloc=A" style="color:#0000FF;text-align:left">View Larger Map</a></small>
|
||||
</div>
|
||||
{{/constant}}
|
||||
{{/if}}
|
||||
|
||||
<div class="clearboth"> </div>
|
||||
|
||||
X={{x}}
|
||||
</template>
|
||||
|
||||
<template name="stateDemo">
|
||||
<h2>Template callbacks</h2>
|
||||
|
||||
<p>
|
||||
<input type="button" value="X++" class="x">
|
||||
<input type="button" value="Y++" class="y">
|
||||
<input type="button" value="Z++" class="z">
|
||||
</p>
|
||||
|
||||
<p>
|
||||
You can get a <em>created</em> callback when a template is
|
||||
initially rendered; a <em>rendered</em> when a template is placed on
|
||||
the screen and when any part of the template is redrawn; and
|
||||
a <em>destroyed</em> callback when a template is taken across the
|
||||
screen. All of these callbacks receive a common <em>template state
|
||||
object</em> in 'this' which allows you to attach data to each
|
||||
particular instance of a template.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
In this case, <em>created</em> is used to create a new JavaScript
|
||||
timer that updates the text of a <span> element every
|
||||
second. <em>rendered</em> is used to find the <span> when it
|
||||
appears on the screen, and update the pointer when the
|
||||
<span> is redraw (say, when you press Y++ — since it
|
||||
is not marked to be preserved.) <em>destroyed</em> is used to cancel
|
||||
the timer when the template goes off the screen.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The template state is used to hold the current time count and a
|
||||
reference to the <span> object to update. That's why there
|
||||
can be multiple copies of the same template, each with a different
|
||||
value for the counter.
|
||||
</p>
|
||||
|
||||
X={{x}}<br>
|
||||
<input type="button" value="Create a timer" class="create"><br>
|
||||
{{#each timers}}
|
||||
<div>
|
||||
{{> timer}}
|
||||
Z={{z}}
|
||||
</div>
|
||||
{{/each}}
|
||||
X={{x}}
|
||||
</template>
|
||||
|
||||
<template name="timer">
|
||||
<span class="elapsed"></span>
|
||||
<input type="button" value="Reset" class="reset">
|
||||
<input type="button" value="Delete" class="delete">
|
||||
Y={{y}}
|
||||
</template>
|
||||
|
||||
<template name="d3Demo">
|
||||
<h2>Simple d3.js integration</h2>
|
||||
<p>
|
||||
Meteor fits naturally with the popular d3.js data visualization
|
||||
library by Michael Bostock. Just set up d3 from your
|
||||
template's <em>rendered</em> callback. With Meteor, you can pass
|
||||
data directly out of a Mongo query into d3, and your d3
|
||||
visualization will update in realtime, with no extra code! Try
|
||||
opening this page in two browser windows.
|
||||
</p>
|
||||
|
||||
{{> circles left}}
|
||||
{{> circles right}}
|
||||
<div class="clearboth"> </div>
|
||||
|
||||
</template>
|
||||
|
||||
<template name="circles">
|
||||
<div class="circles">
|
||||
{{#constant}}
|
||||
<svg width="272" height="272"></svg>
|
||||
{{/constant}}
|
||||
<br>
|
||||
{{count}} circles<br>
|
||||
<input type="button" value="Add" class="add">
|
||||
<input type="button" value="Remove" class="remove" {{disabled}}>
|
||||
<input type="button" value="Scram" class="scram">
|
||||
<input type="button" value="Clear" class="clear">
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,273 +0,0 @@
|
||||
Timers = new Mongo.Collection(null);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
if (! Session.get("x")) {
|
||||
Session.set("x", 1);
|
||||
}
|
||||
|
||||
if (! Session.get("y")) {
|
||||
Session.set("y", 1);
|
||||
}
|
||||
|
||||
if (! Session.get("z")) {
|
||||
Session.set("z", 1);
|
||||
}
|
||||
|
||||
Template.preserveDemo.x =
|
||||
Template.constantDemo.x =
|
||||
Template.stateDemo.x =
|
||||
function () {
|
||||
return Session.get("x");
|
||||
};
|
||||
|
||||
Template.timer.y = function () {
|
||||
return Session.get("y");
|
||||
};
|
||||
|
||||
Template.stateDemo.z =
|
||||
function () {
|
||||
return Session.get("z");
|
||||
};
|
||||
|
||||
Template.page.events({
|
||||
'click input.x': function () {
|
||||
Session.set("x", Session.get("x") + 1);
|
||||
},
|
||||
|
||||
'click input.y': function () {
|
||||
Session.set("y", Session.get("y") + 1);
|
||||
},
|
||||
|
||||
'click input.z': function () {
|
||||
Session.set("z", Session.get("z") + 1);
|
||||
}
|
||||
});
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
if (typeof Session.get("spinForward") !== 'boolean') {
|
||||
Session.set("spinForward", true);
|
||||
}
|
||||
|
||||
Template.preserveDemo.preserve([ '.spinner', '.spinforward' ]);
|
||||
|
||||
Template.preserveDemo.spinForwardChecked = function () {
|
||||
return Session.get('spinForward') ? 'checked' : '';
|
||||
};
|
||||
|
||||
Template.preserveDemo.spinAnim = function () {
|
||||
return Session.get('spinForward') ? 'spinForward' : 'spinBackward';
|
||||
};
|
||||
|
||||
Template.preserveDemo.events({
|
||||
'change .spinforward' : function (event) {
|
||||
Session.set('spinForward', event.currentTarget.checked);
|
||||
}
|
||||
});
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Template.constantDemo.checked = function (which) {
|
||||
return Session.get('mapchecked' + which) ? 'checked' : '';
|
||||
};
|
||||
|
||||
Template.constantDemo.show = function (which) {
|
||||
return ! Session.get('mapchecked' + which);
|
||||
};
|
||||
|
||||
Template.constantDemo.events({
|
||||
'change .remove' : function (event) {
|
||||
var tgt = event.currentTarget;
|
||||
Session.set('mapchecked' + tgt.getAttribute("which"), tgt.checked);
|
||||
}
|
||||
});
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Template.stateDemo.events({
|
||||
'click .create': function () {
|
||||
Timers.insert({});
|
||||
}
|
||||
});
|
||||
|
||||
Template.stateDemo.timers = function () {
|
||||
return Timers.find();
|
||||
};
|
||||
|
||||
Template.timer.events({
|
||||
'click .reset': function (event, template) {
|
||||
template.elapsed = 0;
|
||||
updateTimer(template);
|
||||
},
|
||||
'click .delete': function () {
|
||||
Timers.remove(this._id);
|
||||
}
|
||||
});
|
||||
|
||||
var updateTimer = function (timer) {
|
||||
timer.node.innerHTML = timer.elapsed + " second" +
|
||||
((timer.elapsed === 1) ? "" : "s");
|
||||
};
|
||||
|
||||
Template.timer.onCreated(function () {
|
||||
var self = this;
|
||||
self.elapsed = 0;
|
||||
self.node = null;
|
||||
});
|
||||
|
||||
Template.timer.onRendered(function () {
|
||||
var self = this;
|
||||
self.node = this.find(".elapsed");
|
||||
updateTimer(self);
|
||||
|
||||
if (! self.timer) {
|
||||
var tick = function () {
|
||||
self.elapsed++;
|
||||
self.timer = setTimeout(tick, 1000);
|
||||
updateTimer(self);
|
||||
};
|
||||
tick();
|
||||
}
|
||||
});
|
||||
|
||||
Template.timer.onDestroyed(function () {
|
||||
clearInterval(this.timer);
|
||||
});
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Template.d3Demo.left = function () {
|
||||
return { group: "left" };
|
||||
};
|
||||
|
||||
Template.d3Demo.right = function () {
|
||||
return { group: "right" };
|
||||
};
|
||||
|
||||
Template.circles.events({
|
||||
'mousedown circle': function (evt, template) {
|
||||
Session.set("selectedCircle:" + this.group, evt.currentTarget.id);
|
||||
},
|
||||
'click .add': function () {
|
||||
Circles.insert({x: Random.fraction(), y: Random.fraction(),
|
||||
r: Random.fraction() * .1 + .02,
|
||||
color: {
|
||||
r: Random.fraction(),
|
||||
g: Random.fraction(),
|
||||
b: Random.fraction()
|
||||
},
|
||||
group: this.group
|
||||
});
|
||||
},
|
||||
'click .remove': function () {
|
||||
var selected = Session.get("selectedCircle:" + this.group);
|
||||
if (selected) {
|
||||
Circles.remove(selected);
|
||||
Session.set("selectedCircle:" + this.group, null);
|
||||
}
|
||||
},
|
||||
'click .scram': function () {
|
||||
Circles.find({group: this.group}).forEach(function (r) {
|
||||
Circles.update(r._id, {
|
||||
$set: {
|
||||
x: Random.fraction(), y: Random.fraction(), r: Random.fraction() * .1 + .02
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
'click .clear': function () {
|
||||
Circles.remove({group: this.group});
|
||||
}
|
||||
});
|
||||
|
||||
var colorToString = function (color) {
|
||||
var f = function (x) { return Math.floor(x * 256); };
|
||||
return "rgb(" + f(color.r) + "," +
|
||||
+ f(color.g) + "," + + f(color.b) + ")";
|
||||
};
|
||||
|
||||
Template.circles.count = function () {
|
||||
return Circles.find({group: this.group}).count();
|
||||
};
|
||||
|
||||
Template.circles.disabled = function () {
|
||||
return Session.get("selectedCircle:" + this.group) ?
|
||||
'' : 'disabled';
|
||||
};
|
||||
|
||||
Template.circles.onCreated(function () {
|
||||
});
|
||||
|
||||
Template.circles.onRendered(function () {
|
||||
var self = this;
|
||||
self.node = self.find("svg");
|
||||
|
||||
var data = self.data;
|
||||
|
||||
if (! self.handle) {
|
||||
d3.select(self.node).append("rect");
|
||||
self.handle = Deps.autorun(function () {
|
||||
var circle = d3.select(self.node).selectAll("circle")
|
||||
.data(Circles.find({group: data.group}).fetch(),
|
||||
function (d) { return d._id; });
|
||||
|
||||
circle.enter().append("circle")
|
||||
.attr("id", function (d) {
|
||||
return d._id;
|
||||
})
|
||||
.attr("cx", function (d) {
|
||||
return d.x * 272;
|
||||
})
|
||||
.attr("cy", function (d) {
|
||||
return d.y * 272;
|
||||
})
|
||||
.attr("r", 50)
|
||||
.style("fill", function (d) {
|
||||
return colorToString(d.color);
|
||||
})
|
||||
.style("opacity", 0);
|
||||
|
||||
circle.transition()
|
||||
.duration(250)
|
||||
.attr("cx", function (d) {
|
||||
return d.x * 272;
|
||||
})
|
||||
.attr("cy", function (d) {
|
||||
return d.y * 272;
|
||||
})
|
||||
.attr("r", function (d) {
|
||||
return d.r * 272;
|
||||
})
|
||||
.style("fill", function (d) {
|
||||
return colorToString(d.color);
|
||||
})
|
||||
.style("opacity", .9)
|
||||
.ease("cubic-out");
|
||||
|
||||
circle.exit().transition()
|
||||
.duration(250)
|
||||
.attr("r", 0)
|
||||
.remove();
|
||||
|
||||
var selectionId = Session.get("selectedCircle:" + data.group);
|
||||
var s = selectionId && Circles.findOne(selectionId);
|
||||
var rect = d3.select(self.node).select("rect");
|
||||
if (s)
|
||||
rect.attr("x", (s.x - s.r) * 272)
|
||||
.attr("y", (s.y - s.r) * 272)
|
||||
.attr("width", s.r * 2 * 272)
|
||||
.attr("height", s.r * 2 * 272)
|
||||
.attr("display", '')
|
||||
.style("fill", "none")
|
||||
.style("stroke", "red")
|
||||
.style("stroke-width", 3);
|
||||
else
|
||||
rect.attr("display", 'none');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Template.circles.onDestroyed(function () {
|
||||
this.handle && this.handle.stop();
|
||||
});
|
||||
@@ -1 +0,0 @@
|
||||
Circles = new Mongo.Collection("circles");
|
||||
@@ -1,7 +0,0 @@
|
||||
# This file contains information which helps Meteor properly upgrade your
|
||||
# app when you run 'meteor update'. You should check it into version control
|
||||
# with your project.
|
||||
|
||||
notices-for-0.9.0
|
||||
notices-for-0.9.1
|
||||
0.9.4-platform-file
|
||||
1
examples/other/wordplay/.meteor/.gitignore
vendored
1
examples/other/wordplay/.meteor/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
local
|
||||
@@ -1,8 +0,0 @@
|
||||
# Meteor packages used by this project, one per line.
|
||||
#
|
||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||
# but you can also edit it by hand.
|
||||
|
||||
standard-app-packages
|
||||
insecure
|
||||
jquery
|
||||
@@ -1,2 +0,0 @@
|
||||
server
|
||||
browser
|
||||
@@ -1 +0,0 @@
|
||||
METEOR@0.9.4
|
||||
@@ -1,51 +0,0 @@
|
||||
application-configuration@1.0.3
|
||||
autoupdate@1.1.2
|
||||
base64@1.0.1
|
||||
binary-heap@1.0.1
|
||||
blaze-tools@1.0.1
|
||||
blaze@2.0.2
|
||||
boilerplate-generator@1.0.1
|
||||
callback-hook@1.0.1
|
||||
check@1.0.2
|
||||
ctl-helper@1.0.4
|
||||
ctl@1.0.2
|
||||
ddp@1.0.10
|
||||
deps@1.0.5
|
||||
ejson@1.0.4
|
||||
fastclick@1.0.1
|
||||
follower-livedata@1.0.2
|
||||
geojson-utils@1.0.1
|
||||
html-tools@1.0.2
|
||||
htmljs@1.0.2
|
||||
http@1.0.7
|
||||
id-map@1.0.1
|
||||
insecure@1.0.1
|
||||
jquery@1.0.1
|
||||
json@1.0.1
|
||||
livedata@1.0.11
|
||||
logging@1.0.4
|
||||
meteor-platform@1.1.2
|
||||
meteor@1.1.2
|
||||
minifiers@1.1.1
|
||||
minimongo@1.0.4
|
||||
mobile-status-bar@1.0.1
|
||||
mongo@1.0.7
|
||||
observe-sequence@1.0.3
|
||||
ordered-dict@1.0.1
|
||||
random@1.0.1
|
||||
reactive-dict@1.0.4
|
||||
reactive-var@1.0.3
|
||||
reload@1.1.1
|
||||
retry@1.0.1
|
||||
routepolicy@1.0.2
|
||||
session@1.0.3
|
||||
spacebars-compiler@1.0.3
|
||||
spacebars@1.0.3
|
||||
standard-app-packages@1.0.3
|
||||
templating@1.0.8
|
||||
tracker@1.0.3
|
||||
ui@1.0.4
|
||||
underscore@1.0.1
|
||||
url@1.0.1
|
||||
webapp-hashing@1.0.1
|
||||
webapp@1.1.3
|
||||
@@ -1,12 +0,0 @@
|
||||
TODOS
|
||||
strip spaces on input box
|
||||
focus input on game start
|
||||
styling
|
||||
eliminate extra divs
|
||||
|
||||
POSSIBLE EXTENSIONS
|
||||
publish remaining words at end of game
|
||||
UI that works on touch devices sans keyboard
|
||||
spinny while word is getting scored
|
||||
support clicking on board instead of text box
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
body {
|
||||
margin: 0px;
|
||||
background-color: #f4f4f4;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
/* base styles */
|
||||
|
||||
input {
|
||||
height: 50px;
|
||||
width: 300px;
|
||||
font-size: 2em;
|
||||
border: 2px solid black;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
button {
|
||||
position: relative;
|
||||
bottom: 3px;
|
||||
margin: 10px;
|
||||
height: 50px;
|
||||
background-color:#E6EFC2;
|
||||
border:1px solid #dedede;
|
||||
font-weight:bold;
|
||||
cursor:pointer;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color:#D6DFb2;
|
||||
border:1px solid #C6D880;
|
||||
}
|
||||
|
||||
button:active {
|
||||
background-color:#529214;
|
||||
border:1px solid #529214;
|
||||
color:#fff;
|
||||
}
|
||||
|
||||
/*******/
|
||||
|
||||
#main {
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
#left {
|
||||
float: left;
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
#right {
|
||||
float: left;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
#clock {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
text-align: center;
|
||||
font-size: 3em;
|
||||
}
|
||||
|
||||
#board {
|
||||
margin: auto;
|
||||
border:4px solid #999999;
|
||||
border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
padding:2px;
|
||||
|
||||
width:400px;
|
||||
height:400px;
|
||||
background-color:#999999;
|
||||
}
|
||||
|
||||
.square {
|
||||
cursor: pointer;
|
||||
width:84px;
|
||||
height:84px;
|
||||
border:4px solid #eeeee8;
|
||||
border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
margin: 4px;
|
||||
float:left;
|
||||
text-align: center;
|
||||
background-color:#eeeee8;
|
||||
font-size: 65px;
|
||||
}
|
||||
|
||||
.square.last_in_path {
|
||||
color: #ff0000;
|
||||
}
|
||||
|
||||
.square.in_path {
|
||||
color: #990000;
|
||||
}
|
||||
|
||||
#login {
|
||||
margin: 100px auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#login .error {
|
||||
color: red;
|
||||
}
|
||||
|
||||
#lobby {
|
||||
margin: 100px auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#postgame {
|
||||
height: 100px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#scratchpad {
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
#scratchpad input {
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
#scratchpad h1 {
|
||||
float: left;
|
||||
}
|
||||
|
||||
#scores {
|
||||
float: left;
|
||||
width: 100%;
|
||||
background-color: #eeeee8;
|
||||
border: 1px solid black;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
#scores .player {
|
||||
float: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#scores .header {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
#scores .unnamed {
|
||||
color: #444;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
#scores .winner {
|
||||
background-color: yellow;
|
||||
}
|
||||
|
||||
#scores .winner_text {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#scores .word {
|
||||
float: left;
|
||||
font-size: 1.25em;
|
||||
padding: 0.25em;
|
||||
margin: 0.5em;
|
||||
border: 1px solid #030;
|
||||
}
|
||||
|
||||
#scores .word.good {
|
||||
background-color: #0a0;
|
||||
}
|
||||
|
||||
#scores .word.bad {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
#scores .word span.score {
|
||||
width: 1em;
|
||||
}
|
||||
|
||||
#scores .word.bad span.score {
|
||||
background-image: 'circle-ball-dark-antialiased.gif';
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
<head>
|
||||
<title>Word play!</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{{> page}}
|
||||
</body>
|
||||
|
||||
<template name="page">
|
||||
<div id="main">
|
||||
<div id="left">
|
||||
{{> board }}
|
||||
</div>
|
||||
<div id="right">
|
||||
{{> lobby }}
|
||||
{{> scratchpad }}
|
||||
{{> postgame }}
|
||||
{{> scores }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="board">
|
||||
<div id="clock">
|
||||
{{ clock }}
|
||||
</div>
|
||||
<div id="board">
|
||||
<div>
|
||||
<div class="square {{ selected 0 }}">{{ square 0 }}</div>
|
||||
<div class="square {{ selected 1 }}">{{ square 1 }}</div>
|
||||
<div class="square {{ selected 2 }}">{{ square 2 }}</div>
|
||||
<div class="square {{ selected 3 }}">{{ square 3 }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="square {{ selected 4 }}">{{ square 4 }}</div>
|
||||
<div class="square {{ selected 5 }}">{{ square 5 }}</div>
|
||||
<div class="square {{ selected 6 }}">{{ square 6 }}</div>
|
||||
<div class="square {{ selected 7 }}">{{ square 7 }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="square {{ selected 8 }}">{{ square 8 }}</div>
|
||||
<div class="square {{ selected 9 }}">{{ square 9 }}</div>
|
||||
<div class="square {{ selected 10 }}">{{ square 10 }}</div>
|
||||
<div class="square {{ selected 11 }}">{{ square 11 }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="square {{ selected 12 }}">{{ square 12 }}</div>
|
||||
<div class="square {{ selected 13 }}">{{ square 13 }}</div>
|
||||
<div class="square {{ selected 14 }}">{{ square 14 }}</div>
|
||||
<div class="square {{ selected 15 }}">{{ square 15 }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="lobby">
|
||||
<div>
|
||||
{{#if show }}
|
||||
<div id="lobby">
|
||||
<h1>What's your name?</h1>
|
||||
<input id="myname" type="text" />
|
||||
{{#if count}}
|
||||
<h1>{{count}} other players are in the lobby:</h1>
|
||||
{{#each waiting }}
|
||||
<div class="player">{{name}}</div>
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
<div>
|
||||
<button id="startgame" class="startgame" {{disabled}}>
|
||||
{{#if count}} It's on! {{else}} Play solo {{/if}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="scratchpad">
|
||||
{{#if show}}
|
||||
<div id="scratchpad">
|
||||
<input id="scratchpad_input" type="text" />
|
||||
<button name="submit" class="submit">Submit</button>
|
||||
</div>
|
||||
{{/if}}
|
||||
</template>
|
||||
|
||||
<template name="postgame">
|
||||
<div>
|
||||
{{#if show}}
|
||||
<div id="postgame">
|
||||
<button name="backtolobby" class="lobby">Back to lobby</button>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="scores">
|
||||
<div>
|
||||
{{#if show}}
|
||||
<div id="scores">
|
||||
{{#each players}}
|
||||
{{> player }}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="player">
|
||||
<div class="player">
|
||||
<div class="header {{winner}}">
|
||||
{{#if name}}
|
||||
{{name}}
|
||||
{{else}}
|
||||
<span class="unnamed">no name</span>
|
||||
{{/if}}
|
||||
<span class="score">{{total_score}}</span>
|
||||
{{#if winner}}
|
||||
<span class="winner_text">Winner!</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{> words}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="words">
|
||||
<div class="words">
|
||||
{{#each words}}
|
||||
<div id="word_{{_id}}" class="word {{state}}">
|
||||
<span class="score">
|
||||
{{score}}
|
||||
</span>
|
||||
{{word}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,252 +0,0 @@
|
||||
////////// Main client application logic //////////
|
||||
|
||||
//////
|
||||
////// Utility functions
|
||||
//////
|
||||
|
||||
var player = function () {
|
||||
return Players.findOne(Session.get('player_id'));
|
||||
};
|
||||
|
||||
var game = function () {
|
||||
var me = player();
|
||||
return me && me.game_id && Games.findOne(me.game_id);
|
||||
};
|
||||
|
||||
var set_selected_positions = function (word) {
|
||||
var paths = paths_for_word(game().board, word.toUpperCase());
|
||||
var in_a_path = [];
|
||||
var last_in_a_path = [];
|
||||
|
||||
for (var i = 0; i < paths.length; i++) {
|
||||
in_a_path = in_a_path.concat(paths[i]);
|
||||
last_in_a_path.push(paths[i].slice(-1)[0]);
|
||||
}
|
||||
|
||||
for (var pos = 0; pos < 16; pos++) {
|
||||
if (_.indexOf(last_in_a_path, pos) !== -1)
|
||||
Session.set('selected_' + pos, 'last_in_path');
|
||||
else if (_.indexOf(in_a_path, pos) !== -1)
|
||||
Session.set('selected_' + pos, 'in_path');
|
||||
else
|
||||
Session.set('selected_' + pos, false);
|
||||
}
|
||||
};
|
||||
|
||||
var clear_selected_positions = function () {
|
||||
for (var pos = 0; pos < 16; pos++)
|
||||
Session.set('selected_' + pos, false);
|
||||
};
|
||||
|
||||
//////
|
||||
////// lobby template: shows everyone not currently playing, and
|
||||
////// offers a button to start a fresh game.
|
||||
//////
|
||||
|
||||
Template.lobby.helpers({
|
||||
show: function () {
|
||||
// only show lobby if we're not in a game
|
||||
return !game();
|
||||
},
|
||||
|
||||
waiting: function () {
|
||||
var players = Players.find({_id: {$ne: Session.get('player_id')},
|
||||
name: {$ne: ''},
|
||||
game_id: {$exists: false}});
|
||||
|
||||
return players;
|
||||
},
|
||||
|
||||
count: function () {
|
||||
var players = Players.find({_id: {$ne: Session.get('player_id')},
|
||||
name: {$ne: ''},
|
||||
game_id: {$exists: false}});
|
||||
|
||||
return players.count();
|
||||
},
|
||||
|
||||
disabled: function () {
|
||||
var me = player();
|
||||
if (me && me.name)
|
||||
return '';
|
||||
return 'disabled';
|
||||
}
|
||||
});
|
||||
|
||||
var trim = function (string) { return string.replace(/^\s+|\s+$/g, ''); };
|
||||
|
||||
Template.lobby.events({
|
||||
'keyup input#myname': function (evt) {
|
||||
var name = trim($('#lobby input#myname').val());
|
||||
Players.update(Session.get('player_id'), {$set: {name: name}});
|
||||
},
|
||||
'click button.startgame': function () {
|
||||
Meteor.call('start_new_game');
|
||||
}
|
||||
});
|
||||
|
||||
//////
|
||||
////// board template: renders the board and the clock given the
|
||||
////// current game. if there is no game, show a splash screen.
|
||||
//////
|
||||
var SPLASH = ['','','','',
|
||||
'W', 'O', 'R', 'D',
|
||||
'P', 'L', 'A', 'Y',
|
||||
'','','',''];
|
||||
|
||||
Template.board.helpers({
|
||||
square: function (i) {
|
||||
var g = game();
|
||||
return g && g.board && g.board[i] || SPLASH[i];
|
||||
},
|
||||
|
||||
selected: function (i) {
|
||||
return Session.get('selected_' + i);
|
||||
},
|
||||
|
||||
clock: function () {
|
||||
var clock = game() && game().clock;
|
||||
|
||||
if (!clock || clock === 0)
|
||||
return;
|
||||
|
||||
// format into M:SS
|
||||
var min = Math.floor(clock / 60);
|
||||
var sec = clock % 60;
|
||||
return min + ':' + (sec < 10 ? ('0' + sec) : sec);
|
||||
}
|
||||
});
|
||||
|
||||
Template.board.events({
|
||||
'click .square': function (evt) {
|
||||
var textbox = $('#scratchpad input');
|
||||
// Note: Getting the letter out of the DOM is kind of a hack
|
||||
var letter = evt.target.textContent || evt.target.innerText;
|
||||
textbox.val(textbox.val() + letter);
|
||||
textbox.focus();
|
||||
}
|
||||
});
|
||||
|
||||
//////
|
||||
////// scratchpad is where we enter new words.
|
||||
//////
|
||||
|
||||
Template.scratchpad.helpers({
|
||||
show: function () {
|
||||
return game() && game().clock > 0;
|
||||
}
|
||||
});
|
||||
|
||||
Template.scratchpad.events({
|
||||
'click button, keyup input': function (evt) {
|
||||
var textbox = $('#scratchpad input');
|
||||
// if we clicked the button or hit enter
|
||||
if ((evt.type === "click" || (evt.type === "keyup" && evt.which === 13))
|
||||
&& textbox.val()) {
|
||||
var word_id = Words.insert({player_id: Session.get('player_id'),
|
||||
game_id: game() && game()._id,
|
||||
word: textbox.val().toUpperCase(),
|
||||
state: 'pending'});
|
||||
Meteor.call('score_word', word_id);
|
||||
textbox.val('');
|
||||
textbox.focus();
|
||||
clear_selected_positions();
|
||||
} else {
|
||||
set_selected_positions(textbox.val());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Template.postgame.helpers({
|
||||
show: function () {
|
||||
return game() && game().clock === 0;
|
||||
}
|
||||
});
|
||||
|
||||
Template.postgame.events({
|
||||
'click button': function (evt) {
|
||||
Players.update(Session.get('player_id'), {$set: {game_id: null}});
|
||||
}
|
||||
});
|
||||
|
||||
//////
|
||||
////// scores shows everyone's score and word list.
|
||||
//////
|
||||
|
||||
Template.scores.helpers({
|
||||
show: function () {
|
||||
return !!game();
|
||||
},
|
||||
|
||||
players: function () {
|
||||
return game() && game().players;
|
||||
}
|
||||
});
|
||||
|
||||
Template.player.helpers({
|
||||
winner: function () {
|
||||
var g = game();
|
||||
if (g.winners && _.include(g.winners, this._id))
|
||||
return 'winner';
|
||||
return '';
|
||||
},
|
||||
|
||||
total_score: function () {
|
||||
var words = Words.find({game_id: game() && game()._id,
|
||||
player_id: this._id});
|
||||
|
||||
var score = 0;
|
||||
words.forEach(function (word) {
|
||||
if (word.score)
|
||||
score += word.score;
|
||||
});
|
||||
return score;
|
||||
}
|
||||
});
|
||||
|
||||
Template.words.helpers({
|
||||
words: function () {
|
||||
return Words.find({game_id: game() && game()._id,
|
||||
player_id: this._id});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//////
|
||||
////// Initialization
|
||||
//////
|
||||
|
||||
Meteor.startup(function () {
|
||||
// Allocate a new player id.
|
||||
//
|
||||
// XXX this does not handle hot reload. In the reload case,
|
||||
// Session.get('player_id') will return a real id. We should check for
|
||||
// a pre-existing player, and if it exists, make sure the server still
|
||||
// knows about us.
|
||||
var player_id = Players.insert({name: '', idle: false});
|
||||
Session.set('player_id', player_id);
|
||||
|
||||
// subscribe to all the players, the game i'm in, and all
|
||||
// the words in that game.
|
||||
Tracker.autorun(function () {
|
||||
Meteor.subscribe('players');
|
||||
|
||||
if (Session.get('player_id')) {
|
||||
var me = player();
|
||||
if (me && me.game_id) {
|
||||
Meteor.subscribe('games', me.game_id);
|
||||
Meteor.subscribe('words', me.game_id, Session.get('player_id'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// send keepalives so the server can tell when we go away.
|
||||
//
|
||||
// XXX this is not a great idiom. meteor server does not yet have a
|
||||
// way to expose connection status to user code. Once it does, this
|
||||
// code can go away.
|
||||
Meteor.setInterval(function() {
|
||||
if (Meteor.status().connected)
|
||||
Meteor.call('keepalive', Session.get('player_id'));
|
||||
}, 20*1000);
|
||||
});
|
||||
@@ -1,158 +0,0 @@
|
||||
////////// Shared code (client and server) //////////
|
||||
|
||||
Games = new Mongo.Collection('games');
|
||||
// { board: ['A','I',...], clock: 60,
|
||||
// players: [{player_id, name}], winners: [player_id] }
|
||||
|
||||
Words = new Mongo.Collection('words');
|
||||
// {player_id: 10, game_id: 123, word: 'hello', state: 'good', score: 4}
|
||||
|
||||
Players = new Mongo.Collection('players');
|
||||
// {name: 'matt', game_id: 123}
|
||||
|
||||
// 6 faces per die, 16 dice. Q really means Qu.
|
||||
var DICE = ['PCHOAS', 'OATTOW', 'LRYTTE', 'VTHRWE',
|
||||
'EGHWNE', 'SEOTIS', 'ANAEEG', 'IDSYTT',
|
||||
'MTOICU', 'AFPKFS', 'XLDERI', 'ENSIEU',
|
||||
'YLDEVR', 'ZNRNHL', 'NMIQHU', 'OBBAOJ'];
|
||||
|
||||
var DICTIONARY = null;
|
||||
|
||||
// board is an array of length 16, in row-major order. ADJACENCIES
|
||||
// lists the board positions adjacent to each board position.
|
||||
var ADJACENCIES = [
|
||||
[1,4,5],
|
||||
[0,2,4,5,6],
|
||||
[1,3,5,6,7],
|
||||
[2,6,7],
|
||||
[0,1,5,8,9],
|
||||
[0,1,2,4,6,8,9,10],
|
||||
[1,2,3,5,7,9,10,11],
|
||||
[2,3,6,10,11],
|
||||
[4,5,9,12,13],
|
||||
[4,5,6,8,10,12,13,14],
|
||||
[5,6,7,9,11,13,14,15],
|
||||
[6,7,10,14,15],
|
||||
[8,9,13],
|
||||
[8,9,10,12,14],
|
||||
[9,10,11,13,15],
|
||||
[10,11,14]
|
||||
];
|
||||
|
||||
// generate a new random selection of letters.
|
||||
new_board = function () {
|
||||
var board = [];
|
||||
var i;
|
||||
|
||||
// pick random letter from each die
|
||||
for (i = 0; i < 16; i += 1) {
|
||||
board[i] = Random.choice(DICE[i]);
|
||||
}
|
||||
|
||||
// knuth shuffle
|
||||
for (i = 15; i > 0; i -= 1) {
|
||||
var j = Math.floor(Math.random() * (i + 1));
|
||||
var tmp = board[i];
|
||||
board[i] = board[j];
|
||||
board[j] = tmp;
|
||||
}
|
||||
|
||||
return board;
|
||||
};
|
||||
|
||||
// returns an array of valid paths to make the specified word on the
|
||||
// board. each path is an array of board positions 0-15. a valid
|
||||
// path can use each position only once, and each position must be
|
||||
// adjacent to the previous position.
|
||||
paths_for_word = function (board, word) {
|
||||
var valid_paths = [];
|
||||
|
||||
var check_path = function (word, path, positions_to_try) {
|
||||
// base case: the whole word has been consumed. path is valid.
|
||||
if (word.length === 0) {
|
||||
valid_paths.push(path);
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise, try to match each available position against the
|
||||
// first letter of the word, avoiding any positions that are
|
||||
// already used by the path. for each of those matches, descend
|
||||
// recursively, passing the remainder of the word, the accumulated
|
||||
// path, and the positions adjacent to the match.
|
||||
|
||||
for (var i = 0; i < positions_to_try.length; i++) {
|
||||
var pos = positions_to_try[i];
|
||||
if (board[pos] === word[0] && _.indexOf(path, pos) === -1)
|
||||
check_path(word.slice(1), // cdr of word
|
||||
path.concat([pos]), // append matching loc to path
|
||||
ADJACENCIES[pos]); // only look at surrounding tiles
|
||||
}
|
||||
};
|
||||
|
||||
// start recursive search w/ full word, empty path, and all tiles
|
||||
// available for the first letter.
|
||||
check_path(word, [], [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]);
|
||||
|
||||
return valid_paths;
|
||||
};
|
||||
|
||||
Meteor.methods({
|
||||
score_word: function (word_id) {
|
||||
check(word_id, String);
|
||||
var word = Words.findOne(word_id);
|
||||
var game = Games.findOne(word.game_id);
|
||||
|
||||
// client and server can both check that the game has time remaining, and
|
||||
// that the word is at least three chars, isn't already used, and is
|
||||
// possible to make on the board.
|
||||
if (game.clock === 0
|
||||
|| !word.word
|
||||
|| word.word.length < 3
|
||||
|| Words.find({game_id: word.game_id, word: word.word}).count() > 1
|
||||
|| paths_for_word(game.board, word.word).length === 0) {
|
||||
Words.update(word._id, {$set: {score: 0, state: 'bad'}});
|
||||
return;
|
||||
}
|
||||
|
||||
// now only on the server, check against dictionary and score it.
|
||||
if (Meteor.isServer) {
|
||||
if (_.has(DICTIONARY, word.word.toLowerCase())) {
|
||||
var score = Math.pow(2, word.word.length - 3);
|
||||
Words.update(word._id, {$set: {score: score, state: 'good'}});
|
||||
} else {
|
||||
Words.update(word._id, {$set: {score: 0, state: 'bad'}});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if (Meteor.isServer) {
|
||||
DICTIONARY = {};
|
||||
_.each(Assets.getText("enable2k.txt").split("\n"), function (line) {
|
||||
// Skip blanks and comment lines
|
||||
if (line && line.indexOf("//") !== 0) {
|
||||
DICTIONARY[line] = true;
|
||||
}
|
||||
});
|
||||
|
||||
// publish all the non-idle players.
|
||||
Meteor.publish('players', function () {
|
||||
return Players.find({idle: false});
|
||||
});
|
||||
|
||||
// publish single games
|
||||
Meteor.publish('games', function (id) {
|
||||
check(id, String);
|
||||
return Games.find({_id: id});
|
||||
});
|
||||
|
||||
// publish all my words and opponents' words that the server has
|
||||
// scored as good.
|
||||
Meteor.publish('words', function (game_id, player_id) {
|
||||
check(game_id, String);
|
||||
check(player_id, String);
|
||||
return Words.find({$or: [{game_id: game_id, state: 'good'},
|
||||
{player_id: player_id}]});
|
||||
});
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 691 B |
@@ -1,70 +0,0 @@
|
||||
////////// Server only logic //////////
|
||||
|
||||
Meteor.methods({
|
||||
start_new_game: function () {
|
||||
// create a new game w/ fresh board
|
||||
var game_id = Games.insert({board: new_board(),
|
||||
clock: 120});
|
||||
|
||||
// move everyone who is ready in the lobby to the game
|
||||
Players.update({game_id: null, idle: false, name: {$ne: ''}},
|
||||
{$set: {game_id: game_id}},
|
||||
{multi: true});
|
||||
// Save a record of who is in the game, so when they leave we can
|
||||
// still show them.
|
||||
var p = Players.find({game_id: game_id},
|
||||
{fields: {_id: true, name: true}}).fetch();
|
||||
Games.update({_id: game_id}, {$set: {players: p}});
|
||||
|
||||
|
||||
// wind down the game clock
|
||||
var clock = 120;
|
||||
var interval = Meteor.setInterval(function () {
|
||||
clock -= 1;
|
||||
Games.update(game_id, {$set: {clock: clock}});
|
||||
|
||||
// end of game
|
||||
if (clock === 0) {
|
||||
// stop the clock
|
||||
Meteor.clearInterval(interval);
|
||||
// declare zero or more winners
|
||||
var scores = {};
|
||||
Words.find({game_id: game_id}).forEach(function (word) {
|
||||
if (!scores[word.player_id])
|
||||
scores[word.player_id] = 0;
|
||||
scores[word.player_id] += word.score;
|
||||
});
|
||||
var high_score = _.max(scores);
|
||||
var winners = [];
|
||||
_.each(scores, function (score, player_id) {
|
||||
if (score === high_score)
|
||||
winners.push(player_id);
|
||||
});
|
||||
Games.update(game_id, {$set: {winners: winners}});
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
return game_id;
|
||||
},
|
||||
|
||||
|
||||
keepalive: function (player_id) {
|
||||
check(player_id, String);
|
||||
Players.update({_id: player_id},
|
||||
{$set: {last_keepalive: (new Date()).getTime(),
|
||||
idle: false}});
|
||||
}
|
||||
});
|
||||
|
||||
Meteor.setInterval(function () {
|
||||
var now = (new Date()).getTime();
|
||||
var idle_threshold = now - 70*1000; // 70 sec
|
||||
var remove_threshold = now - 60*60*1000; // 1hr
|
||||
|
||||
Players.update({last_keepalive: {$lt: idle_threshold}},
|
||||
{$set: {idle: true}});
|
||||
|
||||
// XXX need to deal with people coming back!
|
||||
// Players.remove({$lt: {last_keepalive: remove_threshold}});
|
||||
|
||||
}, 30*1000);
|
||||
@@ -1,113 +0,0 @@
|
||||
require("./words.js");
|
||||
|
||||
var BOGGLE_DICE = ['pchoas', 'oattow', 'lrytte', 'vthrwe',
|
||||
'eghwne', 'seotis', 'anaeeg', 'idsytt',
|
||||
'mtoicu', 'afpkfs', 'xlderi', 'ensieu',
|
||||
'yldevr', 'znrnhl', 'nmiqhu', 'obbaoj'];
|
||||
|
||||
var FLAGS = [0x1, 0x2, 0x4, 0x8,
|
||||
0x10, 0x20, 0x40, 0x80,
|
||||
0x100, 0x200, 0x400, 0x800,
|
||||
0x1000, 0x2000, 0x4000, 0x8000];
|
||||
|
||||
var MASKS = {};
|
||||
|
||||
// generate masks for all one, two, three, and four-letter combinations
|
||||
var make_masks = function () {
|
||||
var mask_count = 0;
|
||||
|
||||
// recursive helper
|
||||
var check_mask = function (word, index, used) {
|
||||
for (var i = 0; i < 16; i += 1) {
|
||||
for (var j = 0; j < 6; j += 1) {
|
||||
// if die i is still available, and it has a letter that
|
||||
// matches what we need, we can make use of this.
|
||||
if (!(used & FLAGS[i]) && (BOGGLE_DICE[i][j] === word[index])) {
|
||||
if (word.length === index + 1) {
|
||||
// this is the end of the word, we have a valid mask!
|
||||
if (!MASKS[word])
|
||||
MASKS[word] = [];
|
||||
if (MASKS[word].indexOf(used | FLAGS[i]) === -1) {
|
||||
MASKS[word].push(used | FLAGS[i]);
|
||||
mask_count += 1;
|
||||
}
|
||||
// console.log("MASK", word, used | FLAGS[i]);
|
||||
} else {
|
||||
// descend into searching rest of word w/ remaining dice.
|
||||
check_mask(word,
|
||||
index + 1,
|
||||
used | FLAGS[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
process.stderr.write("CALCULATING MASKS FOR ");
|
||||
for (var a = 97; a <= 97; a += 1) {
|
||||
process.stderr.write(String.fromCharCode(a));
|
||||
check_mask(String.fromCharCode(a), 0, 0x0);
|
||||
for (var b = 97; b <= 122; b += 1) {
|
||||
check_mask(String.fromCharCode(a,b), 0, 0x0);
|
||||
for (var c = 97; c <= 122; c += 1) {
|
||||
check_mask(String.fromCharCode(a,b,c), 0, 0x0);
|
||||
for (var d = 97; d <= 122; d += 1) {
|
||||
check_mask(String.fromCharCode(a,b,c,d), 0, 0x0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
process.stderr.write(" DONE [" + mask_count + " MASKS]\n");
|
||||
};
|
||||
|
||||
make_masks();
|
||||
|
||||
function check (word, index, used) {
|
||||
//console.log('CHECK', word, index, used);
|
||||
|
||||
var length;
|
||||
var masks;
|
||||
|
||||
// check up to 4 chars at a time
|
||||
length = (word.length - index > 4) ? 4 : word.length - index;
|
||||
masks = MASKS[word.slice(index, length + index)] || [];
|
||||
|
||||
for (var i = 0; i < masks.length; i += 1) {
|
||||
if (!(used & masks[i])) {
|
||||
// masks[i] has no overlap w/ tiles we already consumed.
|
||||
if (word.length === index + length)
|
||||
// we consumed the whole word.
|
||||
return true;
|
||||
else if (check(word, index + length, used | masks[i]))
|
||||
// some descendant consumed the whole word
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// none of the available masks returned true. there's no match.
|
||||
return false;
|
||||
};
|
||||
|
||||
var dict_len = DICTIONARY.length
|
||||
for (var i = 0; i < dict_len; i+=1) {
|
||||
var word = DICTIONARY[i];
|
||||
|
||||
// reject words that have q followed by non-u. those can't be made
|
||||
// in boggle. otherwise, strip the q -- our dictionary won't
|
||||
// include the u.
|
||||
|
||||
if (word.match(/q/)) {
|
||||
if (word.match(/q[^u]/)) {
|
||||
process.stderr.write('Q REJECT ' + word + '\n');
|
||||
continue;
|
||||
} else {
|
||||
word = word.replace('qu', 'q');
|
||||
process.stderr.write('Q REPLACED ' + word + '\n');
|
||||
}
|
||||
}
|
||||
|
||||
if (check(word, 0, 0x0))
|
||||
console.log(word);
|
||||
else
|
||||
process.stderr.write('REJECT ' + DICTIONARY[i] + '\n');
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
node_modules
|
||||
@@ -1 +0,0 @@
|
||||
local
|
||||
@@ -1,7 +0,0 @@
|
||||
# This file contains a token that is unique to your project.
|
||||
# Check it into your repository along with the rest of this directory.
|
||||
# It can be used for purposes such as:
|
||||
# - ensuring you don't accidentally deploy one app on top of another
|
||||
# - providing package authors with aggregated statistics
|
||||
|
||||
wllgu394zq2.rrlkgpniscl
|
||||
@@ -1,18 +0,0 @@
|
||||
# Meteor packages used by this project, one per line.
|
||||
#
|
||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||
# but you can also edit it by hand.
|
||||
|
||||
autopublish
|
||||
insecure
|
||||
preserve-inputs
|
||||
accounts-ui
|
||||
less
|
||||
accounts-google
|
||||
accounts-github
|
||||
accounts-password
|
||||
accounts-facebook
|
||||
standard-app-packages
|
||||
facebook-config-ui
|
||||
github-config-ui
|
||||
google-config-ui
|
||||
@@ -1,2 +0,0 @@
|
||||
browser
|
||||
server
|
||||
@@ -1 +0,0 @@
|
||||
0.6.1
|
||||
@@ -1,131 +0,0 @@
|
||||
<head>
|
||||
<title>accounts-ui-viewer</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{{> page}}
|
||||
</body>
|
||||
|
||||
<template name="radio">
|
||||
<span class="radio">
|
||||
<input id="{{key}}:{{value}}" {{maybeChecked}} type="radio" name="{{key}}" value="{{value}}" />
|
||||
<label for="{{key}}:{{value}}">{{label}}</label>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template name="button">
|
||||
<button>{{label}}</button>
|
||||
</template>
|
||||
|
||||
<template name="page">
|
||||
<div id="controlpane">
|
||||
<div class="group">
|
||||
<h3>Dropdown align edge:</h3>
|
||||
{{> radio key="alignRight" value="false" label="Left"}}
|
||||
{{> radio key="alignRight" value="true" label="Right"}}
|
||||
</div>
|
||||
<div class="group">
|
||||
<h3>Positioning:</h3>
|
||||
{{> radio key="positioning" value="relative" label="Relative"}}
|
||||
{{> radio key="positioning" value="absolute" label="Absolute"}}
|
||||
{{> radio key="positioning" value="floatRight" label="Float:right"}}
|
||||
{{> radio key="positioning" value="inline" label="Inline"}}
|
||||
</div>
|
||||
<div class="group">
|
||||
<h3>How many third-party services?</h3>
|
||||
{{> radio key="numServices" value="0" label="0"}}
|
||||
{{> radio key="numServices" value="1" label="1"}}
|
||||
{{> radio key="numServices" value="2" label="2"}}
|
||||
{{> radio key="numServices" value="3" label="3"}}
|
||||
</div>
|
||||
<div class="group">
|
||||
<h3>Has password accounts?</h3>
|
||||
{{> radio key="hasPasswords" value="false" label="No"}}
|
||||
{{> radio key="hasPasswords" value="true" label="Yes"}}
|
||||
</div>
|
||||
<div class="group">
|
||||
<h3>Password sign-up fields:</h3>
|
||||
{{> radio key="signupFields" value="EMAIL_ONLY" label="Email"}}
|
||||
{{> radio key="signupFields" value="USERNAME_ONLY" label="Username"}}
|
||||
{{> radio key="signupFields" value="USERNAME_AND_EMAIL" label="Username & Email"}}
|
||||
{{> radio key="signupFields" value="USERNAME_AND_OPTIONAL_EMAIL" label="Username & Optional Email"}}
|
||||
</div>
|
||||
<div class="group">
|
||||
<h3>Fake-Configure:</h3>
|
||||
{{> button key="fakeConfig" value="facebook" label="Facebook"}}
|
||||
{{> button key="fakeConfig" value="github" label="GitHub"}}
|
||||
{{> button key="fakeConfig" value="google" label="Google"}}
|
||||
</div>
|
||||
<div class="group">
|
||||
<h3>Show Configure Dialog:</h3>
|
||||
{{> button key="showConfig" value="facebook" label="Facebook"}}
|
||||
{{> button key="showConfig" value="github" label="GitHub"}}
|
||||
{{> button key="showConfig" value="google" label="Google"}}
|
||||
</div>
|
||||
<div class="group">
|
||||
<h3>Unconfigure:</h3>
|
||||
{{> button key="unconfig" value="facebook" label="Facebook"}}
|
||||
{{> button key="unconfig" value="github" label="GitHub"}}
|
||||
{{> button key="unconfig" value="google" label="Google"}}
|
||||
</div>
|
||||
<div class="group">
|
||||
<h3>Messages:</h3>
|
||||
{{> button key="messages" value="error" label="Error"}}
|
||||
{{> button key="messages" value="info" label="Info"}}
|
||||
{{> button key="messages" value="clear" label="Clear"}}
|
||||
</div>
|
||||
<div class="group">
|
||||
<h3>Signing in/out</h3>
|
||||
{{> button key="sign" value="in" label="Fake sign-in"}}
|
||||
{{> button key="sign" value="out" label="Sign out"}}
|
||||
</div>
|
||||
<div class="group">
|
||||
<h3>Logged-out Views</h3>
|
||||
{{> button key="lov" value="signIn" label="Sign In"}}
|
||||
{{> button key="lov" value="createAccount" label="Create Account"}}
|
||||
{{> button key="lov" value="forgotPassword" label="Forgot Password"}}
|
||||
</div>
|
||||
<div class="group">
|
||||
<h3>Logged-in Views</h3>
|
||||
{{> button key="liv" value="accountButtons" label="Account Buttons"}}
|
||||
{{> button key="liv" value="changePassword" label="Change Password"}}
|
||||
{{> button key="liv" value="messageOnly" label="Message Only"}}
|
||||
</div>
|
||||
<div class="group">
|
||||
<h3>Other Modals</h3>
|
||||
{{> button key="modals" value="resetPassword" label="Reset Password"}}
|
||||
{{> button key="modals" value="enrollAccount" label="Enroll Account"}}
|
||||
{{> button key="modals" value="justVerifiedEmail" label="Verified Email"}}
|
||||
</div>
|
||||
<div class="group">
|
||||
<h3>Logging-in Spinner</h3>
|
||||
{{> radio key="fakeLoggingIn" value="false" label="Off"}}
|
||||
{{> radio key="fakeLoggingIn" value="true" label="Pretend loggingIn=true"}}
|
||||
</div>
|
||||
<div class="group">
|
||||
<h3>Background Color</h3>
|
||||
{{> radio key="bgcolor" value="white" label="White"}}
|
||||
{{> radio key="bgcolor" value="black" label="Black"}}
|
||||
{{> radio key="bgcolor" value="red" label="Red"}}
|
||||
</div>
|
||||
</div>
|
||||
{{#with settings}}
|
||||
<div id="previewpane" class="{{settingsClass}}" style="background:{{bgcolor}}">
|
||||
<div id="preview-wrapper">
|
||||
{{#if match "positioning:inline"}}
|
||||
Here is a place to sign in, yay!
|
||||
{{/if}}
|
||||
{{> loginButtons align=dropdownAlign}}
|
||||
{{#if match "positioning:inline"}}
|
||||
Isn't that great?
|
||||
{{/if}}
|
||||
</div>
|
||||
<div id="pos-indicator"></div>
|
||||
{{#unless match "positioning:absolute"}}
|
||||
<div style="clear:both">
|
||||
A line that shouldn't move when logging in and logging out
|
||||
</div>
|
||||
{{/unless}}
|
||||
</div>
|
||||
{{/with}}
|
||||
</template>
|
||||
@@ -1,221 +0,0 @@
|
||||
|
||||
Meteor.users.allow({ update: () => true });
|
||||
|
||||
const { ServiceConfiguration } = Package['service-configuration'];
|
||||
|
||||
Meteor.methods({
|
||||
'removeService': service => ServiceConfiguration.configurations.remove({ service }),
|
||||
})
|
||||
|
||||
if (Meteor.isClient) {
|
||||
|
||||
Accounts.STASH = { ...Accounts };
|
||||
Accounts.STASH.loggingIn = Meteor.loggingIn;
|
||||
|
||||
const handleSetting = (key, value) => {
|
||||
if (key === "numServices") {
|
||||
const registeredServices = Accounts.oauth.serviceNames();
|
||||
['facebook', 'github', 'google'].forEach((serv, i) => {
|
||||
if (i < value && !registeredServices.includes(serv)) {
|
||||
Accounts.oauth.registerService(serv);
|
||||
} else if (i >= value && registeredServices.includes(serv)) {
|
||||
Accounts.oauth.unregisterService(serv);
|
||||
}
|
||||
});
|
||||
} else if (key === "hasPasswords") {
|
||||
Package['accounts-password'] = value ? {} : null;
|
||||
const user = Meteor.user();
|
||||
if (user) {
|
||||
if (! value) {
|
||||
// make sure we have no username if "app" has no passwords
|
||||
Meteor.users.update(Meteor.userId(),
|
||||
{ $unset: { username: 1 }});
|
||||
} else {
|
||||
// make sure we have a username
|
||||
Meteor.users.update(Meteor.userId(),
|
||||
{ $set: { username: Random.id() }});
|
||||
}
|
||||
}
|
||||
} else if (key === "signupFields") {
|
||||
Accounts.ui._options.passwordSignupFields = value;
|
||||
} else if (key === "fakeLoggingIn") {
|
||||
Meteor.loggingIn = (value ? () => true :
|
||||
Accounts.STASH.loggingIn);
|
||||
}
|
||||
};
|
||||
|
||||
const settings = Session.get('settings');
|
||||
if (! settings) {
|
||||
Session.set('settings', {
|
||||
alignRight: false,
|
||||
positioning: "relative",
|
||||
numServices: 3,
|
||||
hasPasswords: true,
|
||||
signupFields: 'EMAIL_ONLY',
|
||||
fakeLoggingIn: false,
|
||||
bgcolor: 'white'
|
||||
});
|
||||
} else {
|
||||
Object.keys(settings).forEach(key => handleSetting(key, settings[key]));
|
||||
}
|
||||
|
||||
Template.page.helpers({
|
||||
settings: () => Session.get('settings'),
|
||||
settingsClass: () => {
|
||||
var settings = Session.get('settings');
|
||||
var classes = [];
|
||||
if (settings.positioning)
|
||||
classes.push('positioning-' + settings.positioning.toLowerCase());
|
||||
return classes.join(' ');
|
||||
},
|
||||
match: kv => {
|
||||
kv = keyValueFromId(kv);
|
||||
if (! kv)
|
||||
return false;
|
||||
|
||||
return Session.get('settings')[kv[0]] === kv[1];
|
||||
},
|
||||
dropdownAlign: function() {
|
||||
var settings = this;
|
||||
return settings.alignRight ? 'right' : 'left';
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var keyValueFromId = function (id) {
|
||||
var match;
|
||||
if (id && (match = /^(.*?):(.*)$/.exec(id))) {
|
||||
var key = match[1];
|
||||
var value = castValue(match[2]);
|
||||
return [key, value];
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const castValue = value => {
|
||||
if (value === "false")
|
||||
value = false;
|
||||
else if (value === "true")
|
||||
value = true;
|
||||
else if (/^[0-9]+$/.test(value))
|
||||
value = Number(value);
|
||||
return value;
|
||||
};
|
||||
|
||||
Template.radio.helpers({
|
||||
maybeChecked: function() {
|
||||
var curValue = Session.get('settings')[this.key];
|
||||
if (castValue(this.value) === curValue)
|
||||
return 'checked';
|
||||
return '';
|
||||
},
|
||||
});
|
||||
|
||||
const fakeLogin = callback => {
|
||||
Accounts.createUser(
|
||||
{username: Random.id(),
|
||||
password: "password",
|
||||
profile: { name: "Joe Schmoe" }},
|
||||
() => {
|
||||
var user = Meteor.user();
|
||||
if (! user)
|
||||
return;
|
||||
// delete our username if we are in a mode
|
||||
// where there aren't usernames/emails/passwords
|
||||
// (only third-party auth) so that there is no
|
||||
// "Change Password" button when signed in
|
||||
if (! Session.get('settings').hasPasswords)
|
||||
Meteor.users.update(Meteor.userId(),
|
||||
{ $unset: { username: 1 }});
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
const exitFlows = () => {
|
||||
Accounts._loginButtonsSession.set('inSignupFlow', false);
|
||||
Accounts._loginButtonsSession.set('inForgotPasswordFlow', false);
|
||||
Accounts._loginButtonsSession.set('inChangePasswordFlow', false);
|
||||
Accounts._loginButtonsSession.set('inMessageOnlyFlow', false);
|
||||
};
|
||||
|
||||
Template.page.events({
|
||||
'change #controlpane input[type=radio]': event => {
|
||||
const input = event.currentTarget;
|
||||
let keyValue;
|
||||
if (input && input.id && (keyValue = keyValueFromId(input.id))) {
|
||||
const key = keyValue[0];
|
||||
const value = keyValue[1];
|
||||
if (value === "false")
|
||||
value = false;
|
||||
else if (value === "true")
|
||||
value = true;
|
||||
const settings = Session.get('settings');
|
||||
settings[key] = value;
|
||||
Session.set('settings', settings);
|
||||
|
||||
handleSetting(key, value);
|
||||
}
|
||||
},
|
||||
'click #controlpane button': function (event) {
|
||||
const { ServiceConfiguration } = Package['service-configuration'];
|
||||
if (this.key === "fakeConfig") {
|
||||
const service = this.value;
|
||||
if (! ServiceConfiguration.configurations.findOne({ service }))
|
||||
ServiceConfiguration.configurations.insert(
|
||||
{ service, fake: true });
|
||||
} else if (this.key === "unconfig") {
|
||||
const service = this.value;
|
||||
Meteor.call('removeService', service);
|
||||
} else if (this.key === "messages") {
|
||||
if (this.value === "error") {
|
||||
Accounts._loginButtonsSession.errorMessage('An error occurred! Gee golly gosh.');
|
||||
} else if (this.value === "info") {
|
||||
Accounts._loginButtonsSession.infoMessage('Here is some information that is crucial.');
|
||||
} else if (this.value === "clear") {
|
||||
Accounts._loginButtonsSession.resetMessages();
|
||||
}
|
||||
} else if (this.key === "sign") {
|
||||
if (this.value === 'in') {
|
||||
// create a random new user
|
||||
fakeLogin(function () {
|
||||
Accounts._loginButtonsSession.closeDropdown();
|
||||
});
|
||||
} else if (this.value === 'out') {
|
||||
Meteor.logout();
|
||||
}
|
||||
} else if (this.key === "showConfig") {
|
||||
Accounts._loginButtonsSession.configureService(this.value);
|
||||
} else if (this.key === "lov") {
|
||||
exitFlows();
|
||||
Accounts._loginButtonsSession.set("dropdownVisible", true);
|
||||
if (Meteor.userId())
|
||||
Meteor.logout();
|
||||
if (this.value === "createAccount")
|
||||
Accounts._loginButtonsSession.set("inSignupFlow", true);
|
||||
else if (this.value === "forgotPassword")
|
||||
Accounts._loginButtonsSession.set("inForgotPasswordFlow", true);
|
||||
} else if (this.key === "liv") {
|
||||
exitFlows();
|
||||
Accounts._loginButtonsSession.set("dropdownVisible", true);
|
||||
if (! Meteor.userId())
|
||||
fakeLogin(() => {});
|
||||
if (this.value === "changePassword")
|
||||
Accounts._loginButtonsSession.set("inChangePasswordFlow", true);
|
||||
else if (this.value === "messageOnly")
|
||||
Accounts._loginButtonsSession.set("inMessageOnlyFlow", true);
|
||||
} else if (this.key === "modals") {
|
||||
const { value } = this;
|
||||
[
|
||||
'resetPasswordToken',
|
||||
'enrollAccountToken',
|
||||
'justVerifiedEmail'
|
||||
].forEach(k => {
|
||||
Accounts._loginButtonsSession.set(
|
||||
k, k.indexOf(value) >= 0 ? 'foo' : null
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
html, body { height: 100%; }
|
||||
|
||||
#controlpane {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 299px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
|
||||
background: #eee;
|
||||
border-right: 1px solid #999;
|
||||
|
||||
overflow: auto;
|
||||
|
||||
h3 {
|
||||
border-top: 1px solid #999;
|
||||
font-size: 85%;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.group {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
input[type=radio] {
|
||||
margin-left: 5px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
label {
|
||||
padding-left: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
#previewpane {
|
||||
position: absolute;
|
||||
left: 300px;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
|
||||
#preview-wrapper {
|
||||
margin: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.radio {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.positioning-floatright {
|
||||
#login-buttons {
|
||||
float: right;
|
||||
margin-right: 180px;
|
||||
}
|
||||
|
||||
#pos-indicator {
|
||||
display: block;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 200px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.positioning-relative {
|
||||
#login-buttons {
|
||||
position: relative;
|
||||
left: 150px;
|
||||
top: 20px;
|
||||
}
|
||||
|
||||
#pos-indicator {
|
||||
display: block;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 170px;
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.positioning-absolute {
|
||||
#login-buttons {
|
||||
position: absolute;
|
||||
left: 170px;
|
||||
top: 40px;
|
||||
}
|
||||
|
||||
#pos-indicator {
|
||||
display: block;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 170px;
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
#pos-indicator {
|
||||
position: absolute;
|
||||
background: #eec;
|
||||
display: none;
|
||||
}
|
||||
|
||||
a { color: blue; }
|
||||
|
||||
button { padding: 4px;
|
||||
margin-bottom: 4px; // for when buttons wrap
|
||||
}
|
||||
682
examples/unfinished/accounts-ui-viewer/package-lock.json
generated
682
examples/unfinished/accounts-ui-viewer/package-lock.json
generated
@@ -1,682 +0,0 @@
|
||||
{
|
||||
"requires": true,
|
||||
"lockfileVersion": 1,
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.0.0-beta.38",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0-beta.38.tgz",
|
||||
"integrity": "sha512-ZvPtlcvH2ZRzr1U5pkmCE7U3RIun3Nf29XHem47aScmJgMuL06ulkp+4oPBee3QrUVFErDjwNWtC67BzNuxLVw==",
|
||||
"requires": {
|
||||
"core-js": "2.5.3",
|
||||
"regenerator-runtime": "0.11.1"
|
||||
}
|
||||
},
|
||||
"core-js": {
|
||||
"version": "2.5.3",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz",
|
||||
"integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4="
|
||||
},
|
||||
"meteor-node-stubs": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/meteor-node-stubs/-/meteor-node-stubs-0.3.2.tgz",
|
||||
"integrity": "sha512-l93SS/HutbqBRJODO2m7hup8cYI2acF5bB39+ZvP2BX8HMmCSCXeFH7v0sr4hD7zrVvHQA5UqS0pcDYKn0VM6g==",
|
||||
"requires": {
|
||||
"assert": "1.4.1",
|
||||
"browserify-zlib": "0.1.4",
|
||||
"buffer": "4.9.1",
|
||||
"console-browserify": "1.1.0",
|
||||
"constants-browserify": "1.0.0",
|
||||
"crypto-browserify": "3.11.1",
|
||||
"domain-browser": "1.1.7",
|
||||
"events": "1.1.1",
|
||||
"http-browserify": "1.7.0",
|
||||
"https-browserify": "0.0.1",
|
||||
"os-browserify": "0.2.1",
|
||||
"path-browserify": "0.0.0",
|
||||
"process": "0.11.10",
|
||||
"punycode": "1.4.1",
|
||||
"querystring-es3": "0.2.1",
|
||||
"readable-stream": "git+https://github.com/meteor/readable-stream.git#d64a64aa6061b9b6855feff4d09e58fb3b2e4502",
|
||||
"stream-browserify": "2.0.1",
|
||||
"string_decoder": "1.0.3",
|
||||
"timers-browserify": "1.4.2",
|
||||
"tty-browserify": "0.0.0",
|
||||
"url": "0.11.0",
|
||||
"util": "0.10.3",
|
||||
"vm-browserify": "0.0.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"Base64": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/Base64/-/Base64-0.2.1.tgz",
|
||||
"integrity": "sha1-ujpCMHCOGGcFBl5mur3Uw1z2ACg="
|
||||
},
|
||||
"asn1.js": {
|
||||
"version": "4.9.1",
|
||||
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.9.1.tgz",
|
||||
"integrity": "sha1-SLokC0WpKA6UdImQull9IWYX/UA=",
|
||||
"requires": {
|
||||
"bn.js": "4.11.8",
|
||||
"inherits": "2.0.1",
|
||||
"minimalistic-assert": "1.0.0"
|
||||
}
|
||||
},
|
||||
"assert": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz",
|
||||
"integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=",
|
||||
"requires": {
|
||||
"util": "0.10.3"
|
||||
}
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
|
||||
},
|
||||
"base64-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz",
|
||||
"integrity": "sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw=="
|
||||
},
|
||||
"bn.js": {
|
||||
"version": "4.11.8",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
|
||||
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
|
||||
"integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=",
|
||||
"requires": {
|
||||
"balanced-match": "1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"brorand": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
|
||||
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8="
|
||||
},
|
||||
"browserify-aes": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.1.0.tgz",
|
||||
"integrity": "sha512-W2bIMLYoZ9oow7TyePpMJk9l9LY7O3R61a/68bVCDOtnJynnwe3ZeW2IzzSkrQnPKNdJrxVDn3ALZNisSBwb7g==",
|
||||
"requires": {
|
||||
"buffer-xor": "1.0.3",
|
||||
"cipher-base": "1.0.4",
|
||||
"create-hash": "1.1.3",
|
||||
"evp_bytestokey": "1.0.3",
|
||||
"inherits": "2.0.1",
|
||||
"safe-buffer": "5.1.1"
|
||||
}
|
||||
},
|
||||
"browserify-cipher": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.0.tgz",
|
||||
"integrity": "sha1-mYgkSHS/XtTijalWZtzWasj8Njo=",
|
||||
"requires": {
|
||||
"browserify-aes": "1.1.0",
|
||||
"browserify-des": "1.0.0",
|
||||
"evp_bytestokey": "1.0.3"
|
||||
}
|
||||
},
|
||||
"browserify-des": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.0.tgz",
|
||||
"integrity": "sha1-2qJ3cXRwki7S/hhZQRihdUOXId0=",
|
||||
"requires": {
|
||||
"cipher-base": "1.0.4",
|
||||
"des.js": "1.0.0",
|
||||
"inherits": "2.0.1"
|
||||
}
|
||||
},
|
||||
"browserify-rsa": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
|
||||
"integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
|
||||
"requires": {
|
||||
"bn.js": "4.11.8",
|
||||
"randombytes": "2.0.5"
|
||||
}
|
||||
},
|
||||
"browserify-sign": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz",
|
||||
"integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=",
|
||||
"requires": {
|
||||
"bn.js": "4.11.8",
|
||||
"browserify-rsa": "4.0.1",
|
||||
"create-hash": "1.1.3",
|
||||
"create-hmac": "1.1.6",
|
||||
"elliptic": "6.4.0",
|
||||
"inherits": "2.0.1",
|
||||
"parse-asn1": "5.1.0"
|
||||
}
|
||||
},
|
||||
"browserify-zlib": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz",
|
||||
"integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=",
|
||||
"requires": {
|
||||
"pako": "0.2.9"
|
||||
}
|
||||
},
|
||||
"buffer": {
|
||||
"version": "4.9.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
|
||||
"integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
|
||||
"requires": {
|
||||
"base64-js": "1.2.1",
|
||||
"ieee754": "1.1.8",
|
||||
"isarray": "1.0.0"
|
||||
}
|
||||
},
|
||||
"buffer-xor": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
|
||||
"integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk="
|
||||
},
|
||||
"cipher-base": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
|
||||
"integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
|
||||
"requires": {
|
||||
"inherits": "2.0.1",
|
||||
"safe-buffer": "5.1.1"
|
||||
}
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
|
||||
},
|
||||
"console-browserify": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz",
|
||||
"integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=",
|
||||
"requires": {
|
||||
"date-now": "0.1.4"
|
||||
}
|
||||
},
|
||||
"constants-browserify": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
|
||||
"integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U="
|
||||
},
|
||||
"create-ecdh": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.0.tgz",
|
||||
"integrity": "sha1-iIxyNZbN92EvZJgjPuvXo1MBc30=",
|
||||
"requires": {
|
||||
"bn.js": "4.11.8",
|
||||
"elliptic": "6.4.0"
|
||||
}
|
||||
},
|
||||
"create-hash": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz",
|
||||
"integrity": "sha1-YGBCrIuSYnUPSDyt2rD1gZFy2P0=",
|
||||
"requires": {
|
||||
"cipher-base": "1.0.4",
|
||||
"inherits": "2.0.1",
|
||||
"ripemd160": "2.0.1",
|
||||
"sha.js": "2.4.9"
|
||||
}
|
||||
},
|
||||
"create-hmac": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.6.tgz",
|
||||
"integrity": "sha1-rLniIaThe9sHbpBlfEK5PjcmzwY=",
|
||||
"requires": {
|
||||
"cipher-base": "1.0.4",
|
||||
"create-hash": "1.1.3",
|
||||
"inherits": "2.0.1",
|
||||
"ripemd160": "2.0.1",
|
||||
"safe-buffer": "5.1.1",
|
||||
"sha.js": "2.4.9"
|
||||
}
|
||||
},
|
||||
"crypto-browserify": {
|
||||
"version": "3.11.1",
|
||||
"resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.11.1.tgz",
|
||||
"integrity": "sha512-Na7ZlwCOqoaW5RwUK1WpXws2kv8mNhWdTlzob0UXulk6G9BDbyiJaGTYBIX61Ozn9l1EPPJpICZb4DaOpT9NlQ==",
|
||||
"requires": {
|
||||
"browserify-cipher": "1.0.0",
|
||||
"browserify-sign": "4.0.4",
|
||||
"create-ecdh": "4.0.0",
|
||||
"create-hash": "1.1.3",
|
||||
"create-hmac": "1.1.6",
|
||||
"diffie-hellman": "5.0.2",
|
||||
"inherits": "2.0.1",
|
||||
"pbkdf2": "3.0.14",
|
||||
"public-encrypt": "4.0.0",
|
||||
"randombytes": "2.0.5"
|
||||
}
|
||||
},
|
||||
"date-now": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz",
|
||||
"integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs="
|
||||
},
|
||||
"des.js": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz",
|
||||
"integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=",
|
||||
"requires": {
|
||||
"inherits": "2.0.1",
|
||||
"minimalistic-assert": "1.0.0"
|
||||
}
|
||||
},
|
||||
"diffie-hellman": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.2.tgz",
|
||||
"integrity": "sha1-tYNXOScM/ias9jIJn97SoH8gnl4=",
|
||||
"requires": {
|
||||
"bn.js": "4.11.8",
|
||||
"miller-rabin": "4.0.1",
|
||||
"randombytes": "2.0.5"
|
||||
}
|
||||
},
|
||||
"domain-browser": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz",
|
||||
"integrity": "sha1-hnqksJP6oF8d4IwG9NeyH9+GmLw="
|
||||
},
|
||||
"elliptic": {
|
||||
"version": "6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz",
|
||||
"integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=",
|
||||
"requires": {
|
||||
"bn.js": "4.11.8",
|
||||
"brorand": "1.1.0",
|
||||
"hash.js": "1.1.3",
|
||||
"hmac-drbg": "1.0.1",
|
||||
"inherits": "2.0.1",
|
||||
"minimalistic-assert": "1.0.0",
|
||||
"minimalistic-crypto-utils": "1.0.1"
|
||||
}
|
||||
},
|
||||
"events": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
|
||||
"integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ="
|
||||
},
|
||||
"evp_bytestokey": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
|
||||
"integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
|
||||
"requires": {
|
||||
"md5.js": "1.3.4",
|
||||
"safe-buffer": "5.1.1"
|
||||
}
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
|
||||
"integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
|
||||
"requires": {
|
||||
"fs.realpath": "1.0.0",
|
||||
"inflight": "1.0.6",
|
||||
"inherits": "2.0.1",
|
||||
"minimatch": "3.0.4",
|
||||
"once": "1.4.0",
|
||||
"path-is-absolute": "1.0.1"
|
||||
}
|
||||
},
|
||||
"hash-base": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz",
|
||||
"integrity": "sha1-ZuodhW206KVHDK32/OI65SRO8uE=",
|
||||
"requires": {
|
||||
"inherits": "2.0.1"
|
||||
}
|
||||
},
|
||||
"hash.js": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz",
|
||||
"integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==",
|
||||
"requires": {
|
||||
"inherits": "2.0.3",
|
||||
"minimalistic-assert": "1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
}
|
||||
}
|
||||
},
|
||||
"hmac-drbg": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
|
||||
"integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
|
||||
"requires": {
|
||||
"hash.js": "1.1.3",
|
||||
"minimalistic-assert": "1.0.0",
|
||||
"minimalistic-crypto-utils": "1.0.1"
|
||||
}
|
||||
},
|
||||
"http-browserify": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/http-browserify/-/http-browserify-1.7.0.tgz",
|
||||
"integrity": "sha1-M3la3nLfiKz7/TZ3PO/tp2RzWyA=",
|
||||
"requires": {
|
||||
"Base64": "0.2.1",
|
||||
"inherits": "2.0.1"
|
||||
}
|
||||
},
|
||||
"https-browserify": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-0.0.1.tgz",
|
||||
"integrity": "sha1-P5E2XKvmC3ftDruiS0VOPgnZWoI="
|
||||
},
|
||||
"ieee754": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz",
|
||||
"integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q="
|
||||
},
|
||||
"indexof": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
|
||||
"integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10="
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||
"requires": {
|
||||
"once": "1.4.0",
|
||||
"wrappy": "1.0.2"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
|
||||
"integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE="
|
||||
},
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
},
|
||||
"md5.js": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz",
|
||||
"integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=",
|
||||
"requires": {
|
||||
"hash-base": "3.0.4",
|
||||
"inherits": "2.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"hash-base": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz",
|
||||
"integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=",
|
||||
"requires": {
|
||||
"inherits": "2.0.1",
|
||||
"safe-buffer": "5.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"miller-rabin": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz",
|
||||
"integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==",
|
||||
"requires": {
|
||||
"bn.js": "4.11.8",
|
||||
"brorand": "1.1.0"
|
||||
}
|
||||
},
|
||||
"minimalistic-assert": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz",
|
||||
"integrity": "sha1-cCvi3aazf0g2vLP121ZkG2Sh09M="
|
||||
},
|
||||
"minimalistic-crypto-utils": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
|
||||
"integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo="
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"requires": {
|
||||
"brace-expansion": "1.1.8"
|
||||
}
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"requires": {
|
||||
"wrappy": "1.0.2"
|
||||
}
|
||||
},
|
||||
"os-browserify": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.2.1.tgz",
|
||||
"integrity": "sha1-Y/xMzuXS13Y9Jrv4YBB45sLgBE8="
|
||||
},
|
||||
"pako": {
|
||||
"version": "0.2.9",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
|
||||
"integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU="
|
||||
},
|
||||
"parse-asn1": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.0.tgz",
|
||||
"integrity": "sha1-N8T5t+06tlx0gXtfJICTf7+XxxI=",
|
||||
"requires": {
|
||||
"asn1.js": "4.9.1",
|
||||
"browserify-aes": "1.1.0",
|
||||
"create-hash": "1.1.3",
|
||||
"evp_bytestokey": "1.0.3",
|
||||
"pbkdf2": "3.0.14"
|
||||
}
|
||||
},
|
||||
"path-browserify": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz",
|
||||
"integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo="
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
|
||||
},
|
||||
"pbkdf2": {
|
||||
"version": "3.0.14",
|
||||
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.14.tgz",
|
||||
"integrity": "sha512-gjsZW9O34fm0R7PaLHRJmLLVfSoesxztjPjE9o6R+qtVJij90ltg1joIovN9GKrRW3t1PzhDDG3UMEMFfZ+1wA==",
|
||||
"requires": {
|
||||
"create-hash": "1.1.3",
|
||||
"create-hmac": "1.1.6",
|
||||
"ripemd160": "2.0.1",
|
||||
"safe-buffer": "5.1.1",
|
||||
"sha.js": "2.4.9"
|
||||
}
|
||||
},
|
||||
"process": {
|
||||
"version": "0.11.10",
|
||||
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
|
||||
"integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI="
|
||||
},
|
||||
"process-nextick-args": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
|
||||
"integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
|
||||
},
|
||||
"public-encrypt": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.0.tgz",
|
||||
"integrity": "sha1-OfaZ86RlYN1eusvKaTyvfGXBjMY=",
|
||||
"requires": {
|
||||
"bn.js": "4.11.8",
|
||||
"browserify-rsa": "4.0.1",
|
||||
"create-hash": "1.1.3",
|
||||
"parse-asn1": "5.1.0",
|
||||
"randombytes": "2.0.5"
|
||||
}
|
||||
},
|
||||
"punycode": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
|
||||
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
|
||||
},
|
||||
"querystring": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
|
||||
"integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA="
|
||||
},
|
||||
"querystring-es3": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
|
||||
"integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM="
|
||||
},
|
||||
"randombytes": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.5.tgz",
|
||||
"integrity": "sha512-8T7Zn1AhMsQ/HI1SjcCfT/t4ii3eAqco3yOcSzS4mozsOz69lHLsoMXmF9nZgnFanYscnSlUSgs8uZyKzpE6kg==",
|
||||
"requires": {
|
||||
"safe-buffer": "5.1.1"
|
||||
}
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "git+https://github.com/meteor/readable-stream.git#d64a64aa6061b9b6855feff4d09e58fb3b2e4502",
|
||||
"requires": {
|
||||
"inherits": "2.0.3",
|
||||
"isarray": "1.0.0",
|
||||
"process-nextick-args": "1.0.7",
|
||||
"safe-buffer": "5.1.1",
|
||||
"string_decoder": "1.0.3",
|
||||
"util-deprecate": "1.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
}
|
||||
}
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
|
||||
"integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==",
|
||||
"requires": {
|
||||
"glob": "7.1.2"
|
||||
}
|
||||
},
|
||||
"ripemd160": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz",
|
||||
"integrity": "sha1-D0WEKVxTo2KK9+bXmsohzlfRxuc=",
|
||||
"requires": {
|
||||
"hash-base": "2.0.2",
|
||||
"inherits": "2.0.1"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
|
||||
"integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
|
||||
},
|
||||
"sha.js": {
|
||||
"version": "2.4.9",
|
||||
"resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.9.tgz",
|
||||
"integrity": "sha512-G8zektVqbiPHrylgew9Zg1VRB1L/DtXNUVAM6q4QLy8NE3qtHlFXTf8VLL4k1Yl6c7NMjtZUTdXV+X44nFaT6A==",
|
||||
"requires": {
|
||||
"inherits": "2.0.1",
|
||||
"safe-buffer": "5.1.1"
|
||||
}
|
||||
},
|
||||
"stream-browserify": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz",
|
||||
"integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=",
|
||||
"requires": {
|
||||
"inherits": "2.0.1",
|
||||
"readable-stream": "git+https://github.com/meteor/readable-stream.git#d64a64aa6061b9b6855feff4d09e58fb3b2e4502"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
|
||||
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
|
||||
"requires": {
|
||||
"safe-buffer": "5.1.1"
|
||||
}
|
||||
},
|
||||
"timers-browserify": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz",
|
||||
"integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=",
|
||||
"requires": {
|
||||
"process": "0.11.10"
|
||||
}
|
||||
},
|
||||
"tty-browserify": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
|
||||
"integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY="
|
||||
},
|
||||
"url": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
|
||||
"integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
|
||||
"requires": {
|
||||
"punycode": "1.3.2",
|
||||
"querystring": "0.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"punycode": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
|
||||
"integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0="
|
||||
}
|
||||
}
|
||||
},
|
||||
"util": {
|
||||
"version": "0.10.3",
|
||||
"resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
|
||||
"integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
|
||||
"requires": {
|
||||
"inherits": "2.0.1"
|
||||
}
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
|
||||
},
|
||||
"vm-browserify": {
|
||||
"version": "0.0.4",
|
||||
"resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz",
|
||||
"integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=",
|
||||
"requires": {
|
||||
"indexof": "0.0.1"
|
||||
}
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
}
|
||||
}
|
||||
},
|
||||
"regenerator-runtime": {
|
||||
"version": "0.11.1",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
|
||||
"integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"name": "accounts-ui-viewer",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "meteor run"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.0.0-beta.38",
|
||||
"meteor-node-stubs": "^0.3.2"
|
||||
}
|
||||
}
|
||||
1
examples/unfinished/atoms/.meteor/.gitignore
vendored
1
examples/unfinished/atoms/.meteor/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
local
|
||||
@@ -1,9 +0,0 @@
|
||||
# Meteor packages used by this project, one per line.
|
||||
#
|
||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||
# but you can also edit it by hand.
|
||||
|
||||
standard-app-packages
|
||||
autopublish
|
||||
insecure
|
||||
underscore
|
||||
@@ -1 +0,0 @@
|
||||
none
|
||||
@@ -1,12 +0,0 @@
|
||||
g[class=atom] circle {
|
||||
stroke: black;
|
||||
stroke-width: 3px;
|
||||
}
|
||||
|
||||
g[class=atom] text {
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 24px;
|
||||
fill: black;
|
||||
font-weight: bold;
|
||||
text-anchor: middle;
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
<head>
|
||||
<title>Atoms</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="atoms">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" viewBox="0 0 500 500">
|
||||
{{#atom x=100 y=100 color="#ffff00"}}O{{/atom}}
|
||||
{{> hydrogen x=150 y=100}}
|
||||
{{#giantatom x=250 y=100 color="#ff9999"}}My{{/giantatom}}
|
||||
</svg>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<template name="atom">
|
||||
<g class="atom">
|
||||
<circle style="fill: {{#if color}}{{color}}{{else}}white{{/if}}" cx={{x}} cy={{y}} r={{#if r}}{{r}}{{else}}20{{/if}} />
|
||||
<text x={{x}} y={{textY}}>{{> UI.contentBlock}}</text>
|
||||
</g>
|
||||
</template>
|
||||
|
||||
<template name="hydrogen">
|
||||
{{#atom x=x y=y r=r color="#00ffff"}}H{{/atom}}
|
||||
</template>
|
||||
|
||||
<template name="giantatom">
|
||||
{{#atom x=x y=y r=50 color=color}}{{> UI.contentBlock}}{{/atom}}
|
||||
</template>
|
||||
@@ -1,5 +0,0 @@
|
||||
if (Meteor.isClient) {
|
||||
Template.atom.textY = function () {
|
||||
return this.y + 8;
|
||||
};
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
local
|
||||
@@ -1,9 +0,0 @@
|
||||
# Meteor packages used by this project, one per line.
|
||||
#
|
||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||
# but you can also edit it by hand.
|
||||
|
||||
underscore
|
||||
jquery
|
||||
jquery-layout
|
||||
standard-app-packages
|
||||
@@ -1 +0,0 @@
|
||||
0.6.0
|
||||
@@ -1,26 +0,0 @@
|
||||
#room-list .room.selected {
|
||||
color: white;
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
.room .name {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.room .delete {
|
||||
float: right;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.room:hover .delete {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.add-room {
|
||||
margin-top: 20px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.add-room:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
<body>
|
||||
|
||||
<div class="ui-layout-center" id="chat-view">
|
||||
{{> center_pane }}
|
||||
</div>
|
||||
<div class="ui-layout-east">East</div>
|
||||
<div class="ui-layout-west">
|
||||
{{> room_list}}
|
||||
{{> add_room}}
|
||||
</div>
|
||||
<div class="ui-layout-north">Azrael</div>
|
||||
|
||||
</body>
|
||||
|
||||
<template name="room_list">
|
||||
<div id="room-list">
|
||||
{{#each rooms}}
|
||||
{{> room}}
|
||||
{{/each}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="add_room">
|
||||
<div class="add-room">
|
||||
Create new room
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="room">
|
||||
<div class="room {{maybe_selected}}">
|
||||
{{#if editing}}
|
||||
<input id="room_name_input" value="{{name}}">
|
||||
{{else}}
|
||||
<div class="name">{{name}}</div>
|
||||
<div class="delete">(x)</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="center_pane">
|
||||
<div id="center-pane">
|
||||
{{#if any_room_selected}}
|
||||
<div id="chat">
|
||||
{{#each messages}}
|
||||
{{> chat_message}}
|
||||
{{else}}
|
||||
No chat yet!
|
||||
{{/each}}
|
||||
</div>
|
||||
<input id="chat-entry">
|
||||
{{else}}
|
||||
No room selected
|
||||
{{/if}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="chat_message">
|
||||
<div>
|
||||
{{username}}: {{message}}
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,128 +0,0 @@
|
||||
Meteor.subscribe('rooms');
|
||||
|
||||
Session.set('current_room', null);
|
||||
Session.set('editing_room_name', false);
|
||||
|
||||
Deps.autorun(function () {
|
||||
var room_id = Session.get('current_room');
|
||||
if (room_id) Meteor.subscribe('room-detail', room_id);
|
||||
});
|
||||
|
||||
// XXX would be nice to eliminate this function and have people just
|
||||
// call Session.set("current_room", foo) directly instead
|
||||
var selectRoom = function (room_id) {
|
||||
// XXX pushstate
|
||||
var room = Rooms.find(room_id);
|
||||
Session.set('current_room', room_id);
|
||||
};
|
||||
|
||||
Meteor.startup(function () {
|
||||
$('body').layout({applyDefaultStyles: true})
|
||||
});
|
||||
|
||||
Template.room_list.rooms = function () {
|
||||
// XXX it would be nice if this were find instead of findLive (ie,
|
||||
// if they were unified in some sane way)
|
||||
return Rooms.findLive({}, {sort: {name: 1}});
|
||||
};
|
||||
|
||||
Template.add_room.events = {
|
||||
'click': function () {
|
||||
// XXX should put up dialog to get name
|
||||
// XXX should support automatically set created/updated timestamps
|
||||
var room_id = Rooms.insert({name: "New room",
|
||||
// XXX horrid syntax
|
||||
created: (new Date()).getTime()});
|
||||
selectRoom(room_id);
|
||||
// XXX XXX XXX this fails to work -- it leaves edit mode after
|
||||
// 1RTT. what happens is, the server echos the insert back to us,
|
||||
// and that is currently wired up to trigger a changed event on
|
||||
// the findlive, which redraws the element, which triggers blur,
|
||||
// which causes us to set editing_room_name to false.
|
||||
//
|
||||
// one option is to have the rendering function (maybe in a
|
||||
// post-render routine?) decide if it currently wants
|
||||
// focus. (should that be within the recomputation envelope, I
|
||||
// wonder?)
|
||||
//
|
||||
// another is to suppress blur on rerender. probably the only
|
||||
// principled way to do this is to narrow the scope of the
|
||||
// rerender to not include the <input>.
|
||||
//
|
||||
// [No idea if the comment above is still current]
|
||||
Session.set('editing_room_name', true);
|
||||
Deps.flush();
|
||||
$('#room_name_input').focus();
|
||||
}
|
||||
};
|
||||
|
||||
Template.room.events = {
|
||||
'mousedown': function (evt) {
|
||||
selectRoom(this._id);
|
||||
},
|
||||
'dblclick': function (evt) {
|
||||
Session.set('editing_room_name', true);
|
||||
// XXX XXX doesn't generalize.. the element might very reasonably
|
||||
// not have a unique id. may need a different strategy..
|
||||
Deps.flush();
|
||||
$('#room_name_input').focus();
|
||||
},
|
||||
'blur input': function (evt) {
|
||||
Session.set('editing_room_name', false);
|
||||
},
|
||||
'keypress input': function (evt) {
|
||||
// XXX should really have a binding/validator-based pattern
|
||||
// XXX check to see this pattern works if you are saving
|
||||
// continuously (on every keystroke)
|
||||
var value = $(evt.target).val();
|
||||
if (evt.which === 13 && value.length)
|
||||
Rooms.update(this._id, {$set: {name: value}});
|
||||
if (evt.which === 13 || evt.which === 27)
|
||||
Session.set('editing_room_name', false);
|
||||
},
|
||||
// If you make this event be click (rather than mousedown), then
|
||||
// delete doesn't work if the room isn't already selected. what
|
||||
// happens is, the mousedown triggers the selection, which redraws
|
||||
// the room, meaning that the elements are replaced out from under
|
||||
// the event, and the click event is lost.. bleh. needs
|
||||
// reconsideration.
|
||||
'mousedown .delete': function (evt) {
|
||||
Rooms.remove('rooms', this._id);
|
||||
Session.set('current_room', null);
|
||||
},
|
||||
};
|
||||
|
||||
Template.room.editing = function (options) {
|
||||
// Check current_room first, before editing_room_name, to minimize
|
||||
// number of redraws
|
||||
return (Session.equals('current_room', this._id) &&
|
||||
Session.equals('editing_room_name', true));
|
||||
};
|
||||
|
||||
Template.room.maybe_selected = function () {
|
||||
return Session.equals('current_room', this._id) ? "selected" : "";
|
||||
};
|
||||
|
||||
Template.center_pane.messages = function () {
|
||||
return Chat.findLive({room: Session.get("current_room")},
|
||||
{sort: {created: 1}});
|
||||
};
|
||||
|
||||
Template.center_pane.any_room_selected = function () {
|
||||
return !Session.equals('current_room', null);
|
||||
};
|
||||
|
||||
Template.center_pane.events = {
|
||||
'keydown #chat-entry': function (evt) {
|
||||
if (evt.which === 13) {
|
||||
var room_id = Session.get('current_room');
|
||||
if (!room_id)
|
||||
return;
|
||||
|
||||
Chat.insert({room: room_id, message: $(evt.target).val(),
|
||||
username: "someone",
|
||||
created: (new Date()).getTime()});
|
||||
$(evt.target).val('');
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,21 +0,0 @@
|
||||
// XXX it is actually very dangerous to store times as Number. use
|
||||
// Date type once it's implemented in minimongo
|
||||
Rooms = new Mongo.Collection("rooms");
|
||||
//Rooms.schema({name: String, created: Number});
|
||||
|
||||
Chat = new Mongo.Collection("chat");
|
||||
/*
|
||||
Chat.schema({room: String, message: String,
|
||||
username: String, created: Number});
|
||||
*/
|
||||
|
||||
if (Meteor.isServer) {
|
||||
Meteor.publish('rooms', function () {
|
||||
return Rooms.find();
|
||||
});
|
||||
|
||||
// XXX should limit to just a certain amount of recent chat ..
|
||||
Meteor.publish('room-detail', function (room) {
|
||||
return Chat.find({room: room});
|
||||
});
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
local
|
||||
@@ -1,10 +0,0 @@
|
||||
# Meteor packages used by this project, one per line.
|
||||
#
|
||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||
# but you can also edit it by hand.
|
||||
|
||||
insecure
|
||||
preserve-inputs
|
||||
bootstrap
|
||||
random
|
||||
standard-app-packages
|
||||
@@ -1 +0,0 @@
|
||||
0.6.5.1
|
||||
@@ -1 +0,0 @@
|
||||
/* CSS declarations go here */
|
||||
@@ -1,23 +0,0 @@
|
||||
<head>
|
||||
<title>benchmark</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{{> status}}
|
||||
|
||||
{{> params}}
|
||||
</body>
|
||||
|
||||
<template name="status">
|
||||
<p>Status: {{status}}</p>
|
||||
<p>Update Rate: {{updateRate}}</p>
|
||||
</template>
|
||||
|
||||
<template name="params">
|
||||
<dl>
|
||||
{{#each params}}
|
||||
<dt>{{key}}</dt><dd>{{value}}</dd>
|
||||
{{/each}}
|
||||
</dl>
|
||||
</template>
|
||||
|
||||
@@ -1,244 +0,0 @@
|
||||
|
||||
// Pick scenario from settings.
|
||||
// XXX settings now has public. could move stuff there and avoid this.
|
||||
var PARAMS = {};
|
||||
if (Meteor.isServer) {
|
||||
if (!Meteor.settings.params)
|
||||
throw new Error("Must set scenario with Meteor.settings");
|
||||
__meteor_runtime_config__.PARAMS = PARAMS = Meteor.settings.params;
|
||||
} else {
|
||||
PARAMS = __meteor_runtime_config__.PARAMS;
|
||||
}
|
||||
|
||||
|
||||
// id for this client or server.
|
||||
var processId = Random.id();
|
||||
console.log("processId", processId);
|
||||
|
||||
|
||||
//////////////////////////////
|
||||
// Helper Functions
|
||||
//////////////////////////////
|
||||
|
||||
var random = function (n) {
|
||||
return Math.floor(Random.fraction() * n);
|
||||
};
|
||||
|
||||
var randomChars =
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'.split('');
|
||||
var randomString = function (length) {
|
||||
// XXX make more efficient
|
||||
var ret = '';
|
||||
_.times(length, function () {
|
||||
ret += Random.choice(randomChars);
|
||||
});
|
||||
return ret;
|
||||
};
|
||||
|
||||
var preCall = function (name) {
|
||||
console.log('> ' + name);
|
||||
};
|
||||
|
||||
var postCall = function (name) {
|
||||
return function (err, callback) {
|
||||
console.log('< ' + name + ' ' + (err ? 'ERR' : 'OK'));
|
||||
};
|
||||
};
|
||||
|
||||
var pickCollection = function () {
|
||||
return Random.choice(Collections);
|
||||
};
|
||||
|
||||
var generateDoc = function () {
|
||||
var ret = {};
|
||||
ret.fromProcess = processId;
|
||||
_.times(PARAMS.documentNumFields, function (n) {
|
||||
ret['Field' + n] = randomString(PARAMS.documentSize/PARAMS.documentNumFields);
|
||||
});
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
||||
//////////////////////////////
|
||||
// Data
|
||||
//////////////////////////////
|
||||
|
||||
|
||||
var Collections = [];
|
||||
_.times(PARAMS.numCollections, function (n) {
|
||||
Collections.push(new Mongo.Collection("Collection" + n));
|
||||
});
|
||||
|
||||
|
||||
if (Meteor.isServer) {
|
||||
|
||||
// Make sure we have indexes. Helps mongo CPU usage.
|
||||
Meteor.startup(function () {
|
||||
_.each(Collections, function (C) {
|
||||
C._ensureIndex({toProcess: 1});
|
||||
C._ensureIndex({fromProcess: 1});
|
||||
C._ensureIndex({when: 1});
|
||||
});
|
||||
});
|
||||
|
||||
// periodic db check. generate a client list.
|
||||
var currentClients = [];
|
||||
var totalDocs = 0;
|
||||
Meteor.setInterval(function () {
|
||||
var newClients = {};
|
||||
var newTotal = 0;
|
||||
// XXX hardcoded time
|
||||
var since = +(new Date) - 1000*PARAMS.insertsPerSecond * 5;
|
||||
_.each(Collections, function (C) {
|
||||
_.each(C.find({when: {$gt: since}}, {fields: {fromProcess: 1, when: 1}}).fetch(), function (d) {
|
||||
newTotal += 1;
|
||||
if (d.fromProcess && d.when > since)
|
||||
newClients[d.fromProcess] = true;
|
||||
});
|
||||
});
|
||||
currentClients = _.keys(newClients);
|
||||
totalDocs = newTotal;
|
||||
}, 3*1000); // XXX hardcoded time
|
||||
|
||||
// periodic document cleanup.
|
||||
if (PARAMS.maxAgeSeconds) {
|
||||
Meteor.setInterval(function () {
|
||||
var when = +(new Date) - PARAMS.maxAgeSeconds*1000;
|
||||
_.each(Collections, function (C) {
|
||||
preCall('removeMaxAge');
|
||||
C.remove({when: {$lt: when}}, postCall('removeMaxAge'));
|
||||
});
|
||||
// Clear out 5% of the DB each time, steady state. XXX parameterize?
|
||||
}, 1000*PARAMS.maxAgeSeconds / 20);
|
||||
}
|
||||
|
||||
Meteor.publish("data", function (collection, process) {
|
||||
check(collection, Number);
|
||||
check(process, String);
|
||||
var C = Collections[collection];
|
||||
return C.find({toProcess: process});
|
||||
});
|
||||
|
||||
Meteor.methods({
|
||||
'insert': function (doc) {
|
||||
check(doc, Object);
|
||||
check(doc.fromProcess, String);
|
||||
// pick a random destination. send to ourselves if there is no one
|
||||
// else. by having an entry in the db, we'll end up in the target
|
||||
// list.
|
||||
doc.toProcess = Random.choice(currentClients) || doc.fromProcess;
|
||||
|
||||
doc.when = +(new Date);
|
||||
|
||||
var C = pickCollection();
|
||||
preCall('insert');
|
||||
C.insert(doc, postCall('insert'));
|
||||
},
|
||||
update: function (processId, field, value) {
|
||||
check([processId, field, value], [String]);
|
||||
var modifer = {};
|
||||
modifer[field] = value; // XXX injection attack?
|
||||
|
||||
var C = pickCollection();
|
||||
// update one message.
|
||||
preCall('update');
|
||||
C.update({fromProcess: processId}, {$set: modifer}, {multi: false}, postCall('update'));
|
||||
},
|
||||
remove: function (processId) {
|
||||
check(processId, String);
|
||||
var C = pickCollection();
|
||||
// remove one message.
|
||||
var obj = C.findOne({fromProcess: processId});
|
||||
if (obj) {
|
||||
preCall('remove');
|
||||
C.remove(obj._id, postCall('remove'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// XXX publish stats
|
||||
// - currentClients.length
|
||||
// - serverId
|
||||
// - num ddp sessions
|
||||
// - total documents
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (Meteor.isClient) {
|
||||
// sub to data
|
||||
_.times(PARAMS.numCollections, function (n) {
|
||||
Meteor.subscribe("data", n, processId);
|
||||
});
|
||||
|
||||
// templates
|
||||
Template.params.params = function () {
|
||||
return _.map(PARAMS, function (v, k) {
|
||||
return {key: k, value: v};
|
||||
});
|
||||
};
|
||||
|
||||
Template.status.status = function () {
|
||||
return Meteor.status().status;
|
||||
};
|
||||
|
||||
Template.status.updateRate = function () {
|
||||
return (Session.get('updateAvgs') || []).join(", ");
|
||||
};
|
||||
|
||||
// XXX count of how many docs are in local collection?
|
||||
|
||||
|
||||
// do stuff periodically
|
||||
|
||||
if (PARAMS.insertsPerSecond) {
|
||||
Meteor.setInterval(function () {
|
||||
Meteor.call('insert', generateDoc());
|
||||
}, 1000 / PARAMS.insertsPerSecond);
|
||||
}
|
||||
|
||||
if (PARAMS.updatesPerSecond) {
|
||||
Meteor.setInterval(function () {
|
||||
Meteor.call('update',
|
||||
processId,
|
||||
'Field' + random(PARAMS.documentNumFields),
|
||||
randomString(PARAMS.documentSize/PARAMS.documentNumFields)
|
||||
);
|
||||
}, 1000 / PARAMS.updatesPerSecond);
|
||||
}
|
||||
|
||||
if (PARAMS.removesPerSecond) {
|
||||
Meteor.setInterval(function () {
|
||||
Meteor.call('remove', processId);
|
||||
}, 1000 / PARAMS.removesPerSecond);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// XXX very rough per client update rate. we need to measure this
|
||||
// better. ideally, on the server we could get the global update rate
|
||||
var updateCount = 0;
|
||||
var updateHistories = {1: [], 10: [], 100: [], 1000: []};
|
||||
var updateFunc = function () { updateCount += 1; };
|
||||
_.each(Collections, function (C) {
|
||||
C.find({}).observeChanges({
|
||||
added: updateFunc, changed: updateFunc, removed: updateFunc
|
||||
});
|
||||
});
|
||||
Meteor.setInterval(function () {
|
||||
_.each(updateHistories, function (h, max) {
|
||||
h.push(updateCount);
|
||||
if (h.length > max)
|
||||
h.shift();
|
||||
});
|
||||
Session.set('updateAvgs', _.map(updateHistories, function (h) {
|
||||
return _.reduce(h, function(memo, num) {
|
||||
return memo + num;
|
||||
}, 0) / h.length;
|
||||
}));;
|
||||
updateCount = 0;
|
||||
}, 1000);
|
||||
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
PORT=9000
|
||||
if [ -z "$NUM_CLIENTS" ]; then
|
||||
NUM_CLIENTS=10
|
||||
fi
|
||||
if [ -z "$DURATION" ]; then
|
||||
DURATION=120
|
||||
fi
|
||||
REPORT_INTERVAL=10
|
||||
|
||||
set -e
|
||||
trap 'echo "FAILED. Killing: $(jobs -pr)" ; for pid in "$(jobs -pr)"; do kill $pid ; done' EXIT
|
||||
|
||||
PROJDIR=`dirname $0`
|
||||
cd "$PROJDIR"
|
||||
PROJDIR=`pwd`
|
||||
|
||||
SCENARIO="${1:-default}"
|
||||
|
||||
# clean up from previous runs
|
||||
# XXX this is gross!
|
||||
pkill -f "$PROJDIR/.meteor/local/db" || true
|
||||
../../../meteor reset || true
|
||||
|
||||
# start the benchmark app
|
||||
../../../meteor --production --settings "scenarios/${SCENARIO}.json" --port ${PORT} &
|
||||
OUTER_PID=$!
|
||||
|
||||
echo "Waiting for server to come up"
|
||||
function wait_for_port {
|
||||
local N=0
|
||||
while ! curl -v "$1" 2>&1 | grep ' 200 ' > /dev/null ; do
|
||||
sleep 1
|
||||
N=$(($N+1))
|
||||
if [ $N -ge $2 ] ; then
|
||||
curl -v "$1" || true
|
||||
echo "Timed out waiting for port $1"
|
||||
exit 2
|
||||
fi
|
||||
done
|
||||
}
|
||||
wait_for_port "http://localhost:${PORT}" 60
|
||||
|
||||
|
||||
echo "Starting phantoms"
|
||||
# start a bunch of phantomjs processes
|
||||
PHANTOMSCRIPT=`mktemp -t benchmark-XXXXXXXX`
|
||||
cat > "$PHANTOMSCRIPT" <<EOF
|
||||
var page = require('webpage').create();
|
||||
var url = 'http://localhost:$PORT';
|
||||
page.open(url);
|
||||
EOF
|
||||
for ((i = 0 ; i < $NUM_CLIENTS ; i++)) ; do
|
||||
# sleep between each phantom start both to provide a smoother ramp
|
||||
# to the benchmark and because otherwise their PRNGs get set to the
|
||||
# same seed and you get duplicate key errors!
|
||||
sleep 2
|
||||
phantomjs "$PHANTOMSCRIPT" &
|
||||
done
|
||||
|
||||
ps -o cputime,ppid,args | grep " $OUTER_PID " | grep main.js || true
|
||||
for ((i = 0 ; i < $DURATION/$REPORT_INTERVAL ; i++)) ; do
|
||||
sleep $REPORT_INTERVAL
|
||||
ps -o cputime,ppid,args | grep " $OUTER_PID " | grep main.js || true
|
||||
done
|
||||
|
||||
# print totals of all processes (outer, mongo, inner)
|
||||
echo
|
||||
echo TOTALS
|
||||
ps -o cputime,pid,ppid,args | grep " $OUTER_PID " | grep -v grep || true
|
||||
|
||||
|
||||
# cleanup
|
||||
trap - EXIT
|
||||
for pid in "$(jobs -pr)"; do
|
||||
# not sure why we need both, but it seems to help clean up rogue
|
||||
# mongo and phantomjs processes.
|
||||
kill -INT $pid
|
||||
kill $pid
|
||||
done
|
||||
rm "$PHANTOMSCRIPT"
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
Parameters for simulation:
|
||||
|
||||
- numCollections
|
||||
how many collections to spread the documents over
|
||||
|
||||
- maxAgeSeconds: How long to leave documents in the database. This,
|
||||
combined with all the various rates, determines the steady state
|
||||
database size. In seconds. falsy to disable.
|
||||
|
||||
Per-client action rates:
|
||||
- insertsPerSecond
|
||||
- updatesPerSecond
|
||||
- removesPerSecond
|
||||
|
||||
- documentSize: bytes of randomness per document.
|
||||
// XXX make this a random distribution?
|
||||
- documentNumFields: how many fields of randomness per document.
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"params": {
|
||||
"numCollections": 1,
|
||||
"maxAgeSeconds": 60,
|
||||
"insertsPerSecond": 1,
|
||||
"updatesPerSecond": 1,
|
||||
"removesPerSecond": 0.1,
|
||||
"documentSize": 1024,
|
||||
"documentNumFields": 8
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"params": {
|
||||
"numCollections": 1,
|
||||
"maxAgeSeconds": 60,
|
||||
"insertsPerSecond": 5,
|
||||
"updatesPerSecond": 5,
|
||||
"removesPerSecond": 1,
|
||||
"documentSize": 128,
|
||||
"documentNumFields": 2
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"params": {
|
||||
"numCollections": 1
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user