Merge pull request #11223 from StorytellerCZ/remove-examples

Remove examples directory
This commit is contained in:
Filipe Névola
2020-10-28 18:15:51 -04:00
committed by GitHub
190 changed files with 0 additions and 193415 deletions

17
examples/.gitignore vendored
View File

@@ -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

View File

@@ -1 +0,0 @@
0.6.0

View File

@@ -1 +0,0 @@
local

View File

@@ -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

View File

@@ -1 +0,0 @@
0.7.0.1

View File

@@ -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>

View File

@@ -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});
};
}

View File

@@ -1 +0,0 @@
0.6.0

View File

@@ -1 +0,0 @@
local

View File

@@ -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

View File

@@ -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`.

View File

@@ -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>

View File

@@ -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', '!');
});
}
});
}
}

View File

@@ -1 +0,0 @@
local

View File

@@ -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

View File

@@ -1 +0,0 @@
none

View File

@@ -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; }

View File

@@ -1,7 +0,0 @@
<head>
<title>domrange-grid</title>
</head>
<body>
</body>

View File

@@ -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;
}
}
}
});
});
}

View File

@@ -1 +0,0 @@
local

View File

@@ -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

View File

@@ -1 +0,0 @@
none

View File

@@ -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;
}

View File

@@ -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 &mdash; when <code>&#123;{loggingIn}}</code> is true</li>
<li>Logged in &mdash; when there is a <code>&#123;{currentUser}}</code></li>
<li>Logged out &mdash; 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>&#123;{> 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>

View File

@@ -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}});
});
}

View File

@@ -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

View File

@@ -1 +0,0 @@
local

View File

@@ -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

View File

@@ -1,2 +0,0 @@
server
browser

View File

@@ -1 +0,0 @@
METEOR@0.9.4

View File

@@ -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

View File

@@ -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);
}
});

View File

@@ -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;
}

View File

@@ -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">&copy;
<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">&times;</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 &mdash; 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">&times;</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>

View File

@@ -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

View File

@@ -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}]});
});

View File

@@ -1 +0,0 @@
local

View File

@@ -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

View File

@@ -1 +0,0 @@
0.6.0

View File

@@ -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>

View File

@@ -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}});
}
}
});

View File

@@ -1 +0,0 @@
local

View File

@@ -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

View File

@@ -1 +0,0 @@
0.6.0

File diff suppressed because it is too large Load Diff

View File

@@ -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;
}

View File

@@ -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&amp;source=s_q&amp;hl=en&amp;geocode=&amp;q=140+10th+Street,+San+Francisco,+CA&amp;aq=0&amp;oq=140+10th+s&amp;sll=37.7577,-122.4376&amp;sspn=0.166931,0.329247&amp;ie=UTF8&amp;hq=&amp;hnear=140+10th+St,+San+Francisco,+California+94103&amp;t=m&amp;ll=37.774921,-122.415419&amp;spn=0.013569,0.017252&amp;z=14&amp;iwloc=A&amp;output=embed"></iframe><br /><small><a href="https://maps.google.com/maps?f=q&amp;source=embed&amp;hl=en&amp;geocode=&amp;q=140+10th+Street,+San+Francisco,+CA&amp;aq=0&amp;oq=140+10th+s&amp;sll=37.7577,-122.4376&amp;sspn=0.166931,0.329247&amp;ie=UTF8&amp;hq=&amp;hnear=140+10th+St,+San+Francisco,+California+94103&amp;t=m&amp;ll=37.774921,-122.415419&amp;spn=0.013569,0.017252&amp;z=14&amp;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&amp;source=s_q&amp;hl=en&amp;geocode=&amp;q=880+Harrison+Street,+San+Francisco,+CA&amp;aq=0&amp;oq=880+harrison&amp;sll=37.7577,-122.4376&amp;sspn=0.166931,0.329247&amp;ie=UTF8&amp;hq=&amp;hnear=880+Harrison+St,+San+Francisco,+California+94107&amp;t=m&amp;ll=37.779534,-122.411213&amp;spn=0.013568,0.01708&amp;z=14&amp;iwloc=A&amp;output=embed"></iframe><br /><small><a href="https://maps.google.com/maps?f=q&amp;source=embed&amp;hl=en&amp;geocode=&amp;q=880+Harrison+Street,+San+Francisco,+CA&amp;aq=0&amp;oq=880+harrison&amp;sll=37.7577,-122.4376&amp;sspn=0.166931,0.329247&amp;ie=UTF8&amp;hq=&amp;hnear=880+Harrison+St,+San+Francisco,+California+94107&amp;t=m&amp;ll=37.779534,-122.411213&amp;spn=0.013568,0.01708&amp;z=14&amp;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 &lt;span&gt; element every
second. <em>rendered</em> is used to find the &lt;span&gt; when it
appears on the screen, and update the pointer when the
&lt;span&gt; is redraw (say, when you press Y++ &mdash; 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 &lt;span&gt; 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>

View File

@@ -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();
});

View File

@@ -1 +0,0 @@
Circles = new Mongo.Collection("circles");

View File

@@ -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

View File

@@ -1 +0,0 @@
local

View File

@@ -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

View File

@@ -1,2 +0,0 @@
server
browser

View File

@@ -1 +0,0 @@
METEOR@0.9.4

View File

@@ -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

View File

@@ -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

View File

@@ -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';
}

View File

@@ -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>

View File

@@ -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);
});

View File

@@ -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

View File

@@ -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);

View File

@@ -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');
};

View File

@@ -1 +0,0 @@
node_modules

View File

@@ -1 +0,0 @@
local

View File

@@ -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

View File

@@ -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

View File

@@ -1,2 +0,0 @@
browser
server

View File

@@ -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>

View File

@@ -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
);
});
}
}
});
}

View File

@@ -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
}

View File

@@ -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=="
}
}
}

View File

@@ -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"
}
}

View File

@@ -1 +0,0 @@
local

View File

@@ -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

View File

@@ -1 +0,0 @@
none

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -1,5 +0,0 @@
if (Meteor.isClient) {
Template.atom.textY = function () {
return this.y + 8;
};
}

View File

@@ -1 +0,0 @@
local

View File

@@ -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

View File

@@ -1 +0,0 @@
0.6.0

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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('');
}
}
};

View File

@@ -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});
});
}

View File

@@ -1 +0,0 @@
local

View File

@@ -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

View File

@@ -1 +0,0 @@
0.6.5.1

View File

@@ -1 +0,0 @@
/* CSS declarations go here */

View File

@@ -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>

View File

@@ -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);
}

View File

@@ -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"

View File

@@ -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.

View File

@@ -1,11 +0,0 @@
{
"params": {
"numCollections": 1,
"maxAgeSeconds": 60,
"insertsPerSecond": 1,
"updatesPerSecond": 1,
"removesPerSecond": 0.1,
"documentSize": 1024,
"documentNumFields": 8
}
}

View File

@@ -1,11 +0,0 @@
{
"params": {
"numCollections": 1,
"maxAgeSeconds": 60,
"insertsPerSecond": 5,
"updatesPerSecond": 5,
"removesPerSecond": 1,
"documentSize": 128,
"documentNumFields": 2
}
}

View File

@@ -1,5 +0,0 @@
{
"params": {
"numCollections": 1
}
}

Some files were not shown because too many files have changed in this diff Show More