Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: auto reveal #76

Merged
merged 6 commits into from
Jul 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
.PHONY: build run generate test
.PHONY:
build
build-all
run
generate
test
lint
lint-fix
demo

build: generate
@go build -v -o 2sp ./cmd/2sp
Expand Down
8 changes: 8 additions & 0 deletions internal/testcommon/suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"go.uber.org/zap"

"github.com/six78/2-story-points-cli/internal/config"
"github.com/six78/2-story-points-cli/pkg/protocol"
)

type Suite struct {
Expand Down Expand Up @@ -46,3 +47,10 @@ func (s *Suite) FakePayload() ([]byte, []byte) {

return payload, jsonPayload
}

func (s *Suite) FakeIssue() *protocol.Issue {
issue := &protocol.Issue{}
err := gofakeit.Struct(&issue)
s.Require().NoError(err)
return issue
}
18 changes: 9 additions & 9 deletions internal/view/DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@

Author: @Alice [LanguageChange] [v2] [Proposal]
Assignee: @Bob [NeedsInvestigation]

╭───────┬───────────┬─────────┬───────┬──────╮
│ Alice │ Bob (You) │ Charlie │ David │ Erin │ Recommended: 8
├───────┼───────────┼─────────┼───────┼──────┤ Acceptable: ✓
│ 8 │ 8 │ 5 │ 5 │ 8 │ > Not bad.
│ Alice │ Bob (You) │ Charlie │ David │ Erin │
├───────┼───────────┼─────────┼───────┼──────┤
│ 8 │ 8 │ 5 │ 5 │ 8 │ Revealing in 3.0
╰───────┴───────────┴─────────┴───────┴──────╯
╭───╮
╭───╮ ╭───╮ ╭───╮ ╭───╮ │ 8 │ ╭────╮ ╭────╮ ╭───
│ 1 │ │ 2 │ │ 3 │ │ 5 │ ╰───╯ │ 13 │ │ 21 │ │ 34
╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰────╯ ╰────╯ ╰───
╭───╮ ╭───╮ ╭───╮ ╭───╮ │ 8 │ ╭────╮ ╭────╮ ╭───╮
│ 1 │ │ 2 │ │ 3 │ │ 5 │ ╰───╯ │ 13 │ │ 21 │ │ ?
╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰────╯ ╰────╯ ╰───╯
^

Use [←] and [→] arrows to select a card and press [Enter]
[Tab] To switch to issues list view
[C] Switch to command mode [Q] Leave room [E] Exit [H] Help
Expand Down Expand Up @@ -192,7 +192,7 @@ Therefore the card can be in one of these 4 states:
</tr>
</table>

For example for a Fibbonacci deck
For example for a Fibonacci deck

```shell
Your vote:
Expand Down
55 changes: 28 additions & 27 deletions internal/view/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,46 +7,47 @@ import (

tea "github.com/charmbracelet/bubbletea"
"github.com/pkg/errors"
"golang.org/x/exp/slices"

"github.com/six78/2-story-points-cli/internal/view/commands"
"github.com/six78/2-story-points-cli/internal/view/messages"
"github.com/six78/2-story-points-cli/internal/view/states"
"github.com/six78/2-story-points-cli/pkg/game"
"github.com/six78/2-story-points-cli/pkg/protocol"
"golang.org/x/exp/slices"
)

type Action string

const (
Rename Action = "rename"
New Action = "new"
Join Action = "join"
Exit Action = "exit"
Vote Action = "vote"
Unvote Action = "unvote"
Deal Action = "deal"
Add Action = "add"
Reveal Action = "reveal"
Finish Action = "finish"
Deck Action = "deck"
Select Action = "select"
Rename Action = "rename"
New Action = "new"
Join Action = "join"
Exit Action = "exit"
Vote Action = "vote"
Retract Action = "retract"
Deal Action = "deal"
Add Action = "add"
Reveal Action = "reveal"
Finish Action = "finish"
Deck Action = "deck"
Select Action = "select"
)

type actionFunc func(m *model, args []string) tea.Cmd

var actions = map[Action]actionFunc{
Rename: runRenameAction,
Vote: runVoteAction,
Unvote: runUnvoteAction,
Deal: runDealAction,
Add: runAddAction,
New: runNewAction,
Join: runJoinAction,
Exit: runExitAction,
Reveal: runRevealAction,
Finish: runFinishAction,
Deck: runDeckAction,
Select: runSelectAction,
Rename: runRenameAction,
Vote: runVoteAction,
Retract: runRetractAction,
Deal: runDealAction,
Add: runAddAction,
New: runNewAction,
Join: runJoinAction,
Exit: runExitAction,
Reveal: runRevealAction,
Finish: runFinishAction,
Deck: runDeckAction,
Select: runSelectAction,
}

func processPlayerNameInput(m *model, playerName string) tea.Cmd {
Expand Down Expand Up @@ -93,9 +94,9 @@ func runVoteAction(m *model, args []string) tea.Cmd {
}
}

func runUnvoteAction(m *model, args []string) tea.Cmd {
func runRetractAction(m *model, args []string) tea.Cmd {
return func() tea.Msg {
return commands.PublishVote(m.game, "")()
return commands.RetractVote(m.game)()
}
}

Expand Down
14 changes: 14 additions & 0 deletions internal/view/commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

tea "github.com/charmbracelet/bubbletea"
"github.com/pkg/errors"

"github.com/six78/2-story-points-cli/internal/transport"
"github.com/six78/2-story-points-cli/internal/view/messages"
"github.com/six78/2-story-points-cli/internal/view/states"
Expand Down Expand Up @@ -99,6 +100,19 @@ func PublishVote(game *game.Game, vote protocol.VoteValue) tea.Cmd {
}
}

func RetractVote(game *game.Game) tea.Cmd {
return func() tea.Msg {
err := game.RetractVote()
if err != nil {
return messages.NewErrorMessage(err)
}
// TODO: Send err=nil ErrorMessage here
return messages.MyVote{
Result: game.MyVote(),
}
}
}

func FinishVoting(game *game.Game, result protocol.VoteValue) tea.Cmd {
return func() tea.Msg {
err := game.Finish(result)
Expand Down
2 changes: 0 additions & 2 deletions internal/view/components/hintview/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,9 @@ func (m Model) View() string {
}

return lipgloss.JoinVertical(lipgloss.Top,
"",
headerStyle.Render("Recommended:")+""+voteview.Render(m.hint.Value),
headerStyle.Render("Acceptable:")+" "+renderAcceptanceIcon(m.hint.Acceptable),
headerStyle.Render(">")+" "+textStyle.Render(m.hint.Description),
"",
)
}

Expand Down
2 changes: 0 additions & 2 deletions internal/view/components/hintview/model_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,9 @@ func TestUpdateAcceptableVote(t *testing.T) {
}

expectedLines := []string{
"",
"Recommended: " + string(issue.Hint.Value),
"Acceptable: " + expectedAcceptableIcon,
"> " + issue.Hint.Description,
"",
}

lines := strings.Split(model.View(), "\n")
Expand Down
3 changes: 1 addition & 2 deletions internal/view/components/issueview/issue_view.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ var (
primaryStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#F0F0F0"))
secondaryStyle = lipgloss.NewStyle().Foreground(config.ForegroundShadeColor)
hyperlinkStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#648EF8")).Underline(true)
errorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#FF1744"))
)

const (
Expand Down Expand Up @@ -146,7 +145,7 @@ func (m *Model) renderTitle(info *issueInfo) string {
}

if info.err != nil {
return errorStyle.Render(fmt.Sprintf("[%s]", info.err.Error()))
return secondaryStyle.Render(fmt.Sprintf("[%s]", info.err.Error()))
}

if info.title == nil {
Expand Down
47 changes: 47 additions & 0 deletions internal/view/components/votestate/model.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package votestate

import (
"fmt"
"time"

tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"

"github.com/six78/2-story-points-cli/internal/config"
"github.com/six78/2-story-points-cli/internal/view/messages"
)

var style = lipgloss.NewStyle().Foreground(config.ForegroundShadeColor)

type Model struct {
duration time.Duration
start time.Time
}

func (m Model) Init() tea.Cmd {
return nil
}

func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
switch msg := msg.(type) {
case messages.AutoRevealScheduled:
m.start = time.Now()
m.duration = msg.Duration
case messages.AutoRevealCancelled:
m.start = time.Time{}
m.duration = 0
}

return m, nil
}

func (m Model) View() string {
if m.start.IsZero() {
return ""
}
left := (m.duration - time.Since(m.start)).Seconds()
if left == 0 {
return style.Render("Revealing votes...")
}
return style.Render(fmt.Sprintf("Revealing in %.1f", left))
}
9 changes: 9 additions & 0 deletions internal/view/messages/messages.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package messages

import (
"time"

"github.com/six78/2-story-points-cli/internal/transport"
"github.com/six78/2-story-points-cli/internal/view/states"
"github.com/six78/2-story-points-cli/pkg/protocol"
Expand Down Expand Up @@ -59,3 +61,10 @@ type MyVote struct {

type EnableEnterKey struct {
}

type AutoRevealScheduled struct {
Duration time.Duration
}

type AutoRevealCancelled struct {
}
32 changes: 22 additions & 10 deletions internal/view/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/six78/2-story-points-cli/internal/view/components/playersview"
"github.com/six78/2-story-points-cli/internal/view/components/shortcutsview"
"github.com/six78/2-story-points-cli/internal/view/components/userinput"
"github.com/six78/2-story-points-cli/internal/view/components/votestate"
"github.com/six78/2-story-points-cli/internal/view/components/wakustatusview"
"github.com/six78/2-story-points-cli/internal/view/messages"
"github.com/six78/2-story-points-cli/internal/view/states"
Expand All @@ -46,16 +47,18 @@ type model struct {
connectionStatus transport.ConnectionStatus

// UI components state
commandMode bool
roomViewState states.RoomView
errorView errorview.Model
playersView playersview.Model
hintView hintview.Model
shortcutsView shortcutsview.Model
wakuStatusView wakustatusview.Model
deckView deckview.Model
issueView issueview.Model
issuesListView issuesview.Model
commandMode bool
roomViewState states.RoomView
errorView errorview.Model
playersView playersview.Model
hintView hintview.Model
shortcutsView shortcutsview.Model
wakuStatusView wakustatusview.Model
deckView deckview.Model
issueView issueview.Model
issuesListView issuesview.Model
voteStateView votestate.Model

gameEventHandler eventhandler.Model[game.Event, interface{}]
transportEventHandler eventhandler.Model[transport.ConnectionStatus, messages.ConnectionStatus]

Expand Down Expand Up @@ -95,6 +98,7 @@ func InitialModel(game *game.Game, transport transport.Service) model {
deckView: deckView,
issueView: issueview.New(),
issuesListView: issuesview.New(),
voteStateView: votestate.Model{},
// Other
disableEnterKey: false,
disableEnterRestart: nil,
Expand All @@ -120,6 +124,7 @@ func (m model) Init() tea.Cmd {
m.deckView.Init(),
m.issueView.Init(),
m.issuesListView.Init(),
m.voteStateView.Init(),
commands.InitializeApp(m.game, m.transport),
)
}
Expand Down Expand Up @@ -286,6 +291,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.deckView = m.deckView.Update(msg)
m.issueView, cmds.IssueViewCommand = m.issueView.Update(msg)
m.issuesListView, cmds.IssuesListViewCommand = m.issuesListView.Update(msg)
m.voteStateView, _ = m.voteStateView.Update(msg)
m.gameEventHandler, cmds.GameEventHandlerCommand = m.gameEventHandler.Update(msg)
m.transportEventHandler, cmds.TransportEventHandlerCommand = m.transportEventHandler.Update(msg)

Expand Down Expand Up @@ -400,6 +406,12 @@ func gameEventToMessage(event game.Event) interface{} {
if state, ok := event.Data.(*protocol.State); ok {
return messages.GameStateMessage{State: state}
}
case game.EventAutoRevealScheduled:
if duration, ok := event.Data.(time.Duration); ok {
return messages.AutoRevealScheduled{Duration: duration}
}
case game.EventAutoRevealCancelled:
return messages.AutoRevealCancelled{}
default:
return nil
}
Expand Down
9 changes: 8 additions & 1 deletion internal/view/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,17 @@ func (m model) renderRoomCurrentIssueView() string {
)
}

playersView := m.playersView.View()
if m.gameState.VotesRevealed {
playersView = lipgloss.JoinHorizontal(lipgloss.Center, playersView, " ", m.hintView.View())
} else if m.game.IsDealer() && m.gameState.AllPlayersVoted() {
playersView = lipgloss.JoinHorizontal(0.75, playersView, " ", m.voteStateView.View())
}

return lipgloss.JoinVertical(lipgloss.Top,
m.issueView.View(),
"",
lipgloss.JoinHorizontal(lipgloss.Left, m.playersView.View(), " ", m.hintView.View()),
playersView,
m.deckView.View(),
)
}
Expand Down
4 changes: 4 additions & 0 deletions pkg/game/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ type configuration struct {
OnlineMessagePeriod time.Duration
StateMessagePeriod time.Duration
PublishStateLoopEnabled bool
AutoRevealEnabled bool
AutoRevealDelay time.Duration
}

var defaultConfig = configuration{
Expand All @@ -16,4 +18,6 @@ var defaultConfig = configuration{
OnlineMessagePeriod: 5 * time.Second,
StateMessagePeriod: 30 * time.Second,
PublishStateLoopEnabled: true,
AutoRevealEnabled: true,
AutoRevealDelay: 2 * time.Second,
}
Loading
Loading