From 78d50d9a6d9618be281aa085b9635dc3e6a3a937 Mon Sep 17 00:00:00 2001 From: didier amyot Date: Sun, 5 Jul 2015 17:17:01 -0400 Subject: [PATCH] so mamy things... --- api/alldoormen_test.go | 4 +- api/api.go | 8 + api/createdoorman_test.go | 4 +- api/doorman.go | 21 +++ doormen/doormen.go | 36 ++-- doormen/doormen_test.go | 42 ++--- doormen/encoding.go | 26 +++ doormen/encoding_test.go | 38 +++++ .../inmemorydbconnectior_test.go | 2 +- static/.bowerrc | 3 + static/bower.json | 3 +- static/coffee/app.coffee | 22 ++- static/coffee/controllers/apispecCtrl.coffee | 8 +- static/coffee/controllers/doormanCtrl.coffee | 14 +- static/coffee/controllers/homeCtrl.coffee | 12 +- .../coffee/controllers/newdoormanCtrl.coffee | 34 ++-- static/coffee/directives/ratinput.coffee | 64 ++++++++ static/coffee/services/rationals.coffee | 80 +++++++++ static/gulpfile.js | 2 +- static/index.html | 58 ++++--- static/partials/doorman.html | 14 +- static/partials/home.html | 4 +- static/partials/newdoorman.html | 155 +++++++++--------- static/scss/_doorman.scss | 35 ++++ 24 files changed, 516 insertions(+), 173 deletions(-) create mode 100644 doormen/encoding.go create mode 100644 doormen/encoding_test.go create mode 100644 static/.bowerrc create mode 100644 static/coffee/directives/ratinput.coffee create mode 100644 static/coffee/services/rationals.coffee diff --git a/api/alldoormen_test.go b/api/alldoormen_test.go index 953c3ec..aeae2b5 100644 --- a/api/alldoormen_test.go +++ b/api/alldoormen_test.go @@ -16,8 +16,8 @@ func init() { func TestGetAllDoormen(t *testing.T) { conn = inmemoryconnector.NewInMemoryDBConnector() - conn.Save(doormen.QuickNewDoormanDefinition("foo", 0.25, 0.75)) - conn.Save(doormen.QuickNewDoormanDefinition("bar", 0.5, 0.5)) + conn.Save(doormen.QuickNewDoormanDefinition("foo", "0.25", "0.75")) + conn.Save(doormen.QuickNewDoormanDefinition("bar", "0.5", "0.5")) req, err := http.NewRequest("GET", "http://bigtits.com/api/doormen", nil) if err != nil { diff --git a/api/api.go b/api/api.go index c4638c2..37123bd 100644 --- a/api/api.go +++ b/api/api.go @@ -44,11 +44,19 @@ func Create(root string) http.Handler { UpdateDoorman, } + getDoormanStatus := &simpleapi.Endpoint{ + "/doormen/{id}/status", + `Returns the status of the doorman as a doorman client expect it.`, + "GET", + GetDoormanStatus, + } + api := simpleapi.New( root, serverSpecification, createDoorman, allDoorman, + getDoormanStatus, doorman, updateDoorman, ) diff --git a/api/createdoorman_test.go b/api/createdoorman_test.go index afb620f..00b29e4 100644 --- a/api/createdoorman_test.go +++ b/api/createdoorman_test.go @@ -18,7 +18,7 @@ func TestCreateDoorman(t *testing.T) { conn = inmemoryconnector.NewInMemoryDBConnector() payload := ` {"name":"name", - "values":[{"name":"red","probability":0.2},{"name":"blue","probability":0.8}], + "values":[{"name":"red","probability":"1/5"},{"name":"blue","probability": "4/5"}], "emails": ["alice"] }` req, err := http.NewRequest( @@ -34,7 +34,7 @@ func TestCreateDoorman(t *testing.T) { w := httptest.NewRecorder() CreateDoorman(w, req) if w.Code != 201 { - t.Error("bad status code", w.Code) + t.Error("bad status code", w.Code, "\n", w.Body) } else if location := w.Header().Get("location"); location == "" { t.Error("missing location header", location) } else if dor, _ := conn.GetDoorman(GetIdFromLocation(location)); dor == nil { diff --git a/api/doorman.go b/api/doorman.go index 26e9eca..3cbfb30 100644 --- a/api/doorman.go +++ b/api/doorman.go @@ -21,3 +21,24 @@ func GetDoorman(w http.ResponseWriter, r *http.Request) { encoder.Encode(doorman) } } + +func GetDoormanStatus(w http.ResponseWriter, r *http.Request) { + id := getDoormanIdFromRequest(w, r) + if id == "" { + Write404Error(w) + return + } + doorman, err := conn.GetDoorman(id) + if err != nil { + Write500Error(w, err) + } else if doorman == nil { + Write404Error(w) + } + + if payload, err := doorman.AsDoormanUpdatePayload(); err != nil { + Write500Error(w, err) + } else { + w.Write(payload) + } + +} diff --git a/doormen/doormen.go b/doormen/doormen.go index c1c2450..e0c8ddc 100644 --- a/doormen/doormen.go +++ b/doormen/doormen.go @@ -4,18 +4,22 @@ import ( "encoding/json" "errors" "fmt" + "gopkg.in/mgo.v2/bson" + "math/big" "strconv" "time" "github.com/didiercrunch/doorman/shared" - "gopkg.in/mgo.v2/bson" ) -const epsilon float64 = 0.0001 - type DoormanValue struct { - Name string `json:"name" bson:"name"` - Probability float64 `json:"probability" bson:"probability"` + Name string `json:"name"` + Probability *big.Rat `json:"probability"` +} + +type doormanValue struct { + Name string + Probability string } type DoormanId struct { @@ -43,10 +47,15 @@ func NewDoormanDefinition(name string, ownerEmails ...string) *DoormanDefinition // a way to quickly create doormen in tests. do not use in real code. will // panic if a doorman is wrongly specify -func QuickNewDoormanDefinition(name string, probs ...float64) *DoormanDefinition { +func QuickNewDoormanDefinition(name string, probs ...string) *DoormanDefinition { ret := NewDoormanDefinition(name, "natasha@bigtits.com") for i, prob := range probs { - ret.Values = append(ret.Values, &DoormanValue{"T" + strconv.Itoa(i), prob}) + r := new(big.Rat) + _, err := fmt.Sscan(prob, r) + if err != nil { + panic(err) + } + ret.Values = append(ret.Values, &DoormanValue{"T" + strconv.Itoa(i), r}) } if err := ret.Validate("natasha@bigtits.com"); err != nil { panic(err) @@ -79,18 +88,19 @@ func (dmd *DoormanDefinition) AsWriteAccess(email string) bool { } func (dmd *DoormanDefinition) ValidateDoormanValueProbabilities() error { - var sum float64 = 0 + sum, zero, one := big.NewRat(0, 1), big.NewRat(0, 1), big.NewRat(1, 1) for _, w := range dmd.Values { - if p := w.Probability; p < 0-epsilon || p > 1+epsilon { + if p := w.Probability; p.Cmp(zero) < 0 || p.Cmp(one) > 0 { return errors.New(fmt.Sprintf("doorman value %v is out of range", p)) } else { - sum += p + sum = new(big.Rat).Add(sum, p) } } - if sum < 1-epsilon || sum > 1+epsilon { - return errors.New("the sum of the probability must be 1.0") + if sum.Cmp(one) != 0 { + return errors.New("the sum of the probability must be 1") } + return nil } @@ -125,7 +135,7 @@ func (dmd *DoormanDefinition) CanBeUpdatedBy(dmd2 *DoormanDefinition) error { func (dmd *DoormanDefinition) AsDoormanUpdatePayload() ([]byte, error) { wu := &shared.DoormanUpdater{Id: dmd.Id.Hex()} wu.Timestamp = time.Now().Unix() - wu.Probabilities = make([]float64, len(dmd.Values)) + wu.Probabilities = make([]*big.Rat, len(dmd.Values)) for i, value := range dmd.Values { wu.Probabilities[i] = value.Probability } diff --git a/doormen/doormen_test.go b/doormen/doormen_test.go index 8405541..93b62a2 100644 --- a/doormen/doormen_test.go +++ b/doormen/doormen_test.go @@ -2,13 +2,17 @@ package doormen import ( "encoding/json" - "math" + "math/big" "testing" "github.com/didiercrunch/doorman/shared" "gopkg.in/mgo.v2/bson" ) +var zero = big.NewRat(0, 1) + +const epsilon float64 = 0.0001 + func TestNewDoormanDefinition(t *testing.T) { dmd := NewDoormanDefinition("some_name") if dmd.Id == "" { @@ -33,40 +37,40 @@ func TestValidate(t *testing.T) { t.Error(err) } - dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", 0}, &DoormanValue{"foo", 0}}} + dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", zero}, &DoormanValue{"foo", zero}}} if err := dmd.Validate("bob"); err.Error() != "doorman value names must be unique within a doorman" { t.Error(err) } - dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", 0}, &DoormanValue{"", 0}}} + dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", zero}, &DoormanValue{"", zero}}} if err := dmd.Validate("bob"); err.Error() != "doorman value cannot be empty" { t.Error(err) } - dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", -0.25}, &DoormanValue{"bar", 0.5}}} - if err := dmd.Validate("bob"); err.Error() != "doorman value -0.25 is out of range" { + dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", big.NewRat(-1, 4)}, &DoormanValue{"bar", big.NewRat(1, 2)}}} + if err := dmd.Validate("bob"); err.Error() != "doorman value -1/4 is out of range" { t.Error(err) } - dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", 2.5}, &DoormanValue{"bar", 0.5}}} - if err := dmd.Validate("bob"); err.Error() != "doorman value 2.5 is out of range" { + dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", big.NewRat(5, 2)}, &DoormanValue{"bar", big.NewRat(1, 2)}}} + if err := dmd.Validate("bob"); err.Error() != "doorman value 5/2 is out of range" { t.Error(err) } - dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", 0.75}, &DoormanValue{"bar", 0.5}}} - if err := dmd.Validate("bob"); err.Error() != "the sum of the probability must be 1.0" { + dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", big.NewRat(3, 4)}, &DoormanValue{"bar", big.NewRat(1, 2)}}} + if err := dmd.Validate("bob"); err.Error() != "the sum of the probability must be 1" { t.Error(err) } - dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", 0.2}, &DoormanValue{"bar", 0.2}}} - if err := dmd.Validate("bob"); err.Error() != "the sum of the probability must be 1.0" { + dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", big.NewRat(1, 5)}, &DoormanValue{"bar", big.NewRat(1, 5)}}} + if err := dmd.Validate("bob"); err.Error() != "the sum of the probability must be 1" { t.Error(err) } - dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", 0.8}, &DoormanValue{"bar", 0.2}}} + dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", big.NewRat(4, 5)}, &DoormanValue{"bar", big.NewRat(1, 5)}}} if err := dmd.Validate("bob"); err.Error() != "A doorman needs at least one owner specify as 'email'." { t.Error(err) } - dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", 0.8}, &DoormanValue{"bar", 0.2}}, OwnerEmails: []string{"alice"}} + dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", big.NewRat(4, 5)}, &DoormanValue{"bar", big.NewRat(1, 5)}}, OwnerEmails: []string{"alice"}} if err := dmd.Validate("bob"); err.Error() != "'bob' is not allow to edit this doorman." { t.Error(err) } @@ -91,7 +95,7 @@ func TestAsJson(t *testing.T) { func TestAsDoormanUpdatePayload(t *testing.T) { wd := &DoormanDefinition{} wd.Id = bson.NewObjectId() - wd.Values = []*DoormanValue{&DoormanValue{"C", 0.25}, &DoormanValue{"T1", 0.75}} + wd.Values = []*DoormanValue{&DoormanValue{"C", big.NewRat(1, 4)}, &DoormanValue{"T1", big.NewRat(3, 4)}} data, err := wd.AsDoormanUpdatePayload() if err != nil { t.Error(err) @@ -106,9 +110,9 @@ func TestAsDoormanUpdatePayload(t *testing.T) { } if len(wu.Probabilities) != 2 { t.Error("bad probability length") - } else if math.Abs(wu.Probabilities[0]-0.25) > epsilon { + } else if wu.Probabilities[0].Cmp(big.NewRat(1, 4)) != 0 { t.Error("bad first probability") - } else if math.Abs(wu.Probabilities[1]-0.75) > epsilon { + } else if wu.Probabilities[1].Cmp(big.NewRat(3, 4)) != 0 { t.Error("bad second probability") } else if wu.Timestamp == 0 { t.Error("no time specify") @@ -118,7 +122,7 @@ func TestAsDoormanUpdatePayload(t *testing.T) { } func TestQuickNewDoormanDefinition(t *testing.T) { - dmd := QuickNewDoormanDefinition("foo", 0.75, 0.25) + dmd := QuickNewDoormanDefinition("foo", "0.75", "0.25") if dmd.Name != "foo" { t.Error() } @@ -127,11 +131,11 @@ func TestQuickNewDoormanDefinition(t *testing.T) { t.Error() } - if dmd.Values[0].Name != "T0" || dmd.Values[0].Probability != 0.75 { + if dmd.Values[0].Name != "T0" || dmd.Values[0].Probability.Cmp(big.NewRat(3, 4)) != 0 { t.Error() } - if dmd.Values[1].Name != "T1" || dmd.Values[1].Probability != 0.25 { + if dmd.Values[1].Name != "T1" || dmd.Values[1].Probability.Cmp(big.NewRat(1, 4)) != 0 { t.Error() } if len(dmd.OwnerEmails) != 1 && dmd.OwnerEmails[0] != "natasha@bigtits.com" { diff --git a/doormen/encoding.go b/doormen/encoding.go new file mode 100644 index 0000000..ede7879 --- /dev/null +++ b/doormen/encoding.go @@ -0,0 +1,26 @@ +package doormen + +import ( + "gopkg.in/mgo.v2/bson" + "math/big" +) + +func (val *DoormanValue) GetBSON() (interface{}, error) { + ret := &doormanValue{val.Name, ""} + txt, err := val.Probability.MarshalText() + if err != nil { + return nil, err + } + ret.Probability = string(txt) + return ret, nil +} + +func (val *DoormanValue) SetBSON(raw bson.Raw) error { + v := new(doormanValue) + if err := raw.Unmarshal(v); err != nil { + return err + } + val.Name = v.Name + val.Probability = new(big.Rat) + return val.Probability.UnmarshalText([]byte(v.Probability)) +} diff --git a/doormen/encoding_test.go b/doormen/encoding_test.go new file mode 100644 index 0000000..3f5b68b --- /dev/null +++ b/doormen/encoding_test.go @@ -0,0 +1,38 @@ +package doormen + +import ( + "encoding/json" + "gopkg.in/mgo.v2/bson" + "reflect" + "testing" +) + +func TestJsonEncoding(t *testing.T) { + dmd := QuickNewDoormanDefinition("test json", "1/4", "3/4") + data, err := json.Marshal(dmd) + if err != nil { + t.Error(err) + } + dmd2 := new(DoormanDefinition) + if err = json.Unmarshal(data, dmd2); err != nil { + t.Error(err) + } + if !reflect.DeepEqual(dmd2, dmd) { + t.Error() + } +} + +func TestBsonEncoding(t *testing.T) { + dmd := QuickNewDoormanDefinition("test bson", "1/4", "3/4") + data, err := bson.Marshal(dmd) + if err != nil { + t.Error(err) + } + dmd2 := new(DoormanDefinition) + if err = bson.Unmarshal(data, dmd2); err != nil { + t.Error(err) + } + if !reflect.DeepEqual(dmd2, dmd) { + t.Error() + } +} diff --git a/inmemoryconnector/inmemorydbconnectior_test.go b/inmemoryconnector/inmemorydbconnectior_test.go index 3a2dab4..7e3c485 100644 --- a/inmemoryconnector/inmemorydbconnectior_test.go +++ b/inmemoryconnector/inmemorydbconnectior_test.go @@ -8,7 +8,7 @@ import ( ) func createMockDoorman(name string) *doormen.DoormanDefinition { - return doormen.QuickNewDoormanDefinition(name, 0.5, 0.5) + return doormen.QuickNewDoormanDefinition(name, "0.5", "0.5") } func TestNewInMemoryDBConnector(t *testing.T) { diff --git a/static/.bowerrc b/static/.bowerrc new file mode 100644 index 0000000..5773025 --- /dev/null +++ b/static/.bowerrc @@ -0,0 +1,3 @@ +{ + "directory": "app/bower_components" +} diff --git a/static/bower.json b/static/bower.json index 2cf305d..f931f32 100644 --- a/static/bower.json +++ b/static/bower.json @@ -22,6 +22,7 @@ "angularAMD": "~0.2.1", "angular-foundation": "~0.5.1", "lodash": "~3.9.3", - "font-awesome": "~4.3.0" + "font-awesome": "~4.3.0", + "bourbon": "~4.2.3" } } diff --git a/static/coffee/app.coffee b/static/coffee/app.coffee index bc8a295..4a397dd 100644 --- a/static/coffee/app.coffee +++ b/static/coffee/app.coffee @@ -1,9 +1,11 @@ define [ "angularAMD" + "lodash" "angular-route" "foundation" -], (angularAMD) -> + +], (angularAMD, _) -> app = angular.module("webapp", ["ngRoute", "mm.foundation"]) app.config ($routeProvider) -> $routeProvider.when("/", angularAMD.route( @@ -39,6 +41,24 @@ define [ document.getElementsByTagName("body")[0].style.visibility = "" return + app.controller "topbarCtrl", ["$scope", "$rootScope", "$location", ($scope, $rootScope, $location) -> + + $scope.init = () -> + $scope.email = localStorage.getItem("email") + $scope.loggedIn = !!$scope.email + if !$scope.loggedIn + $location.path('/').replace() + + $scope.logout = () -> + localStorage.setItem("email", "") + $scope.email = "" + $scope.loggedIn = false + $scope.$emit("logout") + + $scope.init() + $rootScope.$on('login', $scope.init) + return + ] angularAMD.bootstrap(app) diff --git a/static/coffee/controllers/apispecCtrl.coffee b/static/coffee/controllers/apispecCtrl.coffee index 5e2cf97..6c7dac6 100644 --- a/static/coffee/controllers/apispecCtrl.coffee +++ b/static/coffee/controllers/apispecCtrl.coffee @@ -25,8 +25,8 @@ define ["app", "angularAMD"], (app, angularAMD) -> angularAMD.directive "wabApi", () -> directive = - restrict: 'E', - scope: - endpoint: '@' - template: '/api{{ endpoint }}' + restrict: 'E', + scope: + endpoint: '@' + template: '/api{{ endpoint }}' return directive diff --git a/static/coffee/controllers/doormanCtrl.coffee b/static/coffee/controllers/doormanCtrl.coffee index 0b65579..b4472e8 100644 --- a/static/coffee/controllers/doormanCtrl.coffee +++ b/static/coffee/controllers/doormanCtrl.coffee @@ -1,6 +1,8 @@ -define ["app", "lodash"], (app, _) -> +define ["app", "lodash", "directives/ratinput"], (app, _, ratinput) -> + app.controller "DoormanCtrl", ["$scope", "$http","$routeParams","$location", ($scope, $http, $routeParams, $location) -> $scope.doorman = {values: []} + $scope.somevalue = "2/6" $scope.apiurl = "/api/doormen/#{$routeParams.id}" $scope.user = localStorage.getItem("email") @@ -20,10 +22,18 @@ define ["app", "lodash"], (app, _) -> $scope.initEmails = () -> [$scope.leftEmailColumns, $scope.rightEmailColumns] = _.chunk($scope.doorman.emails, 2) + $scope.rationalToFloat = (rat) -> + if _.contains(rat, "/") + num = Number(rat.substr(0, rat.indexOf("/"))) + denum = Number(rat.substr(rat.indexOf("/") + 1)) + return num / denum + Number(rat) + + $scope.validate = ()-> ret = 0 for val in $scope.doorman.values - ret += val.probability + ret += $scope.rationalToFloat(val.probability) return Math.abs(ret - 1.0) < 0.000001 $scope.addNewAuthorEmail = () -> diff --git a/static/coffee/controllers/homeCtrl.coffee b/static/coffee/controllers/homeCtrl.coffee index 6e77979..4115ff2 100644 --- a/static/coffee/controllers/homeCtrl.coffee +++ b/static/coffee/controllers/homeCtrl.coffee @@ -1,11 +1,15 @@ define ["app"], (app) -> - app.controller "HomeCtrl", ($scope) -> - $scope.message = "message from home ctrl" - $scope.loggedEmail = localStorage.getItem("email") + app.controller "HomeCtrl", ['$scope', '$rootScope', ($scope, $rootScope) -> + $scope.init = () -> + $scope.loggedEmail = localStorage.getItem("email") + $scope.login = () -> if $scope.email localStorage.setItem("email", $scope.email) $scope.loggedEmail = $scope.email $scope.email = "" + $scope.$emit("login") - return + $scope.init() + $rootScope.$on('logout', $scope.init) + ] diff --git a/static/coffee/controllers/newdoormanCtrl.coffee b/static/coffee/controllers/newdoormanCtrl.coffee index 291fb12..b0d4107 100644 --- a/static/coffee/controllers/newdoormanCtrl.coffee +++ b/static/coffee/controllers/newdoormanCtrl.coffee @@ -1,10 +1,10 @@ epsilon = 0.0001 -define ["app", "../filters"], (app, filters) -> - app.controller "NewDoormanCtrl", ["$scope", "$http", "$location", ($scope, $http, $location) -> +define ["app", "lodash", "../filters", "directives/ratinput"], (app, _, filters, ratinput) -> + app.controller "NewDoormanCtrl", ["$scope", "$http", "$location", "rationals", ($scope, $http, $location, rationals) -> $scope.error = "" $scope.user = localStorage.getItem("email") - $scope.value = {probability: 0, name: ""} + $scope.value = {probability: "1", name: ""} $scope.payload = name: "" values: [] @@ -17,27 +17,34 @@ define ["app", "../filters"], (app, filters) -> if !$scope.isValidValue() return $scope.payload.values.push($scope.value) - $scope.value = {name: "", probability: 0} + $scope.value = + name: "" + probability: $scope.getProbabilityRemaining() $scope.removeValue = (idx) -> $scope.payload.values.splice(idx, 1); $scope.getProbabilityRemaining = () -> - ret = 0 - for v in $scope.payload.values - ret += v.probability - return 1 - ret + sum = rationals.sum(_.pluck($scope.payload.values, "probability")...) + return rationals.minus("1", sum) + $scope.redirectToDoorman = (doormanLocation) -> $http.get(doormanLocation).then (res) -> id = res.data.id return $location.url("/doormen/#{id}") + $scope.getPayload = () -> + payload = _.cloneDeep($scope.payload) + for value in payload.values + value.probability = value.probability + return payload + $scope.createDoorman = () -> if not $scope.isDoormanValid() return params = {params: {user: $scope.user}} - $http.post("/api/doormen", $scope.payload, params). + $http.post("/api/doormen", $scope.getPayload(), params). then((res, s) -> $scope.redirectToDoorman(res.headers("location")) ).catch((res) -> @@ -49,9 +56,8 @@ define ["app", "../filters"], (app, filters) -> return false if $scope.payload.values.length < 2 return false - if Math.abs($scope.getProbabilityRemaining()) > epsilon - return false - return true + rationals.equal($scope.getProbabilityRemaining(), "0" ) + $scope.addEmail = () -> if not $scope.newEmail @@ -64,6 +70,10 @@ define ["app", "../filters"], (app, filters) -> return $scope.payload.emails.splice(idx, 1); + $scope.getSum = () -> + probs = _.pluck($scope.payload.values, "probability") + return rationals.pp(rationals.sum(probs...)) + return diff --git a/static/coffee/directives/ratinput.coffee b/static/coffee/directives/ratinput.coffee new file mode 100644 index 0000000..5a2edfe --- /dev/null +++ b/static/coffee/directives/ratinput.coffee @@ -0,0 +1,64 @@ +template = """ +
+
+
+ + ⁄ + + +
+
+
+
+
+ + ⁄ + + + +
+
+
+
+""" + + +define ["app", "angularAMD", "lodash", "services/rationals"], (app, angularAMD, _, rationals) -> + + angularAMD.directive "ratInput", () -> + directive = + restrict: 'E', + template: template + scope: + ngModel: "=" + ngDisabled: "=" + controller: [ '$scope', "rationals", ($scope, rationals) -> + + $scope.getNominator = ()-> + if _.contains($scope.ngModel, "/") + return Number($scope.ngModel.substr(0, $scope.ngModel.indexOf("/"))) + Number($scope.ngModel) + + $scope.getDenominator = () -> + if _.contains($scope.ngModel, "/") + return Number($scope.ngModel.substr($scope.ngModel.indexOf("/") + 1)) + 1 + + $scope.changeModel = ()-> + $scope.ngModel = rationals.newRat($scope.nominator, $scope.denominator) + + $scope.enableEdit = () -> + if not $scope.ngDisabled + $scope.edit = true + + $scope.init = () -> + $scope.nominator = rationals.getNominator($scope.ngModel) + $scope.denominator = $scope.getDenominator() + + $scope.$watch 'ngModel', () -> + $scope.init() + + $scope.init() + + ] + return directive diff --git a/static/coffee/services/rationals.coffee b/static/coffee/services/rationals.coffee new file mode 100644 index 0000000..d31e483 --- /dev/null +++ b/static/coffee/services/rationals.coffee @@ -0,0 +1,80 @@ +define ["app", "angularAMD", "lodash"], (app, angularAMD, _) -> + + app.service "rationals", () -> + service = + getNominator: (rat) -> + if _.contains(rat, "/") + return Number(rat.substr(0, rat.indexOf("/"))) + Number(rat) + + getDenominator: (rat) -> + if _.contains(rat, "/") + return Number(rat.substr(rat.indexOf("/") + 1)) + 1 + + getNomDenom: (rat) -> + return [service.getNominator(rat), service.getDenominator(rat)] + + gcd: (a, b) -> + if b == 0 + return a + return service.gcd(b, a % b) + + lcm: (a, b) -> + return a * b / service.gcd(a, b) + + changeDenominator: (rat, newDenom) -> + [nom, denom] = service.getNomDenom(rat) + return service.newRat( nom * newDenom / denom, newDenom) + + add: (rat1, rat2) -> + [nom, denom] = service.getNomDenom(rat2) + [nNom, nDenom] = service.getNomDenom(rat1) + gcd = service.gcd(nDenom, denom) + + rNom = nom*nDenom + nNom * denom + rDenom = denom * nDenom + return service.newRat(rNom/gcd , rDenom/gcd) + + minus: (rat1, rat2) -> + return service.add(rat1, service.opposite(rat2)) + + reduce: (rat) -> + [nom, denom] = service.getNomDenom(rat) + gcd = service.gcd(nom, denom) + return service.newRat(nom/gcd, denom/gcd) + + opposite: (rat) -> + [nom, denom] = service.getNomDenom(rat) + return service.newRat(-nom, denom) + + + sum: (rats...) -> + ret = "0/1" + for rat in rats + ret = service.add(ret, rat) + return service.reduce(ret) + + equal: (rat1, rat2) -> + [nom1, denom1] = service.getNomDenom(rat1) + [nom2, denom2] = service.getNomDenom(rat2) + return nom1 * denom2 == nom2 * denom1 + + pp: (x) -> + [num, denum] = service.getNomDenom(service.reduce(x)) + if denum == 1 + return num + return "#{num}/#{denum}" + + + newRat: (nom , denom) -> + if nom * denom >= 0 + return "#{Math.abs(nom)}/#{Math.abs(denom)}" + return "#{-Math.abs(nom)}/#{Math.abs(denom)}" + + return service + + app.filter "ratpp", ["rationals", (rationals) -> + filter = (n) -> + return rationals.pp(n) + ] diff --git a/static/gulpfile.js b/static/gulpfile.js index 25c065b..660ccfa 100644 --- a/static/gulpfile.js +++ b/static/gulpfile.js @@ -17,7 +17,7 @@ gulp.task('bower', function() { gulp.task('sass', ['bower'], function () { return gulp.src('./scss/*.scss') - .pipe(sass({includePaths: ['bower_components/foundation/scss']})) + .pipe(sass({includePaths: ['bower_components/foundation/scss', 'bower_components/bourbon/app/assets/stylesheets']})) .pipe(gulp.dest('./css')); }); diff --git a/static/index.html b/static/index.html index 390f0ad..485b4a9 100644 --- a/static/index.html +++ b/static/index.html @@ -9,35 +9,39 @@ +
+ + + +
    +
  • + +
  • +
- - + +
+
+
- - - - - - - -
- - +
+ diff --git a/static/partials/doorman.html b/static/partials/doorman.html index 6c99f59..6bdc987 100644 --- a/static/partials/doorman.html +++ b/static/partials/doorman.html @@ -12,19 +12,13 @@
-
+
+
@@ -55,11 +49,11 @@