Ugly jam feed. Needs CSS work.

This commit is contained in:
Asaf Gartner 2022-06-25 16:24:04 +03:00
parent 64f94bddbb
commit 316aba12b6
5 changed files with 565 additions and 17 deletions

View File

@ -29,6 +29,7 @@ type ProjectsQuery struct {
ProjectIDs []int // if empty, all projects
Slugs []string // if empty, all projects
OwnerIDs []int // if empty, all projects
JamSlugs []string // if empty, all projects
// Ignored when using CountProjects
Limit, Offset int // if empty, no pagination
@ -101,6 +102,14 @@ func FetchProjects(
)
}
if len(q.JamSlugs) > 0 {
qb.Add(
`
JOIN jam_project ON jam_project.project_id = project.id
`,
)
}
// Filters (permissions are checked after the query, in Go)
qb.Add(`
WHERE
@ -130,6 +139,9 @@ func FetchProjects(
if len(q.Slugs) > 0 {
qb.Add(`AND (project.slug != '' AND project.slug = ANY ($?))`, q.Slugs)
}
if len(q.JamSlugs) > 0 {
qb.Add(`AND (jam_project.jam_slug = ANY ($?) AND jam_project.participating = TRUE)`, q.JamSlugs)
}
// Output
if q.Limit > 0 {

View File

@ -320,6 +320,12 @@ func BuildProjectNew() string {
return Url("/p/new", nil)
}
func BuildProjectNewJam() string {
defer CatchPanic()
return Url("/p/new", []Q{Q{Name: "jam", Value: "1"}})
}
var RegexPersonalProject = regexp.MustCompile("^/p/(?P<projectid>[0-9]+)(/(?P<projectslug>[a-zA-Z0-9-]+))?")
func BuildPersonalProject(id int, slug string) string {

View File

@ -0,0 +1,280 @@
{{/*
This is a copy-paste from base.html because we want to preserve the unique
style of this page no matter what future changes we make to the base.
*/}}
<!DOCTYPE html{{ if .OpenGraphItems }} prefix="og: http://ogp.me/ns#"{{ end }}>
<html lang="en-US">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/png" sizes="16x16" href="{{ static "wheeljam2022/favicon-16x16.png" }}">
<link rel="icon" type="image/png" sizes="32x32" href="{{ static "wheeljam2022/favicon-32x32.png" }}">
{{ if .CanonicalLink }}<link rel="canonical" href="{{ .CanonicalLink }}">{{ end }}
{{ range .OpenGraphItems }}
{{ if .Property }}
<meta property="{{ .Property }}" content="{{ .Value }}" />
{{ else }}
<meta name="{{ .Name }}" content="{{ .Value }}" />
{{ end }}
{{ end }}
{{ if .Title }}
<title>{{ .Title }} | Handmade Network</title>
{{ else }}
<title>Handmade Network</title>
{{ end }}
<meta name="theme-color" content="#346ba6">
<link rel="stylesheet" href="{{ static "fonts/mohave/stylesheet.css" }}">
<link href='https://fonts.googleapis.com/css?family=Fira+Sans:300,400,500,600' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/css?family=Fira+Mono:300,400,500,700' rel='stylesheet' type='text/css'>
<link rel="stylesheet" type="text/css" href="{{ static "style.css" }}">
<link rel="stylesheet" href="{{ statictheme .Theme "theme.css" }}" />
<style>
:root {
--content-background: #f8f8f8;
}
body {
background: linear-gradient(#346ba6, #814cb7)
}
.user-options,
header form,
header .menu-bar .wiki,
header .menu-bar .library
{
display: none !important;
}
header {
border-bottom-color: white;
margin-bottom: 0 !important;
}
.hmn-logo {
background-color: rgba(255, 255, 255, 0.1) !important;
}
header a, footer a {
color: white !important;
}
header .submenu {
background-color: #346ba6;
}
#top-container {
margin: 3rem 0;
}
#logo {
width: 16rem;
}
h1, h2, h3 {
font-family: 'MohaveHMN', sans-serif;
margin-bottom: 0;
font-weight: normal;
}
#title {
color: white;
font-size: 2.4rem;
line-height: 0.8;
margin-top: 2rem;
letter-spacing: -0.06rem;
text-transform: uppercase;
}
#dates {
font-variant: small-caps;
font-size: 1.6rem;
margin-top: 0.2rem;
}
#tagline {
font-size: 1rem;
margin-top: 1rem;
line-height: 1.4;
}
#top-container a {
color: white !important;
text-decoration: underline;
}
.actions {
margin-top: 1.5rem;
}
.actions a {
text-decoration: none !important;
line-height: 1.4;
font-weight: 500;
transition: background-color 50ms ease-in-out;
background-color:rgba(255, 255, 255, 0.1);
}
.actions a:hover {
background-color: rgba(255, 255, 255, 0.2);
}
.actions a:active {
background-color: rgba(255, 255, 255, 0.15);
}
.section {
font-size: 1rem;
line-height: 1.4;
}
.section h2 {
font-variant: small-caps;
font-size: 2.2rem;
line-height: 1.1;
}
.section h3 {
font-variant: small-caps;
font-size: 2rem;
line-height: 0.8;
margin-top: 1.4rem;
}
.section p {
margin-top: 1em;
margin-bottom: 1em;
}
.section a {
text-decoration: underline;
}
.flex-fair {
flex-basis: 1px;
flex-grow: 1;
flex-shrink: 1;
}
ul {
list-style-type: disc;
}
li {
margin-top: 0.6rem;
margin-bottom: 0.6rem;
}
.section li p {
margin-top: 0.6rem;
margin-bottom: 0.6rem;
}
footer {
border-top: 2px solid white;
margin-top: 2rem;
text-align: center;
}
footer h2 {
text-transform: uppercase;
}
.showcase-item {
background-color: rgba(0, 0, 0, 0.2);
border-color: rgba(255, 255, 255, 0.5);
}
@media screen and (min-width: 30em) {
/* not small styles */
#top-container {
margin: 3rem 0;
}
#logo {
width: 31rem;
}
#title {
font-size: 5.2rem;
}
#dates {
font-size: 2.8rem;
}
#tagline {
font-size: 1.2rem;
margin-top: 1.2rem;
}
.actions {
margin-top: 2.2rem;
}
.actions a {
font-size: 1.2rem;
}
.section h2 {
font-size: 3.4rem;
}
.section h3 {
font-size: 2.4rem;
margin-top: 1.6rem;
}
}
</style>
</head>
<body>
<div class="left white">
<div class="mt4-ns mw8 margin-center ph3-m ph4-l">
{{ template "header.html" . }}
</div>
<div id="top-container" class="flex flex-column items-center ph3">
<h1 id="title">Wheel Reinvention Jam</h1>
<h2 id="dates">August 15 - 21, 2O22</h2>
<div id="tagline" class="center">
A one-week jam to change the status quo.
</div>
</div>
<div class="bg-black-20 pt4 pb3 pb4-ns">
<div class="section mw8 margin-center ph3 ph4-l mv4">
<h2>Projects</h2>
<div class="projects">
{{ range .JamProjects }}
<div class="mv3">
{{ template "project_card.html" projectcarddata . "" }}
</div>
{{ end }}
</div>
</div>
</div>
<div class="section mw8 margin-center ph3 ph4-l">
<h2>Recent activity</h2>
<div class="timeline">
{{ range .TimelineItems }}
{{ template "timeline_item.html" . }}
{{ end }}
</div>
</div>
<div class="mw8 margin-center ph3-m ph4-l">
{{ template "footer.html" . }}
</div>
</div>
</body>
</html>

View File

@ -26,6 +26,9 @@
{{ end }}
<meta name="theme-color" content="#346ba6">
<script src="{{ static "js/templates.js" }}"></script>
<script src="{{ static "js/showcase.js" }}"></script>
<link rel="stylesheet" href="{{ static "fonts/mohave/stylesheet.css" }}">
<link href='https://fonts.googleapis.com/css?family=Fira+Sans:300,400,500,600' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/css?family=Fira+Mono:300,400,500,700' rel='stylesheet' type='text/css'>
@ -105,12 +108,12 @@
text-decoration: underline;
}
#actions {
.actions {
margin-top: 1.5rem;
}
#actions a {
text-decoration: none;
.actions a {
text-decoration: none !important;
line-height: 1.4;
font-weight: 500;
@ -118,11 +121,11 @@
background-color:rgba(255, 255, 255, 0.1);
}
#actions a:hover {
.actions a:hover {
background-color: rgba(255, 255, 255, 0.2);
}
#actions a:active {
.actions a:active {
background-color: rgba(255, 255, 255, 0.15);
}
@ -183,6 +186,11 @@
text-transform: uppercase;
}
.showcase-item {
background-color: rgba(0, 0, 0, 0.2);
border-color: rgba(255, 255, 255, 0.5);
}
@media screen and (min-width: 30em) {
/* not small styles */
@ -208,11 +216,11 @@
margin-top: 1.2rem;
}
#actions {
.actions {
margin-top: 2.2rem;
}
#actions a {
.actions a {
font-size: 1.2rem;
}
@ -241,11 +249,18 @@
<div id="tagline" class="center">
A one-week jam to change the status quo.
</div>
<div id="actions" class="flex justify-center">
<div class="actions flex justify-center">
{{ if gt .DaysUntilStart 0 }}
<a class="ba b--white br2 pv2 pv3-ns ph3 ph4-ns" target="_blank" href="https://github.com/HandmadeNetwork/wishlist/discussions">Choose a project</a>
{{ else }}
<!-- TODO: A reasonable call to action! -->
{{ if gt .DaysUntilEnd 0 }}
{{ if .SubmittedProjectUrl }}
<a class="ba b--white br2 pv2 pv3-ns ph3 ph4-ns" target="_blank" href="{{ .SubmittedProjectUrl }}">Share your progress</a>
{{ else }}
<a class="ba b--white br2 pv2 pv3-ns ph3 ph4-ns" target="_blank" href="https://github.com/HandmadeNetwork/wishlist/discussions">Choose a project</a>
<a class="ba b--white br2 pv2 pv3-ns ph3 ph4-ns ml3" target="_blank" href="{{ .ProjectSubmissionUrl }}">Create a jam project</a>
{{ end }}
{{ end }}
{{ end }}
<a class="ba b--white br2 pv2 pv3-ns ph3 ph4-ns ml3" target="_blank" href="https://discord.gg/hmn">Join the Discord</a>
</div>
@ -266,6 +281,28 @@
</p>
</div>
{{ if .ShowcaseJson }}
<div id="showcase-outer-container" class="bg-black-20 pt4 pb3 pb4-ns">
<div class="section mw8 margin-center ph3 ph4-l">
{{ if gt .DaysUntilEnd 0 }}
<h2>Recent activity</h2>
<p>
These screenshots and videos were shared in #jam-showcase on our <a href="https://discord.gg/hmn" target="_blank">Discord</a>. Join us!
</p>
{{ else }}
<h2>Showcase</h2>
<p>
Post-jam text
</p>
{{ end }}
<div id="showcase-container" class="mw8 center-layout mh2 mh0-ns"></div>
<div class="actions flex justify-center">
<a class="ba b--white br2 pv2 pv3-ns ph3 ph4-ns ml3" target="_blank" href="{{ .ShowcaseFeedUrl }}">See more</a>
</div>
</div>
</div>
{{ end }}
<div class="bg-black-20 pt4 pb3 pb4-ns">
<div class="section mw8 margin-center ph3 ph4-l">
<h2>Details / Rules</h2>
@ -340,6 +377,109 @@
{{ template "footer.html" . }}
</div>
</div>
{{ template "showcase_templates.html" }}
<!-- Copy-pasted and mangled from showcase.html -->
<script>
const ROW_HEIGHT = 300;
const ITEM_SPACING = 4;
const showcaseItems = JSON.parse("{{ .ShowcaseJson }}");
const addThumbnailFuncs = new Array(showcaseItems.length);
const showcaseOuterContainer = document.querySelector('#showcase-outer-container');
let showcaseContainer = document.querySelector('#showcase-container');
showcaseOuterContainer.classList.toggle('dn', showcaseItems.length === 0);
const itemElements = []; // array of arrays
for (let i = 0; i < showcaseItems.length; i++) {
const item = showcaseItems[i];
const [itemEl, addThumbnail] = makeShowcaseItem(item);
itemEl.container.setAttribute('data-index', i);
itemEl.container.setAttribute('data-date', item.date);
addThumbnailFuncs[i] = addThumbnail;
itemElements.push(itemEl.container);
}
function layout() {
const width = showcaseContainer.getBoundingClientRect().width;
showcaseContainer = emptyElement(showcaseContainer);
function addRow(itemEls, rowWidth, container) {
const totalSpacing = ITEM_SPACING * (itemEls.length - 1);
const scaleFactor = (width / Math.max(rowWidth, width));
const row = document.createElement('div');
row.classList.add('flex');
row.classList.toggle('justify-between', rowWidth >= width);
row.style.marginBottom = `${ITEM_SPACING}px`;
for (const itemEl of itemEls) {
const index = parseInt(itemEl.getAttribute('data-index'), 10);
const item = showcaseItems[index];
const aspect = item.width / item.height;
const baseWidth = (aspect * ROW_HEIGHT) * scaleFactor;
const actualWidth = baseWidth - (totalSpacing / itemEls.length);
itemEl.style.width = `${actualWidth}px`;
itemEl.style.height = `${scaleFactor * ROW_HEIGHT}px`;
itemEl.style.marginRight = `${ITEM_SPACING}px`;
row.appendChild(itemEl);
}
container.appendChild(row);
}
let rowItemEls = [];
let rowWidth = 0;
let numRows = 0;
for (const itemEl of itemElements) {
const index = parseInt(itemEl.getAttribute('data-index'), 10);
const item = showcaseItems[index];
const aspect = item.width / item.height;
rowWidth += aspect * ROW_HEIGHT;
rowItemEls.push(itemEl);
if (rowWidth > width) {
addRow(rowItemEls, rowWidth, showcaseContainer);
numRows += 1;
if (numRows == 3) {
return;
}
rowItemEls = [];
rowWidth = 0;
}
}
addRow(rowItemEls, rowWidth, showcaseContainer);
}
function loadImages() {
const items = showcaseContainer.querySelectorAll('.showcase-item');
for (const item of items) {
const i = parseInt(item.getAttribute('data-index'), 10);
addThumbnailFuncs[i]();
}
}
layout();
layout(); // scrollbars are fun!!
loadImages();
window.addEventListener('resize', () => {
layout();
});
</script>
</body>
</html>

View File

@ -32,22 +32,132 @@ func JamIndex2022(c *RequestContext) ResponseData {
type JamPageData struct {
templates.BaseData
DaysUntilStart, DaysUntilEnd int
SubmittedProjectUrl string
ProjectSubmissionUrl string
ShowcaseFeedUrl string
ShowcaseJson string
}
var showcaseItems []templates.TimelineItem
submittedProjectUrl := ""
if daysUntilStart <= 0 && daysUntilEnd > 0 {
if c.CurrentUser != nil {
projects, err := hmndata.FetchProjects(c.Context(), c.Conn, c.CurrentUser, hmndata.ProjectsQuery{
OwnerIDs: []int{c.CurrentUser.ID},
JamSlugs: []string{hmndata.WRJ2022.Slug},
Limit: 1,
})
if err != nil {
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch jam projects for current user"))
}
if len(projects) > 0 {
urlContext := hmndata.UrlContextForProject(&projects[0].Project)
submittedProjectUrl = urlContext.BuildHomepage()
}
}
jamProjects, err := hmndata.FetchProjects(c.Context(), c.Conn, c.CurrentUser, hmndata.ProjectsQuery{
JamSlugs: []string{hmndata.WRJ2022.Slug},
})
if err != nil {
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch jam projects for current user"))
}
jamProjectTags := make([]int, 0, len(jamProjects))
for _, jp := range jamProjects {
if jp.Tag != nil {
jamProjectTags = append(jamProjectTags, jp.Tag.ID)
}
}
snippets, err := hmndata.FetchSnippets(c.Context(), c.Conn, c.CurrentUser, hmndata.SnippetQuery{
Tags: jamProjectTags,
Limit: 12,
})
if err != nil {
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch snippets for jam showcase"))
}
showcaseItems = make([]templates.TimelineItem, 0, len(snippets))
for _, s := range snippets {
timelineItem := SnippetToTimelineItem(&s.Snippet, s.Asset, s.DiscordMessage, s.Tags, s.Owner, c.Theme)
if timelineItem.CanShowcase {
showcaseItems = append(showcaseItems, timelineItem)
}
}
}
showcaseJson := templates.TimelineItemsToJSON(showcaseItems)
res.MustWriteTemplate("wheeljam_2022_index.html", JamPageData{
BaseData: baseData,
DaysUntilStart: daysUntilStart,
DaysUntilEnd: daysUntilEnd,
BaseData: baseData,
DaysUntilStart: daysUntilStart,
DaysUntilEnd: daysUntilEnd,
ProjectSubmissionUrl: hmnurl.BuildProjectNewJam(),
SubmittedProjectUrl: submittedProjectUrl,
ShowcaseFeedUrl: hmnurl.BuildJamFeed2022(),
ShowcaseJson: showcaseJson,
}, c.Perf)
return res
}
func JamFeed2022(c *RequestContext) ResponseData {
// List newly-created jam projects
// list snippets from jam projects
// list forum posts from jam project threads
// timeline everything
return FourOhFour(c)
jamProjects, err := hmndata.FetchProjects(c.Context(), c.Conn, c.CurrentUser, hmndata.ProjectsQuery{
JamSlugs: []string{hmndata.WRJ2022.Slug},
})
if err != nil {
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch jam projects for current user"))
}
jamProjectTags := make([]int, 0, len(jamProjects))
for _, jp := range jamProjects {
if jp.Tag != nil {
jamProjectTags = append(jamProjectTags, jp.Tag.ID)
}
}
snippets, err := hmndata.FetchSnippets(c.Context(), c.Conn, c.CurrentUser, hmndata.SnippetQuery{
Tags: jamProjectTags,
})
if err != nil {
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch snippets for jam showcase"))
}
timelineItems := make([]templates.TimelineItem, 0, len(snippets))
for _, s := range snippets {
timelineItem := SnippetToTimelineItem(&s.Snippet, s.Asset, s.DiscordMessage, s.Tags, s.Owner, c.Theme)
timelineItems = append(timelineItems, timelineItem)
}
// TODO(asaf): add forum posts from jam project threads to timeline
// TODO(asaf): Sort timeline items
pageProjects := make([]templates.Project, 0, len(jamProjects))
for _, p := range jamProjects {
pageProjects = append(pageProjects, templates.ProjectAndStuffToTemplate(&p, hmndata.UrlContextForProject(&p.Project).BuildHomepage(), c.Theme))
}
type JamFeedData struct {
templates.BaseData
JamProjects []templates.Project
TimelineItems []templates.TimelineItem
}
baseData := getBaseDataAutocrumb(c, hmndata.WRJ2022.Name)
baseData.OpenGraphItems = []templates.OpenGraphItem{
{Property: "og:site_name", Value: "Handmade.Network"},
{Property: "og:type", Value: "website"},
{Property: "og:image", Value: hmnurl.BuildPublic("wheeljam2022/opengraph.png", true)},
{Property: "og:description", Value: "A one-week jam to change the status quo. August 15 - 21 on Handmade Network."},
{Property: "og:url", Value: hmnurl.BuildJamIndex()},
}
var res ResponseData
res.MustWriteTemplate("wheeljam_2022_feed.html", JamFeedData{
BaseData: baseData,
JamProjects: pageProjects,
TimelineItems: timelineItems,
}, c.Perf)
return res
}
func JamIndex2021(c *RequestContext) ResponseData {