Add back project nav

This commit is contained in:
Ben Visness 2021-10-27 20:35:53 -05:00
parent 5eff3c38b4
commit 4e47c51fa1
11 changed files with 315 additions and 215 deletions

View File

@ -8726,8 +8726,22 @@ header .hmn-logo {
justify-content: center;
color: white !important; }
@media screen and (min-width: 30em) {
header .hmn-logo {
header .hmn-logo.big {
width: 11.25rem; } }
@media screen and (min-width: 30em) {
header .hmn-logo.small {
width: 3.75rem;
padding: 0.8rem;
text-align: justify;
text-justify: inter-character;
flex-direction: column;
font-size: 1rem;
line-height: 1em;
align-items: stretch; }
header .hmn-logo.small div::after {
content: '';
display: inline-block;
width: 100%; } }
header .items {
position: relative; }
@ -9324,51 +9338,54 @@ span.icon-rss::before {
width: auto;
max-height: calc(100vh - 2rem); } }
.carousel-container {
width: 50rem; }
.carousel-container .carousel {
box-sizing: content-box;
position: relative; }
.carousel-container .carousel-item {
position: absolute;
top: 0;
left: 0; }
.carousel-container .carousel-item:not(.active) {
display: none; }
.carousel-container .carousel-item br {
line-height: 0.6em; }
.carousel-container .carousel-description {
max-height: 14rem;
overflow: hidden; }
.carousel-container .carousel-fade {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 30px;
background: linear-gradient( rgba(240, 240, 240, 0) , #f0f0f0 );
background: linear-gradient( var(--dim-background-transparent) , var(--dim-background) ); }
.carousel-container .carousel-item-small {
position: absolute;
top: 0;
left: 0; }
.carousel-container .carousel-item-small:not(.active) {
display: none; }
.carousel-container .carousel-button {
border: 1px solid;
border-color: #999;
border-color: var(--dimmer-color);
cursor: pointer;
transition: all 100ms ease-in-out; }
.carousel-container .carousel-button:hover {
background-color: #bbb;
background-color: var(--dimmest-color); }
.carousel-container .carousel-button.active {
border-color: #666;
border-color: var(--theme-color); }
.carousel-container .carousel-button.active:hover {
background-color: #ccc;
background-color: var(--theme-color-dimmest); }
.carousel-container .carousel {
box-sizing: content-box;
position: relative; }
.carousel-container .carousel-item {
position: absolute;
top: 0;
left: 0; }
.carousel-container .carousel-item:not(.active) {
display: none; }
.carousel-container .carousel-item br {
line-height: 0.6em; }
.carousel-container .carousel-description {
max-height: 14rem;
overflow: hidden; }
.carousel-container .carousel-fade {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 30px;
background: linear-gradient( rgba(240, 240, 240, 0) , #f0f0f0 );
background: linear-gradient( var(--dim-background-transparent) , var(--dim-background) ); }
.carousel-container .carousel-item-small {
position: absolute;
top: 0;
left: 0; }
.carousel-container .carousel-item-small:not(.active) {
display: none; }
.carousel-container .carousel-button {
border: 1px solid;
border-color: #999;
border-color: var(--dimmer-color);
cursor: pointer;
transition: all 100ms ease-in-out; }
.carousel-container .carousel-button:hover {
background-color: #bbb;
background-color: var(--dimmest-color); }
.carousel-container .carousel-button.active {
border-color: #666;
border-color: var(--theme-color); }
.carousel-container .carousel-button.active:hover {
background-color: #ccc;
background-color: var(--theme-color-dimmest); }
.notice {
color: #fff;

View File

@ -1,6 +1,4 @@
.carousel-container {
width: 50rem;
.carousel {
box-sizing: content-box;
position: relative;

View File

@ -12,9 +12,30 @@ header {
align-items: center;
justify-content: center;
color: white !important;
@media #{$breakpoint-not-small} {
width: px2rem(180px);
&.big {
@media #{$breakpoint-not-small} {
width: px2rem(180px);
}
}
&.small {
@media #{$breakpoint-not-small} {
width: $logo-height;
padding: 0.8rem;
text-align: justify;
text-justify: inter-character;
flex-direction: column;
font-size: 1rem;
line-height: 1em;
align-items: stretch;
div::after {
content: '';
display: inline-block;
width: 100%;
}
}
}
}

View File

@ -26,35 +26,65 @@
{{ end }}
</div>
<div class="menu-bar flex flex-column flex-row-ns justify-between {{ if .IsProjectPage }}project{{ end }}">
<div class="flex flex-column flex-row-ns">
<a href="{{ .Header.HMNHomepageUrl }}" class="hmn-logo bg-theme-dark">
Handmade
</a>
<div class="items flex items-center justify-center justify-start-ns ml2-ns ml3-l">
<div class="root-item">
<a href="{{ .Header.ProjectIndexUrl }}">Projects</a>
</div>
<div class="root-item">
<a>Media <div class="dib svgicon ml1">{{ svg "chevron-down-thick" }}</div></a>
<div class="submenu b--theme-dark">
<a href="{{ .Header.PodcastUrl }}">Podcast</a>
<a href="https://handmadedev.show/" target="_blank">Handmade Dev Show</a>
<div class="flex flex-column flex-row-ns items-center">
{{ $itemsClass := "items flex items-center justify-center justify-start-ns ml2-ns ml3-l" }}
{{ if .Header.Project }}
<a href="{{ .Header.HMNHomepageUrl }}" class="hmn-logo small bg-theme-dark">
<div>Hand</div>
<div>made</div>
</a>
<a href="{{ .Project.Url }}">
<h2 class="mb0 mt2 mt0-ns ml3-ns">{{ .Project.Name }}</h2>
</a>
{{ with .Header.Project }}
<div class="{{ $itemsClass }}">
{{ if .HasBlog }}
<div class="root-item">
<a href="{{ .BlogUrl }}">Blog</a>
</div>
{{ end }}
{{ if .HasForums }}
<div class="root-item">
<a href="{{ .ForumsUrl }}">Forums</a>
</div>
{{ end }}
{{ if .HasEpisodeGuide }}
<div class="root-item">
<a href="{{ .EpisodeGuideUrl }}">Episode Guide</a>
</div>
{{ end }}
</div>
{{ end }}
{{ else }}
<a href="{{ .Header.HMNHomepageUrl }}" class="hmn-logo big bg-theme-dark">
Handmade
</a>
<div class="{{ $itemsClass }}">
<div class="root-item">
<a href="{{ .Header.ProjectIndexUrl }}">Projects</a>
</div>
<div class="root-item">
<a>Media <div class="dib svgicon ml1">{{ svg "chevron-down-thick" }}</div></a>
<div class="submenu b--theme-dark">
<a href="{{ .Header.PodcastUrl }}">Podcast</a>
<a href="https://handmadedev.show/" target="_blank">Handmade Dev Show</a>
</div>
</div>
<div class="root-item">
<a href="{{ .Header.ForumsUrl }}">Forums</a>
</div>
<div class="root-item">
<a>Resources <div class="dib svgicon ml1">{{ svg "chevron-down-thick" }}</div></a>
<div class="submenu b--theme-dark">
<a href="{{ .Header.LibraryUrl }}">Library</a>
</div>
</div>
</div>
<div class="root-item">
<a href="{{ .Header.ForumsUrl }}">Forums</a>
</div>
<div class="root-item">
<a>Resources <div class="dib svgicon ml1">{{ svg "chevron-down-thick" }}</div></a>
<div class="submenu b--theme-dark">
<a href="{{ .Header.LibraryUrl }}">Library</a>
</div>
</div>
</div>
{{ end }}
</div>
<div class="dn flex-ns items-center f3">
<a class="svgicon" href="https://twitter.com/handmade_net/" target="_blank">{{ svg "twitter" }}</a>
<a class="svgicon ml2" href="{{ .DiscordUrl }}" target="_blank">{{ svg "discord" }}</a>
<a class="svgicon ml2" href="https://discord.gg/hxWxDee" target="_blank">{{ svg "discord" }}</a>
</div>
</div>
</header>

View File

@ -207,7 +207,7 @@
<h2>Community Showcase</h2>
<div class="bg--card pa3 br3">
<div class="mb3">
This is a selection of recent work done by community members. Want to participate? <a href="{{ .DiscordUrl }}" target="_blank">Join us on Discord.</a>
This is a selection of recent work done by community members. Want to participate? <a href="https://discord.gg/hxWxDee" target="_blank">Join us on Discord.</a>
</div>
<div id="showcase-container"></div>
<div>

View File

@ -7,10 +7,32 @@
{{ end }}
{{ define "content" }}
<div class="flex flex-column flex-row-l">
<div class="flex-grow-1 overflow-hidden">
<div class="flex flex-column flex-row-ns">
<div class="sidebar flex-shrink-0 mw5 self-center self-start-ns mh3 ml0-ns overflow-hidden">
<img alt="{{ .Project.Name }}" class="br3" src="{{ .Project.Logo }}" />
<a href="{{ .CurrentProjectUrl }}">
<h2 class="mt3">{{ .Project.Name }}</h2>
</a>
<div class="mt3">
<div class="mb3">
{{ range $i, $owner := .Owners }}
<div class="flex mv3 items-center {{ if eq $i 0 }}mt2{{ end }}">
<img class="avatar-icon mr2" src="{{ $owner.AvatarUrl }}" />
<a class="user-link" href="{{ $owner.ProfileUrl }}">{{ $owner.Name }}</a>
</div>
{{ end }}
</div>
{{ range .ProjectLinks }}
<div class="pair flex">
<div class="key flex-auto flex-shrink-0 mr2">{{ .Name }}</div>
<div class="value projectlink truncate"><a class="external" href="{{ .Url }}" ><span class="icon-{{ .Icon }}"></span> {{ .LinkText }}</a></div>
</div>
{{ end }}
</div>
</div>
<div class="flex-grow-1 overflow-hidden">
{{ with .Screenshots }}
<div class="carousel-container mw-100 mv2 mv3-ns margin-center">
<div class="carousel-container mw-100 mb3">
<div class="carousel aspect-ratio aspect-ratio--16x9 overflow-hidden bg--dim br2-ns">
<div class="dn db-l">
{{ range $index, $screenshot := . }}
@ -48,27 +70,6 @@
</div>
{{ end }}
</div>
<div class="sidebar flex-shrink-0 mw6 w-30-l self-center self-start-l mh3 mh0-ns ml3-l overflow-hidden">
<div class="content-block">
<img alt="{{ .Project.Name }} Logo" class="br3" src="{{ .Project.Logo }}" />
<div class="mv3 relative">
<div class="mb3">
{{ range $i, $owner := .Owners }}
<div class="flex mv3 items-center {{ if eq $i 0 }}mt2{{ end }}">
<img class="avatar-icon mr2" src="{{ $owner.AvatarUrl }}" />
<a class="user-link" href="{{ $owner.ProfileUrl }}">{{ $owner.Name }}</a>
</div>
{{ end }}
</div>
{{ range .ProjectLinks }}
<div class="pair flex flex-wrap">
<div class="key flex-auto mr1">{{ .Name }}</div>
<div class="value projectlink"><a class="external" href="{{ .Url }}" ><span class="icon-{{ .Icon }}"></span> {{ .LinkText }}</a></div>
</div>
{{ end }}
</div>
</div>
</div>
</div>
<script>
const numCarouselItems = {{ len .Screenshots }};
@ -89,6 +90,10 @@
let carouselTimerCurrent = 0;
const carouselTimer = setInterval(() => {
if (numCarouselItems === 0) {
return;
}
const next = (carouselTimerCurrent + 1) % numCarouselItems;
activateCarouselItem(next);
carouselTimerCurrent = next;

View File

@ -123,6 +123,10 @@
let carouselTimerCurrent = 0;
const carouselTimer = setInterval(() => {
if (numCarouselItems === 0) {
return;
}
const next = (carouselTimerCurrent + 1) % numCarouselItems;
activateCarousel(next);
carouselTimerCurrent = next;

View File

@ -16,9 +16,10 @@ type BaseData struct {
Notices []Notice
ReportIssueMailto string
CurrentUrl string
LoginPageUrl string
ProjectCSSUrl string
CurrentUrl string
CurrentProjectUrl string
LoginPageUrl string
ProjectCSSUrl string
Project Project
User *User
@ -50,6 +51,17 @@ type Header struct {
PodcastUrl string
ForumsUrl string
LibraryUrl string
Project *ProjectHeader
}
type ProjectHeader struct {
HasForums bool
HasBlog bool
HasEpisodeGuide bool
ForumsUrl string
BlogUrl string
EpisodeGuideUrl string
}
type Footer struct {

125
src/website/base_data.go Normal file
View File

@ -0,0 +1,125 @@
package website
import (
"git.handmade.network/hmn/hmn/src/config"
"git.handmade.network/hmn/hmn/src/hmnurl"
"git.handmade.network/hmn/hmn/src/models"
"git.handmade.network/hmn/hmn/src/templates"
)
func getBaseDataAutocrumb(c *RequestContext, title string) templates.BaseData {
return getBaseData(c, title, []templates.Breadcrumb{{Name: title, Url: ""}})
}
// NOTE(asaf): If you set breadcrumbs, the breadcrumb for the current project will automatically be prepended when necessary.
// If you pass nil, no breadcrumbs will be created.
func getBaseData(c *RequestContext, title string, breadcrumbs []templates.Breadcrumb) templates.BaseData {
var templateUser *templates.User
var templateSession *templates.Session
if c.CurrentUser != nil {
u := templates.UserToTemplate(c.CurrentUser, c.Theme)
s := templates.SessionToTemplate(c.CurrentSession)
templateUser = &u
templateSession = &s
}
notices := getNoticesFromCookie(c)
if len(breadcrumbs) > 0 {
projectUrl := hmnurl.BuildProjectHomepage(c.CurrentProject.Slug)
if breadcrumbs[0].Url != projectUrl {
rootBreadcrumb := templates.Breadcrumb{
Name: c.CurrentProject.Name,
Url: projectUrl,
}
breadcrumbs = append([]templates.Breadcrumb{rootBreadcrumb}, breadcrumbs...)
}
}
baseData := templates.BaseData{
Theme: c.Theme,
Title: title,
Breadcrumbs: breadcrumbs,
CurrentUrl: c.FullUrl(),
CurrentProjectUrl: hmnurl.BuildProjectHomepage(c.CurrentProject.Slug),
LoginPageUrl: hmnurl.BuildLoginPage(c.FullUrl()),
ProjectCSSUrl: hmnurl.BuildProjectCSS(c.CurrentProject.Color1),
Project: templates.ProjectToTemplate(c.CurrentProject, c.Theme),
User: templateUser,
Session: templateSession,
Notices: notices,
ReportIssueMailto: "team@handmade.network",
OpenGraphItems: buildDefaultOpenGraphItems(c.CurrentProject, title),
IsProjectPage: !c.CurrentProject.IsHMN(),
Header: templates.Header{
AdminUrl: hmnurl.BuildAdminApprovalQueue(), // TODO(asaf): Replace with general-purpose admin page
UserSettingsUrl: hmnurl.BuildUserSettings(""),
LoginActionUrl: hmnurl.BuildLoginAction(c.FullUrl()),
LogoutActionUrl: hmnurl.BuildLogoutAction(c.FullUrl()),
ForgotPasswordUrl: hmnurl.BuildRequestPasswordReset(),
RegisterUrl: hmnurl.BuildRegister(),
HMNHomepageUrl: hmnurl.BuildHomepage(),
ProjectIndexUrl: hmnurl.BuildProjectIndex(1),
PodcastUrl: hmnurl.BuildPodcast(),
ForumsUrl: hmnurl.BuildForum(models.HMNProjectSlug, nil, 1),
LibraryUrl: hmnurl.BuildLibrary(),
},
Footer: templates.Footer{
HomepageUrl: hmnurl.BuildHomepage(),
AboutUrl: hmnurl.BuildAbout(),
ManifestoUrl: hmnurl.BuildManifesto(),
CodeOfConductUrl: hmnurl.BuildCodeOfConduct(),
CommunicationGuidelinesUrl: hmnurl.BuildCommunicationGuidelines(),
ProjectIndexUrl: hmnurl.BuildProjectIndex(1),
ForumsUrl: hmnurl.BuildForum(models.HMNProjectSlug, nil, 1),
ContactUrl: hmnurl.BuildContactPage(),
},
}
if c.CurrentUser != nil {
baseData.Header.UserProfileUrl = hmnurl.BuildUserProfile(c.CurrentUser.Username)
}
if !c.CurrentProject.IsHMN() {
episodeGuideUrl := ""
defaultTopic, hasAnnotations := config.Config.EpisodeGuide.Projects[c.CurrentProject.Slug]
if hasAnnotations {
episodeGuideUrl = hmnurl.BuildEpisodeList(c.CurrentProject.Slug, defaultTopic)
}
baseData.Header.Project = &templates.ProjectHeader{
HasForums: c.CurrentProject.ForumEnabled,
HasBlog: c.CurrentProject.BlogEnabled,
HasEpisodeGuide: hasAnnotations,
ForumsUrl: hmnurl.BuildForum(c.CurrentProject.Slug, nil, 1),
BlogUrl: hmnurl.BuildBlog(c.CurrentProject.Slug, 1),
EpisodeGuideUrl: episodeGuideUrl,
}
}
return baseData
}
func buildDefaultOpenGraphItems(project *models.Project, title string) []templates.OpenGraphItem {
if title == "" {
title = "Handmade Network"
}
image := hmnurl.BuildPublic("logo.png", false)
if !project.IsHMN() {
image = hmnurl.BuildUserFile(project.LogoLight)
}
return []templates.OpenGraphItem{
{Property: "og:title", Value: title},
{Property: "og:site_name", Value: "Handmade Network"},
{Property: "og:type", Value: "website"},
{Property: "og:image", Value: image},
}
}

View File

@ -25,9 +25,6 @@ type LandingTemplateData struct {
FeedUrl string
PodcastUrl string
StreamsUrl string
IRCUrl string
DiscordUrl string
ShowUrl string
ShowcaseUrl string
AtomFeedUrl string
MarkAllReadUrl string
@ -168,9 +165,6 @@ func Index(c *RequestContext) ResponseData {
FeedUrl: hmnurl.BuildFeed(),
PodcastUrl: hmnurl.BuildPodcast(),
StreamsUrl: hmnurl.BuildStreams(),
IRCUrl: hmnurl.BuildBlogThread(models.HMNProjectSlug, 1138, "[Tutorial] Handmade Network IRC"),
DiscordUrl: "https://discord.gg/hxWxDee",
ShowUrl: "https://handmadedev.show/",
ShowcaseUrl: hmnurl.BuildShowcase(),
AtomFeedUrl: hmnurl.BuildAtomFeed(),
MarkAllReadUrl: hmnurl.BuildForumMarkRead(models.HMNProjectSlug, 0),

View File

@ -277,112 +277,6 @@ func NewWebsiteRoutes(longRequestContext context.Context, conn *pgxpool.Pool, pe
return router
}
func getBaseDataAutocrumb(c *RequestContext, title string) templates.BaseData {
return getBaseData(c, title, []templates.Breadcrumb{{Name: title, Url: ""}})
}
// NOTE(asaf): If you set breadcrumbs, the breadcrumb for the current project will automatically be prepended when necessary.
// If you pass nil, no breadcrumbs will be created.
func getBaseData(c *RequestContext, title string, breadcrumbs []templates.Breadcrumb) templates.BaseData {
var templateUser *templates.User
var templateSession *templates.Session
if c.CurrentUser != nil {
u := templates.UserToTemplate(c.CurrentUser, c.Theme)
s := templates.SessionToTemplate(c.CurrentSession)
templateUser = &u
templateSession = &s
}
notices := getNoticesFromCookie(c)
if len(breadcrumbs) > 0 {
projectUrl := hmnurl.BuildProjectHomepage(c.CurrentProject.Slug)
if breadcrumbs[0].Url != projectUrl {
rootBreadcrumb := templates.Breadcrumb{
Name: c.CurrentProject.Name,
Url: projectUrl,
}
breadcrumbs = append([]templates.Breadcrumb{rootBreadcrumb}, breadcrumbs...)
}
}
// TODO: move to project-specific navigation
// episodeGuideUrl := ""
// defaultTopic, hasAnnotations := config.Config.EpisodeGuide.Projects[c.CurrentProject.Slug]
// if hasAnnotations {
// episodeGuideUrl = hmnurl.BuildEpisodeList(c.CurrentProject.Slug, defaultTopic)
// }
baseData := templates.BaseData{
Theme: c.Theme,
Title: title,
Breadcrumbs: breadcrumbs,
CurrentUrl: c.FullUrl(),
LoginPageUrl: hmnurl.BuildLoginPage(c.FullUrl()),
ProjectCSSUrl: hmnurl.BuildProjectCSS(c.CurrentProject.Color1),
Project: templates.ProjectToTemplate(c.CurrentProject, c.Theme),
User: templateUser,
Session: templateSession,
Notices: notices,
ReportIssueMailto: "team@handmade.network",
OpenGraphItems: buildDefaultOpenGraphItems(c.CurrentProject, title),
IsProjectPage: !c.CurrentProject.IsHMN(),
Header: templates.Header{
AdminUrl: hmnurl.BuildAdminApprovalQueue(), // TODO(asaf): Replace with general-purpose admin page
UserSettingsUrl: hmnurl.BuildUserSettings(""),
LoginActionUrl: hmnurl.BuildLoginAction(c.FullUrl()),
LogoutActionUrl: hmnurl.BuildLogoutAction(c.FullUrl()),
ForgotPasswordUrl: hmnurl.BuildRequestPasswordReset(),
RegisterUrl: hmnurl.BuildRegister(),
HMNHomepageUrl: hmnurl.BuildHomepage(),
ProjectIndexUrl: hmnurl.BuildProjectIndex(1),
PodcastUrl: hmnurl.BuildPodcast(),
ForumsUrl: hmnurl.BuildForum(c.CurrentProject.Slug, nil, 1),
LibraryUrl: hmnurl.BuildLibrary(),
},
Footer: templates.Footer{
HomepageUrl: hmnurl.BuildHomepage(),
AboutUrl: hmnurl.BuildAbout(),
ManifestoUrl: hmnurl.BuildManifesto(),
CodeOfConductUrl: hmnurl.BuildCodeOfConduct(),
CommunicationGuidelinesUrl: hmnurl.BuildCommunicationGuidelines(),
ProjectIndexUrl: hmnurl.BuildProjectIndex(1),
ForumsUrl: hmnurl.BuildForum(models.HMNProjectSlug, nil, 1),
ContactUrl: hmnurl.BuildContactPage(),
},
}
if c.CurrentUser != nil {
baseData.Header.UserProfileUrl = hmnurl.BuildUserProfile(c.CurrentUser.Username)
}
return baseData
}
func buildDefaultOpenGraphItems(project *models.Project, title string) []templates.OpenGraphItem {
if title == "" {
title = "Handmade Network"
}
image := hmnurl.BuildPublic("logo.png", false)
if !project.IsHMN() {
image = hmnurl.BuildUserFile(project.LogoLight)
}
return []templates.OpenGraphItem{
{Property: "og:title", Value: title},
{Property: "og:site_name", Value: "Handmade Network"},
{Property: "og:type", Value: "website"},
{Property: "og:image", Value: image},
}
}
func FetchProjectBySlug(ctx context.Context, conn *pgxpool.Pool, slug string) (*models.Project, error) {
if len(slug) > 0 && slug != models.HMNProjectSlug {
subdomainProjectRow, err := db.QueryOne(ctx, conn, models.Project{}, "SELECT $columns FROM handmade_project WHERE slug = $1", slug)