Skip to content

Commit

Permalink
Move "Recently Installed Updates" section to the top.
Browse files Browse the repository at this point in the history
This change implements a minor presentation redesign, where the
"Recently Installed Updates" section is moved at the top, above
the "Updates Available" section. Previously it was at the bottom.

The motivation to do this is to improve the user experience when
installing updates from top to bottom. This way, the recently
updated package moves up to the section above, which is a much
smaller jump than moving it all the way to the bottom of the page.

Clean up some of the code in the process.

Regenerate assets.

Fixes #63.
  • Loading branch information
dmitshur committed Oct 28, 2018
1 parent 19e2d27 commit 5eb0212
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 73 deletions.
8 changes: 6 additions & 2 deletions _data/component/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ type UpdatesBody struct {
func (*UpdatesBody) Render() vecty.ComponentOrHTML {
return elem.Body(
gpscomponent.UpdatesContent(
mockComponentRPs,
mockActive,
mockHistory,
true,
)...,
)
}

var mockComponentRPs = []*model.RepoPresentation{
var mockActive = []*model.RepoPresentation{
{
RepoRoot: "github.com/gopherjs/gopherjs",
ImportPathPattern: "github.com/gopherjs/gopherjs/...",
Expand Down Expand Up @@ -95,6 +96,9 @@ var mockComponentRPs = []*model.RepoPresentation{
UpdateState: model.Available,
UpdateSupported: true,
},
}

var mockHistory = []*model.RepoPresentation{
{
RepoRoot: "golang.org/x/image",
ImportPathPattern: "golang.org/x/image/...",
Expand Down
18 changes: 9 additions & 9 deletions assets/assets_vfsdata.go

Large diffs are not rendered by default.

41 changes: 23 additions & 18 deletions component/header.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,34 @@ func (*Header) Render() vecty.ComponentOrHTML {
// updatesHeader combines checkingForUpdates, noUpdates and updatesHeading
// into one high level component.
type updatesHeader struct {
RPs []*model.RepoPresentation
Active []*model.RepoPresentation
CheckingUpdates bool
}

func (u updatesHeader) Render() []vecty.MarkupOrChild {
var ns []vecty.MarkupOrChild
// Show "Checking for updates..." while still checking.
if u.CheckingUpdates {
ns = append(ns, checkingForUpdates())
}
available, updating, supported := u.status()
// Show "No Updates Available" if we're done checking and there are no remaining updates.
if !u.CheckingUpdates && available == 0 && !updating {
ns = append(ns, noUpdates())
switch {
case u.CheckingUpdates:
// Show "Checking for updates..." while still checking.
ns = append(ns, heading(elem.Heading2, "Checking for updates..."))
case !u.CheckingUpdates && len(u.Active) > 0:
// Show "Updates Available" if we're done checking and there are active updates.
ns = append(ns, heading(elem.Heading2, "Updates Available"))
case !u.CheckingUpdates && len(u.Active) == 0:
// Show "No Updates Available" if we're done checking and there are no remaining updates.
ns = append(ns,
elem.Heading2(
vecty.Markup(vecty.Style("text-align", "center"), vecty.Style("margin-bottom", "2px")),
vecty.Text("No Updates Available"),
),
elem.Heading4(
vecty.Markup(vecty.Style("text-align", "center"), vecty.Style("margin-top", "2px"), vecty.Style("font-weight", "normal")),
vecty.Text("All your Go packages are up to date"),
),
)
}
// Show number of updates available and Update All button.
available, updating, supported := u.status()
ns = append(ns, &updatesHeading{
Available: available,
Updating: updating,
Expand All @@ -56,9 +68,9 @@ func (u updatesHeader) Render() []vecty.MarkupOrChild {
return ns
}

// status returns available, updating, supported updates in u.RPs.
// status reports available, updating, supported updates in u.Active.
func (u updatesHeader) status() (available uint, updating bool, supported bool) {
for _, rp := range u.RPs {
for _, rp := range u.Active {
switch rp.UpdateState {
case model.Available:
available++
Expand Down Expand Up @@ -138,13 +150,6 @@ func (u *updatesHeading) updateAllButton() *vecty.HTML {
}
}

// InstalledUpdates is a heading for installed updates.
func InstalledUpdates() *vecty.HTML { return heading(elem.Heading3, "Installed Updates") }

func checkingForUpdates() *vecty.HTML { return heading(elem.Heading2, "Checking for updates...") }

func noUpdates() *vecty.HTML { return heading(elem.Heading2, "No Updates Available") }

func heading(heading func(markup ...vecty.MarkupOrChild) *vecty.HTML, text string) *vecty.HTML {
return heading(
vecty.Markup(vecty.Style("text-align", "center")),
Expand Down
34 changes: 23 additions & 11 deletions component/updates.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,37 +7,49 @@ import (
)

// UpdatesContent returns the entire content of updates tab.
func UpdatesContent(rps []*model.RepoPresentation, checkingUpdates bool) []vecty.MarkupOrChild {
func UpdatesContent(active, history []*model.RepoPresentation, checkingUpdates bool) []vecty.MarkupOrChild {
return []vecty.MarkupOrChild{
&Header{},
elem.Div(
vecty.Markup(vecty.Class("center-max-width")),
elem.Div(
updatesContent(rps, checkingUpdates)...,
updatesContent(active, history, checkingUpdates)...,
),
),
}
}

func updatesContent(rps []*model.RepoPresentation, checkingUpdates bool) []vecty.MarkupOrChild {
func updatesContent(active, history []*model.RepoPresentation, checkingUpdates bool) []vecty.MarkupOrChild {
var content = []vecty.MarkupOrChild{
vecty.Markup(vecty.Class("content")),
}

// History with "Recently Installed Updates" heading, if any.
if len(history) > 0 {
content = append(content, heading(elem.Heading3, "Recently Installed Updates"))

for _, rp := range history {
content = append(content, &RepoPresentation{
RepoPresentation: rp,
})
}

// Spacer at the bottom.
content = append(content, elem.Div(
vecty.Markup(vecty.Style("height", "60px")),
))
}

// Updates header.
content = append(content,
updatesHeader{
RPs: rps,
Active: active,
CheckingUpdates: checkingUpdates,
}.Render()...,
)

wroteInstalledUpdates := false
for _, rp := range rps {
if rp.UpdateState == model.Updated && !wroteInstalledUpdates {
content = append(content, InstalledUpdates())
wroteInstalledUpdates = true
}

// Active updates.
for _, rp := range active {
content = append(content, &RepoPresentation{
RepoPresentation: rp,
})
Expand Down
3 changes: 2 additions & 1 deletion frontend/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ type UpdatesBody struct {
func (b *UpdatesBody) Render() vecty.ComponentOrHTML {
return elem.Body(
gpscomponent.UpdatesContent(
store.RPs(),
store.Active(),
store.History(),
store.CheckingUpdates(),
)...,
)
Expand Down
57 changes: 25 additions & 32 deletions frontend/store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@ import (
)

var (
rps []*model.RepoPresentation
active []*model.RepoPresentation
history []*model.RepoPresentation
checkingUpdates = true
)

// RPs returns the repo presentations of store.
func RPs() []*model.RepoPresentation { return rps }
// Active returns the active repo presentations in store.
func Active() []*model.RepoPresentation { return active }

// History returns the historical repo presentations in store.
func History() []*model.RepoPresentation { return history }

// CheckingUpdates reports whether the process of checking for updates is still running.
func CheckingUpdates() bool { return checkingUpdates }
Expand All @@ -24,12 +28,16 @@ func CheckingUpdates() bool { return checkingUpdates }
func Apply(a action.Action) action.Response {
switch a := a.(type) {
case *action.AppendRP:
rps = append(rps, a.RP)
moveUp(rps, a.RP)
switch a.RP.UpdateState {
case model.Available, model.Updating:
active = append(active, a.RP)
case model.Updated:
history = append(history, a.RP)
}
return nil

case *action.SetUpdating:
for _, rp := range rps {
for _, rp := range active {
if rp.RepoRoot == a.RepoRoot {
rp.UpdateState = model.Updating
return nil
Expand All @@ -39,7 +47,7 @@ func Apply(a action.Action) action.Response {

case *action.SetUpdatingAll:
var repoRoots []string
for _, rp := range rps {
for _, rp := range active {
if rp.UpdateState == model.Available {
repoRoots = append(repoRoots, rp.RepoRoot)
rp.UpdateState = model.Updating
Expand All @@ -51,10 +59,18 @@ func Apply(a action.Action) action.Response {
return &action.SetUpdatingAllResponse{RepoRoots: repoRoots}

case *action.SetUpdated:
moveDown(rps, a.RepoRoot)
for _, rp := range rps {
for i, rp := range active {
if rp.RepoRoot == a.RepoRoot {
// Remove from active.
copy(active[i:], active[i+1:])
active = active[:len(active)-1]

// Set UpdateState.
rp.UpdateState = model.Updated

// Append to history.
history = append(history, rp)

return nil
}
}
Expand All @@ -68,26 +84,3 @@ func Apply(a action.Action) action.Response {
panic(fmt.Errorf("%v (type %T) is not a valid action", a, a))
}
}

// TODO: Both moveDown and moveUp can be inlined and simplified.

// moveDown moves root down the rps towards all other updated.
func moveDown(rps []*model.RepoPresentation, root string) {
var i int
for ; rps[i].RepoRoot != root; i++ { // i is the current package about to be updated.
}
for ; i+1 < len(rps) && rps[i+1].UpdateState != model.Updated; i++ {
rps[i], rps[i+1] = rps[i+1], rps[i] // Swap the two.
}
}

// moveUp moves last entry up the rps above all other updated entries, unless rp is already updated.
func moveUp(rps []*model.RepoPresentation, rp *model.RepoPresentation) {
// TODO: The "unless rp is already updated" part might not be needed if more strict about possible cases.
if rp.UpdateState == model.Updated {
return
}
for i := len(rps) - 1; i-1 >= 0 && rps[i-1].UpdateState == model.Updated; i-- {
rps[i], rps[i-1] = rps[i-1], rps[i] // Swap the two.
}
}

0 comments on commit 5eb0212

Please sign in to comment.