Skip to content

Commit

Permalink
Merge pull request #29 from flimzy/doneCard
Browse files Browse the repository at this point in the history
Done card
  • Loading branch information
flimzy committed Jan 23, 2017
2 parents 3bf37df + 110dfbb commit 67b77b4
Show file tree
Hide file tree
Showing 13 changed files with 102 additions and 85 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,4 @@ main.js.map

# go-bindata
controllers/ankibasic/data.go
repository/done/data.go
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ before_install:

install:
- go get -u github.com/pmezard/go-difflib/difflib
- go get -u github.com/jteeuwen/go-bindata/...
- go get -u github.com/flimzy/testify
- go get -u -d -tags=js ./...

Expand Down
9 changes: 7 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ cordova-init: plugins platforms
android: cordova-init cordova-www
cordova run android

go-test: npm-install
go-test: npm-install bindata
rm -rf ${GOPATH}/pkg/*_js
gopherjs test --tags=debug,safe github.com/FlashbackSRS/flashback/util github.com/FlashbackSRS/flashback/repository/test github.com/FlashbackSRS/flashback/repository

Expand Down Expand Up @@ -67,7 +67,7 @@ www/js/cardframe.js: webclient/js/cardframe.js
cp $< $@

.PHONY: main.js
main.js: controllers/ankibasic/data.go
main.js: bindata
rm -rf ${GOPATH}/pkg/*_js
gopherjs build --tags=debug ./webclient/*.go
# uglifyjs main.js -c -m -o $@
Expand Down Expand Up @@ -98,5 +98,10 @@ www: javascript css images $(HTML_FILES) $(I18N_FILES)
cordova-www: www
cat www/index.html | sed -e 's/<!-- Cordova Here -->/<script src="cordova.js"><\/script>/' > www/cordova.html

bindata: controllers/ankibasic/data.go repository/done/data.go

controllers/ankibasic/data.go: $(wildcard controllers/ankibasic/js/*)
go-bindata -pkg ankibasic -nocompress -prefix "$(dir $<)" -o $@ $(dir $<)

repository/done/data.go: $(wildcard repository/done/html/*)
go-bindata -pkg done -nocompress -prefix "$(dir $<)" -o $@ $(dir $<)
2 changes: 1 addition & 1 deletion controllers/ankibasic/ankibasic.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func (m *AnkiBasic) Buttons(face int) (studyview.ButtonMap, error) {
}

// Action responds to a card action, such as a button press
func (m *AnkiBasic) Action(card *repo.Card, face *int, startTime time.Time, button studyview.Button) (bool, error) {
func (m *AnkiBasic) Action(card *repo.PouchCard, face *int, startTime time.Time, button studyview.Button) (bool, error) {
log.Debugf("%s button pressed for face %d\n", button, *face)
if btns, ok := buttonMaps[*face]; ok {
if _, valid := btns[button]; !valid {
Expand Down
2 changes: 1 addition & 1 deletion controllers/mock/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func (m *Mock) Buttons(_ int) (studyview.ButtonMap, error) {
}

// Action responds to a card action, such as a button press
func (m *Mock) Action(card *repo.Card, face *int, _ time.Time, button studyview.Button) (bool, error) {
func (m *Mock) Action(card *repo.PouchCard, face *int, _ time.Time, button studyview.Button) (bool, error) {
log.Debugf("face: %d, button: %+v\n", face, button)
return true, nil
}
107 changes: 34 additions & 73 deletions repository/card.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"golang.org/x/net/html"

"github.com/FlashbackSRS/flashback-model"
"github.com/FlashbackSRS/flashback/repository/done"
"github.com/FlashbackSRS/flashback/util"
"github.com/FlashbackSRS/flashback/webclient/views/studyview"
)
Expand All @@ -29,15 +30,23 @@ func init() {
rand.Seed(int64(time.Now().UnixNano()))
}

// Card provides a convenient interface to fb.Card and dependencies
type Card struct {
// Card represents a generic card-like object.
type Card interface {
DocID() string
Buttons(face int) (studyview.ButtonMap, error)
Body(face int) (body string, iframeID string, err error)
Action(face *int, startTime time.Time, button studyview.Button) (done bool, err error)
}

// PouchCard provides a convenient interface to fb.Card and dependencies
type PouchCard struct {
*fb.Card
db *DB
note *Note
priority float32
}

type cardList []*Card
type cardList []*PouchCard

func (c cardList) Len() int { return len(c) }
func (c cardList) Less(i, j int) bool {
Expand All @@ -51,28 +60,28 @@ type jsCard struct {

// MarshalJSON marshals a Card for the benefit of javascript context in HTML
// templates.
func (c *Card) MarshalJSON() ([]byte, error) {
func (c *PouchCard) MarshalJSON() ([]byte, error) {
card := &jsCard{
ID: c.DocID(),
}
return json.Marshal(card)
}

// Save saves the card's current state to the database.
func (c *Card) Save() error {
func (c *PouchCard) Save() error {
log.Debugf("Attempting to save card %s\n", c.Identity())
return c.db.Save(c.Card)
}

// Note returns the card's associated Note
func (c *Card) Note() (*Note, error) {
func (c *PouchCard) Note() (*Note, error) {
if err := c.fetchNote(); err != nil {
return nil, errors.Wrap(err, "Error fetching note for Note()")
}
return c.note, nil
}

func (c *Card) fetchNote() error {
func (c *PouchCard) fetchNote() error {
if c.note != nil {
// Nothing to do
return nil
Expand All @@ -94,21 +103,21 @@ func (c *Card) fetchNote() error {
}

// GetCard fetches the requested card
func (u *User) GetCard(id string) (*Card, error) {
func (u *User) GetCard(id string) (*PouchCard, error) {
db, err := u.DB()
if err != nil {
return nil, errors.Wrap(err, "GetNextCard(): Error connecting to User DB")
return nil, errors.Wrap(err, "connect to user DB")
}
return db.GetCard(id)
}

// GetCard fetches the requested card
func (db *DB) GetCard(id string) (*Card, error) {
func (db *DB) GetCard(id string) (*PouchCard, error) {
card := &fb.Card{}
if err := db.Get(id, card, pouchdb.Options{}); err != nil {
return nil, errors.Wrap(err, "Unable to fetch requested card")
return nil, errors.Wrap(err, "fetch card")
}
return &Card{
return &PouchCard{
Card: card,
db: db,
}, nil
Expand Down Expand Up @@ -137,8 +146,8 @@ func CardPrio(due *fb.Due, interval *fb.Interval, now time.Time) float32 {
}

// GetCards fetches up to limit cards from the db, in priority order.
func GetCards(db *DB, now time.Time, limit int) ([]*Card, error) {
var cards []*Card
func GetCards(db *DB, now time.Time, limit int) ([]*PouchCard, error) {
var cards []*PouchCard
query := map[string]interface{}{
"selector": map[string]interface{}{
"type": "card",
Expand Down Expand Up @@ -167,15 +176,15 @@ func GetCards(db *DB, now time.Time, limit int) ([]*Card, error) {
}

// UnmarshalJSON wraps fb.Card's Unmarshaler
func (c *Card) UnmarshalJSON(data []byte) error {
func (c *PouchCard) UnmarshalJSON(data []byte) error {
fbCard := &fb.Card{}
err := json.Unmarshal(data, fbCard)
c.Card = fbCard
return err
}

// GetNextCard gets the next card to study
func (u *User) GetNextCard() (*Card, error) {
func (u *User) GetNextCard() (Card, error) {
db, err := u.DB()
if err != nil {
return nil, errors.Wrap(err, "GetNextCard(): Error connecting to User DB")
Expand All @@ -185,6 +194,9 @@ func (u *User) GetNextCard() (*Card, error) {
if err != nil {
return nil, errors.Wrap(err, "get card list")
}
if len(cards) == 0 {
return done.GetCard(), nil
}
var weights float32
for _, c := range cards {
weights += c.priority
Expand All @@ -203,7 +215,7 @@ func (u *User) GetNextCard() (*Card, error) {

type cardContext struct {
IframeID string
Card *Card
Card *PouchCard
Note *Note
// Model *Model
// Deck *Deck
Expand All @@ -224,7 +236,7 @@ var faces = map[int]string{
}

// Buttons returns the button states for the given card/face.
func (c *Card) Buttons(face int) (studyview.ButtonMap, error) {
func (c *PouchCard) Buttons(face int) (studyview.ButtonMap, error) {
cont, err := c.getModelController()
if err != nil {
return nil, err
Expand All @@ -233,7 +245,7 @@ func (c *Card) Buttons(face int) (studyview.ButtonMap, error) {
}

// Action handles the action on the card, such as a button press.
func (c *Card) Action(face *int, startTime time.Time, button studyview.Button) (done bool, err error) {
func (c *PouchCard) Action(face *int, startTime time.Time, button studyview.Button) (done bool, err error) {
cont, err := c.getModelController()
if err != nil {
return false, err
Expand All @@ -242,7 +254,7 @@ func (c *Card) Action(face *int, startTime time.Time, button studyview.Button) (
}

// Model returns the model for the card
func (c *Card) Model() (*Model, error) {
func (c *PouchCard) Model() (*Model, error) {
note, err := c.Note()
if err != nil {
return nil, errors.Wrap(err, "retrieve Note")
Expand All @@ -255,7 +267,7 @@ func (c *Card) Model() (*Model, error) {
}

// Body returns the requested card face
func (c *Card) Body(face int) (body string, iframeID string, err error) {
func (c *PouchCard) Body(face int) (body string, iframeID string, err error) {
note, err := c.Note()
if err != nil {
return "", "", errors.Wrap(err, "Unable to retrieve Note")
Expand Down Expand Up @@ -409,7 +421,7 @@ func findContainer(n *html.Node, targetID, targetClass string) *html.Node {
}

// GetAttachment fetches an attachment from the note, failling back to the model
func (c *Card) GetAttachment(filename string) (*Attachment, error) {
func (c *PouchCard) GetAttachment(filename string) (*Attachment, error) {
n, err := c.Note()
if err != nil {
return nil, errors.Wrap(err, "Error fetching Note for GetAttachment()")
Expand All @@ -427,54 +439,3 @@ func (c *Card) GetAttachment(filename string) (*Attachment, error) {
}
return nil, errors.Errorf("File '%s' not found", filename)
}

// Response represents a response button
type Response struct {
Name string
Display string
Icon string
}

var showAnswer = &Response{
Name: "show_answer_button",
Display: "Show Answer",
Icon: "carat-r",
}

var wrongAnswer = &Response{
Name: "wrong_answer_button",
Display: "Again",
Icon: "delete",
}

var hardAnswer = &Response{
Name: "hard_answer_button",
Display: "Hard",
Icon: "clock",
}

var goodAnswer = &Response{
Name: "good_answer_button",
Display: "Good",
Icon: "carat-r",
}

var easyAnswer = &Response{
Name: "easy_answer_button",
Display: "Easy",
Icon: "heart",
}

// Responses returns the list of available responses for a card's face
func (c *Card) Responses(face int) ([]*Response, error) {
var responses []*Response
switch face {
case Question:
responses = []*Response{showAnswer}
case Answer:
responses = []*Response{wrongAnswer, hardAnswer, goodAnswer, easyAnswer}
default:
return nil, errors.Errorf("Unknown card face %d", face)
}
return responses, nil
}
4 changes: 2 additions & 2 deletions repository/card_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type fakeController struct{}
func (f *fakeController) Type() string { return "fake-model" }
func (f *fakeController) IframeScript() []byte { return []byte("/* Fake Model */") }
func (f *fakeController) Buttons(_ int) (studyview.ButtonMap, error) { return nil, nil }
func (f *fakeController) Action(_ *Card, _ *int, _ time.Time, _ studyview.Button) (bool, error) {
func (f *fakeController) Action(_ *PouchCard, _ *int, _ time.Time, _ studyview.Button) (bool, error) {
return false, nil
}

Expand Down Expand Up @@ -162,7 +162,7 @@ func parseDue(ds string) fb.Due {

func TestUnmarshal(t *testing.T) {
raw := `{"_id":"card-alnlcvykyjxsjtijzonc3456kd5u4757.udROb8T8RmRASG5zGHNKnKL25zI.0","_rev":"1-daccd83780014e8cf35ce8f16d2a144c","created":"2015-09-08T23:55:03.000000539Z","imported":"2017-01-02T17:16:56.764985035+01:00","model":"theme-ELr8cEJJOvJU4lYz-VTXhH8wLTo/0","modified":"2016-08-02T13:05:04Z","type":"card"}`
card := &Card{}
card := &PouchCard{}
if err := json.Unmarshal([]byte(raw), card); err != nil {
t.Errorf("Failed to unmarshal card: %s\n", err)
}
Expand Down
47 changes: 47 additions & 0 deletions repository/done/done.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Package done provides a single card to display when there is nothing left to
// study.
package done

import (
"time"

"github.com/FlashbackSRS/flashback/webclient/views/studyview"
)

// Card is a card displaying a "no cards to study" message
type Card struct{}

// GetCard returns the done card.
func GetCard() *Card {
return &Card{}
}

// DocID returns a dummy document ID.
func (c *Card) DocID() string {
return "<<done>>"
}

// Buttons returns the list of available buttons. As there is only one face
// for Done cards, the face value is ignored.
func (c *Card) Buttons(_ int) (studyview.ButtonMap, error) {
return studyview.ButtonMap{
studyview.ButtonRight: studyview.ButtonState{
Name: "Check Again",
Enabled: true,
},
}, nil
}

// Action always returns true, to allow checking for new due cards.
func (c *Card) Action(_ *int, _ time.Time, _ studyview.Button) (done bool, err error) {
return true, nil
}

// Body returns the Done card body.
func (c *Card) Body(_ int) (string, string, error) {
body, err := Asset("done.html")
if err != nil {
return "", "", err
}
return string(body), "doneIFrame", nil
}
1 change: 1 addition & 0 deletions repository/done/html/done.html
Loading

0 comments on commit 67b77b4

Please sign in to comment.