Rework structure of project index. Need new copy.

This commit is contained in:
Ben Visness 2023-09-25 02:40:45 -05:00
parent 45b4928d83
commit 25cc5ef11b
9 changed files with 349 additions and 130 deletions

View File

@ -7553,6 +7553,15 @@ article code {
.g5 { .g5 {
gap: 4rem; } gap: 4rem; }
.grid {
display: grid; }
.grid-1 {
grid-template-columns: 1fr; }
.grid-2 {
grid-template-columns: 1fr 1fr; }
.aspect-ratio--2x1 { .aspect-ratio--2x1 {
padding-bottom: 50%; } padding-bottom: 50%; }
@ -7582,6 +7591,10 @@ article code {
column-gap: 2rem; } column-gap: 2rem; }
.cg5-ns { .cg5-ns {
column-gap: 4rem; } column-gap: 4rem; }
.grid-1-ns {
grid-template-columns: 1fr; }
.grid-2-ns {
grid-template-columns: 1fr 1fr; }
.bg--dim-ns { .bg--dim-ns {
background-color: #f0f0f0; background-color: #f0f0f0;
background-color: var(--dim-background); } } background-color: var(--dim-background); } }
@ -7607,6 +7620,10 @@ article code {
column-gap: 2rem; } column-gap: 2rem; }
.cg5-m { .cg5-m {
column-gap: 4rem; } column-gap: 4rem; }
.grid-1-m {
grid-template-columns: 1fr; }
.grid-2-m {
grid-template-columns: 1fr 1fr; }
.bg--dim-m { .bg--dim-m {
background-color: #f0f0f0; background-color: #f0f0f0;
background-color: var(--dim-background); } } background-color: var(--dim-background); } }
@ -7632,6 +7649,10 @@ article code {
column-gap: 2rem; } column-gap: 2rem; }
.cg5-l { .cg5-l {
column-gap: 4rem; } column-gap: 4rem; }
.grid-1-l {
grid-template-columns: 1fr; }
.grid-2-l {
grid-template-columns: 1fr 1fr; }
.bg--dim-l { .bg--dim-l {
background-color: #f0f0f0; background-color: #f0f0f0;
background-color: var(--dim-background); } } background-color: var(--dim-background); } }

View File

@ -19,6 +19,7 @@ type Jam struct {
Event Event
Name string Name string
Slug string Slug string
UrlSlug string
} }
var WRJ2021 = Jam{ var WRJ2021 = Jam{
@ -28,6 +29,7 @@ var WRJ2021 = Jam{
}, },
Name: "Wheel Reinvention Jam 2021", Name: "Wheel Reinvention Jam 2021",
Slug: "WRJ2021", Slug: "WRJ2021",
UrlSlug: "2021",
} }
var WRJ2022 = Jam{ var WRJ2022 = Jam{
@ -37,6 +39,7 @@ var WRJ2022 = Jam{
}, },
Name: "Wheel Reinvention Jam 2022", Name: "Wheel Reinvention Jam 2022",
Slug: "WRJ2022", Slug: "WRJ2022",
UrlSlug: "2022",
} }
var VJ2023 = Jam{ var VJ2023 = Jam{
@ -46,6 +49,7 @@ var VJ2023 = Jam{
}, },
Name: "Visibility Jam 2023", Name: "Visibility Jam 2023",
Slug: "VJ2023", Slug: "VJ2023",
UrlSlug: "visibility-2023",
} }
var WRJ2023 = Jam{ var WRJ2023 = Jam{
@ -55,6 +59,7 @@ var WRJ2023 = Jam{
}, },
Name: "Wheel Reinvention Jam 2023", Name: "Wheel Reinvention Jam 2023",
Slug: "WRJ2023", Slug: "WRJ2023",
UrlSlug: "2023",
} }
var HMS2022 = Event{ var HMS2022 = Event{
@ -84,6 +89,17 @@ func CurrentJam() *Jam {
return nil return nil
} }
func PreviousJam() *Jam {
now := time.Now()
var mostRecent *Jam
for i, jam := range AllJams {
if jam.EndTime.Before(now) {
mostRecent = &AllJams[i]
}
}
return mostRecent
}
func JamBySlug(slug string) Jam { func JamBySlug(slug string) Jam {
for _, jam := range AllJams { for _, jam := range AllJams {
if jam.Slug == slug { if jam.Slug == slug {

View File

@ -142,9 +142,11 @@ func TestFeed(t *testing.T) {
} }
func TestProjectIndex(t *testing.T) { func TestProjectIndex(t *testing.T) {
AssertRegexMatch(t, BuildProjectIndex(1), RegexProjectIndex, nil) AssertRegexMatch(t, BuildProjectIndex(1, ""), RegexProjectIndex, nil)
AssertRegexMatch(t, BuildProjectIndex(2), RegexProjectIndex, map[string]string{"page": "2"}) AssertRegexMatch(t, BuildProjectIndex(2, ""), RegexProjectIndex, map[string]string{"page": "2"})
assert.Panics(t, func() { BuildProjectIndex(0) }) AssertRegexMatch(t, BuildProjectIndex(1, "test"), RegexProjectIndex, map[string]string{"category": "test"})
AssertRegexMatch(t, BuildProjectIndex(2, "test"), RegexProjectIndex, map[string]string{"page": "2", "category": "test"})
assert.Panics(t, func() { BuildProjectIndex(0, "") })
} }
func TestProjectNew(t *testing.T) { func TestProjectNew(t *testing.T) {
@ -415,6 +417,16 @@ func TestJamRecap2023_Visibility(t *testing.T) {
AssertSubdomain(t, BuildJamRecap2023_Visibility(), "") AssertSubdomain(t, BuildJamRecap2023_Visibility(), "")
} }
func TestJamIndex2023(t *testing.T) {
AssertRegexMatch(t, BuildJamIndex2023(), RegexJamIndex2023, nil)
AssertSubdomain(t, BuildJamIndex2023(), "")
}
func TestJamFeed2023(t *testing.T) {
AssertRegexMatch(t, BuildJamFeed2023(), RegexJamFeed2023, nil)
AssertSubdomain(t, BuildJamFeed2023(), "")
}
func TestTimeMachine(t *testing.T) { func TestTimeMachine(t *testing.T) {
AssertRegexMatch(t, BuildTimeMachine(), RegexTimeMachine, nil) AssertRegexMatch(t, BuildTimeMachine(), RegexTimeMachine, nil)
AssertSubdomain(t, BuildTimeMachine(), "") AssertSubdomain(t, BuildTimeMachine(), "")

View File

@ -126,6 +126,11 @@ func BuildJamRecap2023_Visibility() string {
return Url("/jam/visibility-2023/recap", nil) return Url("/jam/visibility-2023/recap", nil)
} }
func BuildJamIndexAny(slug string) string {
defer CatchPanic()
return Url(fmt.Sprintf("/jam/%s", slug), nil)
}
var RegexTimeMachine = regexp.MustCompile("^/timemachine$") var RegexTimeMachine = regexp.MustCompile("^/timemachine$")
func BuildTimeMachine() string { func BuildTimeMachine() string {
@ -445,17 +450,21 @@ func BuildAtomFeedForShowcase() string {
* Projects * Projects
*/ */
var RegexProjectIndex = regexp.MustCompile("^/projects(/(?P<page>.+)?)?$") var RegexProjectIndex = regexp.MustCompile(`^/projects(/(?P<category>[a-z][a-z0-9]+))?(/(?P<page>\d+))?$`)
func BuildProjectIndex(page int) string { func BuildProjectIndex(page int, category string) string {
defer CatchPanic() defer CatchPanic()
if page < 1 { if page < 1 {
panic(oops.New(nil, "page must be >= 1")) panic(oops.New(nil, "page must be >= 1"))
} }
catpath := ""
if category != "" {
catpath = "/" + category
}
if page == 1 { if page == 1 {
return Url("/projects", nil) return Url(fmt.Sprintf("/projects%s", catpath), nil)
} else { } else {
return Url(fmt.Sprintf("/projects/%d", page), nil) return Url(fmt.Sprintf("/projects%s/%d", catpath, page), nil)
} }
} }

View File

@ -372,6 +372,18 @@ article code {
.g4 { gap: $spacing-large; } .g4 { gap: $spacing-large; }
.g5 { gap: $spacing-extra-large; } .g5 { gap: $spacing-extra-large; }
.grid {
display: grid;
}
.grid-1 {
grid-template-columns: 1fr;
}
.grid-2 {
grid-template-columns: 1fr 1fr;
}
.aspect-ratio--2x1 { .aspect-ratio--2x1 {
padding-bottom: 50%; padding-bottom: 50%;
} }
@ -392,6 +404,8 @@ article code {
.cg3-ns { column-gap: $spacing-medium; } .cg3-ns { column-gap: $spacing-medium; }
.cg4-ns { column-gap: $spacing-large; } .cg4-ns { column-gap: $spacing-large; }
.cg5-ns { column-gap: $spacing-extra-large; } .cg5-ns { column-gap: $spacing-extra-large; }
.grid-1-ns { grid-template-columns: 1fr; }
.grid-2-ns { grid-template-columns: 1fr 1fr; }
.bg--dim-ns { .bg--dim-ns {
@include usevar(background-color, dim-background); @include usevar(background-color, dim-background);
@ -409,6 +423,8 @@ article code {
.cg3-m { column-gap: $spacing-medium; } .cg3-m { column-gap: $spacing-medium; }
.cg4-m { column-gap: $spacing-large; } .cg4-m { column-gap: $spacing-large; }
.cg5-m { column-gap: $spacing-extra-large; } .cg5-m { column-gap: $spacing-extra-large; }
.grid-1-m { grid-template-columns: 1fr; }
.grid-2-m { grid-template-columns: 1fr 1fr; }
.bg--dim-m { .bg--dim-m {
@include usevar(background-color, dim-background); @include usevar(background-color, dim-background);
@ -426,6 +442,8 @@ article code {
.cg3-l { column-gap: $spacing-medium; } .cg3-l { column-gap: $spacing-medium; }
.cg4-l { column-gap: $spacing-large; } .cg4-l { column-gap: $spacing-large; }
.cg5-l { column-gap: $spacing-extra-large; } .cg5-l { column-gap: $spacing-extra-large; }
.grid-1-l { grid-template-columns: 1fr; }
.grid-2-l { grid-template-columns: 1fr 1fr; }
.bg--dim-l { .bg--dim-l {
@include usevar(background-color, dim-background); @include usevar(background-color, dim-background);

View File

@ -4,13 +4,13 @@
<script src="{{ static "js/carousel.js" }}"></script> <script src="{{ static "js/carousel.js" }}"></script>
{{ end }} {{ end }}
{{ define "content" }} {{ define "all_projects" }}
<div> <div>
{{ with .CarouselProjects }} {{ with .OfficialProjects }}
<div class="carousel-container project-carousel mw-100 mv2 mv3-ns margin-center dn db-ns"> <div class="carousel-container project-carousel mw-100 mv2 mv3-ns margin-center dn db-ns">
<div class="carousel pa3 h5 overflow-hidden bg--dim br2-ns"> <div class="carousel pa3 h5 overflow-hidden bg--dim br2-ns">
{{ range $index, $project := . }} {{ range $index, $project := . }}
<div class="carousel-item flex pa3 w-100 h-100 bg--dim items-center {{ if eq $index 0 }}active{{ end }}"> <div class="carousel-item flex pv3 pl3 w-100 h-100 bg--dim items-center {{ if eq $index 0 }}active{{ end }}">
<div class="flex-grow-1 pr3 relative flex flex-column h-100 justify-center"> <div class="flex-grow-1 pr3 relative flex flex-column h-100 justify-center">
<a href="{{ $project.Url }}"> <a href="{{ $project.Url }}">
<h3>{{ $project.Name }}</h3> <h3>{{ $project.Name }}</h3>
@ -31,42 +31,55 @@
<div class="carousel-buttons pv2"></div> <div class="carousel-buttons pv2"></div>
</div> </div>
{{ end }} {{ end }}
<div class="flex flex-column flex-row-l mv3 items-start"> <div class="flex flex-column g3">
<div class="bg--dim-ns br2"> {{ if .OfficialProjects }}
<div class="clear"></div> <div class="ph3 pt3 bg--dim br2 flex flex-column">
<div class="optionbar pv2 ph3"> <h2>Official Projects</h2>
<div class="options"> <p>These projects are boop boop hahahaa:</p>
<a href="{{ .ProjectAtomFeedUrl }}"><span class="icon big">4</span> RSS Feed &ndash; New Projects</span></a> <div class="grid grid-1 grid-2-ns g3">
</div> {{ range .OfficialProjects }}
<div class="options">
{{ template "pagination.html" .Pagination }}
</div>
</div>
<div class="projectlist ph3">
{{ range .Projects }}
<div class="mv3">
{{ template "project_card.html" projectcarddata . "" }} {{ template "project_card.html" projectcarddata . "" }}
</div>
{{ end }} {{ end }}
</div> </div>
<a href="{{ .OfficialProjectsLink }}" class="pa3 tc">See more »</a>
<div class="optionbar bottom pv2 ph3">
<div class="options order-1"></div>
<div class="options order-0 order-last-ns">{{ template "pagination.html" .Pagination }}</div>
</div> </div>
</div> {{ end }}
<div class="w-100 w-40-l ph3 ph0-ns flex-shrink-0"> {{ if .PersonalProjects }}
<div class="ml3-l mt3 mt0-l pa3 bg--dim br2"> <div class="ph3 pt3 bg--dim br2 flex flex-column">
<h2>Personal Projects</h2> <h2>Personal Projects</h2>
<p>Many community members have projects of their own that are currently works in progress. Here's a few:</p> <p>Many community members have projects of their own. Here's a few:</p>
<div class="grid grid-1 grid-2-ns g3">
{{ range .PersonalProjects }} {{ range .PersonalProjects }}
<div class="mv3">
{{ template "project_card.html" projectcarddata . "" }} {{ template "project_card.html" projectcarddata . "" }}
</div>
{{ end }} {{ end }}
</div> </div>
<a href="{{ .PersonalProjectsLink }}" class="pa3 tc">See more »</a>
</div> </div>
{{ end }}
{{ if .CurrentJamProjects }}
<div class="ph3 pt3 bg--dim br2 flex flex-column">
<h2>JAM! NOWS!</h2>
<p>wowowowow</p>
<div class="grid grid-1 grid-2-ns g3">
{{ range .CurrentJamProjects }}
{{ template "project_card.html" projectcarddata . "" }}
{{ end }}
</div>
<a href="{{ .CurrentJamLink }}" class="pa3 tc">See more »</a>
</div>
{{ end }}
{{ if .PreviousJamProjects }}
<div class="ph3 pt3 bg--dim br2 flex flex-column">
<h2>JAM! THENS!</h2>
<p>nonononoono</p>
<div class="grid grid-1 grid-2-ns g3">
{{ range .PreviousJamProjects }}
{{ template "project_card.html" projectcarddata . "" }}
{{ end }}
</div>
<a href="{{ .PreviousJamLink }}" class="pa3 tc">See more »</a>
</div>
{{ end }}
</div> </div>
</div> </div>
@ -76,3 +89,34 @@
}); });
</script> </script>
{{ end }} {{ end }}
{{ define "single_category" }}
<div class="bg--dim-ns br2">
<div class="clear"></div>
<div class="optionbar pv2 ph3">
<div class="options"></div>
<div class="options">
{{ template "pagination.html" .Pagination }}
</div>
</div>
<div class="projectlist pa3 grid grid-1 grid-2-ns g3">
{{ range .PageProjects }}
{{ template "project_card.html" projectcarddata . "" }}
{{ end }}
</div>
<div class="optionbar bottom pv2 ph3">
<div class="options order-1"></div>
<div class="options order-0 order-last-ns">{{ template "pagination.html" .Pagination }}</div>
</div>
</div>
{{ end }}
{{ define "content" }}
{{ if .AllProjects }}
{{ template "all_projects" . }}
{{ else }}
{{ template "single_category" . }}
{{ end }}
{{ end }}

View File

@ -72,7 +72,7 @@ func getBaseData(c *RequestContext, title string, breadcrumbs []templates.Breadc
LoginWithDiscordUrl: hmnurl.BuildLoginWithDiscord(c.FullUrl()), LoginWithDiscordUrl: hmnurl.BuildLoginWithDiscord(c.FullUrl()),
HMNHomepageUrl: hmnurl.BuildHomepage(), HMNHomepageUrl: hmnurl.BuildHomepage(),
ProjectIndexUrl: hmnurl.BuildProjectIndex(1), ProjectIndexUrl: hmnurl.BuildProjectIndex(1, ""),
PodcastUrl: hmnurl.BuildPodcast(), PodcastUrl: hmnurl.BuildPodcast(),
FishbowlUrl: hmnurl.BuildFishbowlIndex(), FishbowlUrl: hmnurl.BuildFishbowlIndex(),
ForumsUrl: hmnurl.HMNProjectContext.BuildForum(nil, 1), ForumsUrl: hmnurl.HMNProjectContext.BuildForum(nil, 1),
@ -85,7 +85,7 @@ func getBaseData(c *RequestContext, title string, breadcrumbs []templates.Breadc
AboutUrl: hmnurl.BuildAbout(), AboutUrl: hmnurl.BuildAbout(),
ManifestoUrl: hmnurl.BuildManifesto(), ManifestoUrl: hmnurl.BuildManifesto(),
CommunicationGuidelinesUrl: hmnurl.BuildCommunicationGuidelines(), CommunicationGuidelinesUrl: hmnurl.BuildCommunicationGuidelines(),
ProjectIndexUrl: hmnurl.BuildProjectIndex(1), ProjectIndexUrl: hmnurl.BuildProjectIndex(1, ""),
RolesUrl: hmnurl.BuildStaffRolesIndex(), RolesUrl: hmnurl.BuildStaffRolesIndex(),
ContactUrl: hmnurl.BuildContactPage(), ContactUrl: hmnurl.BuildContactPage(),
SearchActionUrl: "https://duckduckgo.com", SearchActionUrl: "https://duckduckgo.com",

View File

@ -149,7 +149,7 @@ func AtomFeed(c *RequestContext) ResponseData {
feedData.FeedType = FeedTypeProjects feedData.FeedType = FeedTypeProjects
feedData.FeedID = FeedIDProjects feedData.FeedID = FeedIDProjects
feedData.AtomFeedUrl = hmnurl.BuildAtomFeedForProjects() feedData.AtomFeedUrl = hmnurl.BuildAtomFeedForProjects()
feedData.FeedUrl = hmnurl.BuildProjectIndex(1) feedData.FeedUrl = hmnurl.BuildProjectIndex(1, "")
c.Perf.StartBlock("SQL", "Fetching projects") c.Perf.StartBlock("SQL", "Fetching projects")
_, hasAll := c.Req.URL.Query()["all"] _, hasAll := c.Req.URL.Query()["all"]

View File

@ -6,7 +6,6 @@ import (
"fmt" "fmt"
"image" "image"
"io" "io"
"math"
"math/rand" "math/rand"
"net/http" "net/http"
"path" "path"
@ -29,7 +28,7 @@ import (
"github.com/teacat/noire" "github.com/teacat/noire"
) )
const maxPersonalProjects = 5 const maxPersonalProjects = 10
const maxProjectOwners = 5 const maxProjectOwners = 5
func ProjectCSS(c *RequestContext) ResponseData { func ProjectCSS(c *RequestContext) ResponseData {
@ -75,41 +74,143 @@ func ProjectCSS(c *RequestContext) ResponseData {
type ProjectTemplateData struct { type ProjectTemplateData struct {
templates.BaseData templates.BaseData
Pagination templates.Pagination AllProjects bool
CarouselProjects []templates.Project
Projects []templates.Project
PersonalProjects []templates.Project
ProjectAtomFeedUrl string // Stuff for all projects
WIPForumUrl string OfficialProjects []templates.Project
OfficialProjectsLink string
PersonalProjects []templates.Project
PersonalProjectsLink string
CurrentJamProjects []templates.Project
CurrentJamLink string
PreviousJamProjects []templates.Project
PreviousJamLink string
// Stuff for pages of projects only
Pagination templates.Pagination
PageProjects []templates.Project
} }
func ProjectIndex(c *RequestContext) ResponseData { func ProjectIndex(c *RequestContext) ResponseData {
const projectsPerPage = 20 cat := c.PathParams["category"]
const maxCarouselProjects = 10 pageStr := c.PathParams["page"]
const maxPersonalProjects = 10
officialProjects, err := hmndata.FetchProjects(c, c.Conn, c.CurrentUser, hmndata.ProjectsQuery{ currentJam := hmndata.CurrentJam()
Types: hmndata.OfficialProjects, previousJam := hmndata.PreviousJam()
})
if cat == "" && pageStr == "" {
const projectsPerSection = 8
officialProjects, err := getShuffledOfficialProjects(c)
if err != nil { if err != nil {
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch projects")) return c.ErrorResponse(http.StatusInternalServerError, err)
} }
numPages := int(math.Ceil(float64(len(officialProjects)) / projectsPerPage)) personalProjects, err := getPersonalProjects(c, "")
page, numPages, ok := getPageInfo(c.PathParams["page"], len(officialProjects), feedPostsPerPage) if err != nil {
return c.ErrorResponse(http.StatusInternalServerError, err)
}
var currentJamProjects []templates.Project
if currentJam != nil {
var err error
currentJamProjects, err = getPersonalProjects(c, currentJam.Slug)
if err != nil {
return c.ErrorResponse(http.StatusInternalServerError, err)
}
}
previousJamProjects, err := getPersonalProjects(c, previousJam.Slug)
if err != nil {
return c.ErrorResponse(http.StatusInternalServerError, err)
}
baseData := getBaseDataAutocrumb(c, "Projects")
tmpl := ProjectTemplateData{
BaseData: baseData,
AllProjects: true,
OfficialProjects: officialProjects[:utils.IntMin(len(officialProjects), projectsPerSection)],
OfficialProjectsLink: hmnurl.BuildProjectIndex(1, "official"),
PersonalProjects: personalProjects[:utils.IntMin(len(personalProjects), projectsPerSection)],
PersonalProjectsLink: hmnurl.BuildProjectIndex(1, "personal"),
CurrentJamProjects: currentJamProjects[:utils.IntMin(len(currentJamProjects), projectsPerSection)],
// CurrentJamLink set later
PreviousJamProjects: previousJamProjects[:utils.IntMin(len(previousJamProjects), projectsPerSection)],
PreviousJamLink: hmnurl.BuildJamIndexAny(previousJam.UrlSlug),
}
if hmndata.CurrentJam() != nil {
tmpl.CurrentJamLink = hmnurl.BuildJamIndexAny(hmndata.CurrentJam().UrlSlug)
}
var res ResponseData
res.MustWriteTemplate("project_index.html", tmpl, c.Perf)
return res
} else {
const projectsPerPage = 20
var projects []templates.Project
var err error
switch cat {
case hmndata.WRJ2022.UrlSlug:
projects, err = getPersonalProjects(c, hmndata.WRJ2022.Slug)
case hmndata.VJ2023.Slug:
projects, err = getPersonalProjects(c, hmndata.VJ2023.Slug)
case hmndata.WRJ2023.Slug:
projects, err = getPersonalProjects(c, hmndata.WRJ2023.Slug)
case "personal":
projects, err = getPersonalProjects(c, "")
case "official":
projects, err = getShuffledOfficialProjects(c)
default:
return c.Redirect(hmnurl.BuildProjectIndex(1, ""), http.StatusSeeOther)
}
if err != nil {
return c.ErrorResponse(http.StatusInternalServerError, err)
}
page, numPages, ok := getPageInfo(pageStr, len(projects), projectsPerPage)
if !ok { if !ok {
return c.Redirect(hmnurl.BuildProjectIndex(1), http.StatusSeeOther) return c.Redirect(hmnurl.BuildProjectIndex(1, cat), http.StatusSeeOther)
} }
pagination := templates.Pagination{ pagination := templates.Pagination{
Current: page, Current: page,
Total: numPages, Total: numPages,
FirstUrl: hmnurl.BuildProjectIndex(1), FirstUrl: hmnurl.BuildProjectIndex(1, cat),
LastUrl: hmnurl.BuildProjectIndex(numPages), LastUrl: hmnurl.BuildProjectIndex(numPages, cat),
NextUrl: hmnurl.BuildProjectIndex(utils.IntClamp(1, page+1, numPages)), NextUrl: hmnurl.BuildProjectIndex(utils.IntClamp(1, page+1, numPages), cat),
PreviousUrl: hmnurl.BuildProjectIndex(utils.IntClamp(1, page-1, numPages)), PreviousUrl: hmnurl.BuildProjectIndex(utils.IntClamp(1, page-1, numPages), cat),
}
firstProjectIndex := (page - 1) * projectsPerPage
endIndex := utils.IntMin(firstProjectIndex+projectsPerPage, len(projects))
pageProjects := projects[firstProjectIndex:endIndex]
baseData := getBaseData(c, "Projects", []templates.Breadcrumb{
{"Projects", hmnurl.BuildProjectIndex(1, "")},
})
var res ResponseData
res.MustWriteTemplate("project_index.html", ProjectTemplateData{
BaseData: baseData,
AllProjects: false,
Pagination: pagination,
PageProjects: pageProjects,
}, c.Perf)
return res
}
}
func getShuffledOfficialProjects(c *RequestContext) ([]templates.Project, error) {
official, err := hmndata.FetchProjects(c, c.Conn, c.CurrentUser, hmndata.ProjectsQuery{
Types: hmndata.OfficialProjects,
})
if err != nil {
return nil, oops.New(err, "failed to fetch projects")
} }
c.Perf.StartBlock("PROJECTS", "Grouping and sorting") c.Perf.StartBlock("PROJECTS", "Grouping and sorting")
@ -118,7 +219,7 @@ func ProjectIndex(c *RequestContext) ResponseData {
var recentProjects []templates.Project var recentProjects []templates.Project
var restProjects []templates.Project var restProjects []templates.Project
now := time.Now() now := time.Now()
for _, p := range officialProjects { for _, p := range official {
templateProject := templates.ProjectAndStuffToTemplate(&p, hmndata.UrlContextForProject(&p.Project).BuildHomepage(), c.Theme) templateProject := templates.ProjectAndStuffToTemplate(&p, hmndata.UrlContextForProject(&p.Project).BuildHomepage(), c.Theme)
if p.Project.Slug == "hero" { if p.Project.Slug == "hero" {
@ -146,29 +247,28 @@ func ProjectIndex(c *RequestContext) ResponseData {
featuredProjects = append([]templates.Project{*handmadeHero}, featuredProjects...) featuredProjects = append([]templates.Project{*handmadeHero}, featuredProjects...)
} }
orderedProjects := make([]templates.Project, 0, len(featuredProjects)+len(recentProjects)+len(restProjects)) officialProjects := make([]templates.Project, 0, len(featuredProjects)+len(recentProjects)+len(restProjects))
orderedProjects = append(orderedProjects, featuredProjects...) officialProjects = append(officialProjects, featuredProjects...)
orderedProjects = append(orderedProjects, recentProjects...) officialProjects = append(officialProjects, recentProjects...)
orderedProjects = append(orderedProjects, restProjects...) officialProjects = append(officialProjects, restProjects...)
firstProjectIndex := (page - 1) * projectsPerPage
endIndex := utils.IntMin(firstProjectIndex+projectsPerPage, len(orderedProjects))
pageProjects := orderedProjects[firstProjectIndex:endIndex]
var carouselProjects []templates.Project
if page == 1 {
carouselProjects = featuredProjects[:utils.IntMin(len(featuredProjects), maxCarouselProjects)]
}
c.Perf.EndBlock() c.Perf.EndBlock()
// Fetch and highlight a random selection of personal projects return officialProjects, nil
var personalProjects []templates.Project }
{
func getPersonalProjects(c *RequestContext, jamSlug string) ([]templates.Project, error) {
var slugs []string
if jamSlug != "" {
slugs = []string{jamSlug}
}
projects, err := hmndata.FetchProjects(c, c.Conn, c.CurrentUser, hmndata.ProjectsQuery{ projects, err := hmndata.FetchProjects(c, c.Conn, c.CurrentUser, hmndata.ProjectsQuery{
Types: hmndata.PersonalProjects, Types: hmndata.PersonalProjects,
JamSlugs: slugs,
}) })
if err != nil { if err != nil {
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch personal projects")) return nil, oops.New(err, "failed to fetch personal projects")
} }
sort.Slice(projects, func(i, j int) bool { sort.Slice(projects, func(i, j int) bool {
@ -177,29 +277,28 @@ func ProjectIndex(c *RequestContext) ResponseData {
return p2.AllLastUpdated.Before(p1.AllLastUpdated) // sort backwards - recent first return p2.AllLastUpdated.Before(p1.AllLastUpdated) // sort backwards - recent first
}) })
for i, p := range projects { var personalProjects []templates.Project
if i >= maxPersonalProjects { for _, p := range projects {
break
}
templateProject := templates.ProjectAndStuffToTemplate(&p, hmndata.UrlContextForProject(&p.Project).BuildHomepage(), c.Theme) templateProject := templates.ProjectAndStuffToTemplate(&p, hmndata.UrlContextForProject(&p.Project).BuildHomepage(), c.Theme)
personalProjects = append(personalProjects, templateProject) personalProjects = append(personalProjects, templateProject)
} }
return personalProjects, nil
} }
baseData := getBaseDataAutocrumb(c, "Projects") func jamLink(jamSlug string) string {
var res ResponseData switch jamSlug {
res.MustWriteTemplate("project_index.html", ProjectTemplateData{ case hmndata.WRJ2021.Slug:
BaseData: baseData, return hmnurl.BuildJamIndex2021()
case hmndata.WRJ2022.Slug:
Pagination: pagination, return hmnurl.BuildJamIndex2022()
CarouselProjects: carouselProjects, case hmndata.WRJ2023.Slug:
Projects: pageProjects, return hmnurl.BuildJamIndex2023()
PersonalProjects: personalProjects, case hmndata.VJ2023.Slug:
return hmnurl.BuildJamIndex2023_Visibility()
ProjectAtomFeedUrl: hmnurl.BuildAtomFeedForProjects(), default:
WIPForumUrl: hmnurl.HMNProjectContext.BuildForum([]string{"wip"}, 1), return ""
}, c.Perf) }
return res
} }
type ProjectHomepageData struct { type ProjectHomepageData struct {