Ugly jam feed. Needs CSS work.
This commit is contained in:
parent
64f94bddbb
commit
316aba12b6
|
@ -29,6 +29,7 @@ type ProjectsQuery struct {
|
||||||
ProjectIDs []int // if empty, all projects
|
ProjectIDs []int // if empty, all projects
|
||||||
Slugs []string // if empty, all projects
|
Slugs []string // if empty, all projects
|
||||||
OwnerIDs []int // if empty, all projects
|
OwnerIDs []int // if empty, all projects
|
||||||
|
JamSlugs []string // if empty, all projects
|
||||||
|
|
||||||
// Ignored when using CountProjects
|
// Ignored when using CountProjects
|
||||||
Limit, Offset int // if empty, no pagination
|
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)
|
// Filters (permissions are checked after the query, in Go)
|
||||||
qb.Add(`
|
qb.Add(`
|
||||||
WHERE
|
WHERE
|
||||||
|
@ -130,6 +139,9 @@ func FetchProjects(
|
||||||
if len(q.Slugs) > 0 {
|
if len(q.Slugs) > 0 {
|
||||||
qb.Add(`AND (project.slug != '' AND project.slug = ANY ($?))`, q.Slugs)
|
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
|
// Output
|
||||||
if q.Limit > 0 {
|
if q.Limit > 0 {
|
||||||
|
|
|
@ -320,6 +320,12 @@ func BuildProjectNew() string {
|
||||||
return Url("/p/new", nil)
|
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-]+))?")
|
var RegexPersonalProject = regexp.MustCompile("^/p/(?P<projectid>[0-9]+)(/(?P<projectslug>[a-zA-Z0-9-]+))?")
|
||||||
|
|
||||||
func BuildPersonalProject(id int, slug string) string {
|
func BuildPersonalProject(id int, slug string) string {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -26,6 +26,9 @@
|
||||||
{{ end }}
|
{{ end }}
|
||||||
<meta name="theme-color" content="#346ba6">
|
<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 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+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 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;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
#actions {
|
.actions {
|
||||||
margin-top: 1.5rem;
|
margin-top: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#actions a {
|
.actions a {
|
||||||
text-decoration: none;
|
text-decoration: none !important;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|
||||||
|
@ -118,11 +121,11 @@
|
||||||
background-color:rgba(255, 255, 255, 0.1);
|
background-color:rgba(255, 255, 255, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#actions a:hover {
|
.actions a:hover {
|
||||||
background-color: rgba(255, 255, 255, 0.2);
|
background-color: rgba(255, 255, 255, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#actions a:active {
|
.actions a:active {
|
||||||
background-color: rgba(255, 255, 255, 0.15);
|
background-color: rgba(255, 255, 255, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,6 +186,11 @@
|
||||||
text-transform: uppercase;
|
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) {
|
@media screen and (min-width: 30em) {
|
||||||
/* not small styles */
|
/* not small styles */
|
||||||
|
|
||||||
|
@ -208,11 +216,11 @@
|
||||||
margin-top: 1.2rem;
|
margin-top: 1.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#actions {
|
.actions {
|
||||||
margin-top: 2.2rem;
|
margin-top: 2.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#actions a {
|
.actions a {
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,11 +249,18 @@
|
||||||
<div id="tagline" class="center">
|
<div id="tagline" class="center">
|
||||||
A one-week jam to change the status quo.
|
A one-week jam to change the status quo.
|
||||||
</div>
|
</div>
|
||||||
<div id="actions" class="flex justify-center">
|
<div class="actions flex justify-center">
|
||||||
{{ if gt .DaysUntilStart 0 }}
|
{{ 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>
|
<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 }}
|
{{ 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 }}
|
{{ 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>
|
<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>
|
</div>
|
||||||
|
@ -266,6 +281,28 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</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="bg-black-20 pt4 pb3 pb4-ns">
|
||||||
<div class="section mw8 margin-center ph3 ph4-l">
|
<div class="section mw8 margin-center ph3 ph4-l">
|
||||||
<h2>Details / Rules</h2>
|
<h2>Details / Rules</h2>
|
||||||
|
@ -340,6 +377,109 @@
|
||||||
{{ template "footer.html" . }}
|
{{ template "footer.html" . }}
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -32,22 +32,132 @@ func JamIndex2022(c *RequestContext) ResponseData {
|
||||||
type JamPageData struct {
|
type JamPageData struct {
|
||||||
templates.BaseData
|
templates.BaseData
|
||||||
DaysUntilStart, DaysUntilEnd int
|
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{
|
res.MustWriteTemplate("wheeljam_2022_index.html", JamPageData{
|
||||||
BaseData: baseData,
|
BaseData: baseData,
|
||||||
DaysUntilStart: daysUntilStart,
|
DaysUntilStart: daysUntilStart,
|
||||||
DaysUntilEnd: daysUntilEnd,
|
DaysUntilEnd: daysUntilEnd,
|
||||||
|
ProjectSubmissionUrl: hmnurl.BuildProjectNewJam(),
|
||||||
|
SubmittedProjectUrl: submittedProjectUrl,
|
||||||
|
ShowcaseFeedUrl: hmnurl.BuildJamFeed2022(),
|
||||||
|
ShowcaseJson: showcaseJson,
|
||||||
}, c.Perf)
|
}, c.Perf)
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func JamFeed2022(c *RequestContext) ResponseData {
|
func JamFeed2022(c *RequestContext) ResponseData {
|
||||||
// List newly-created jam projects
|
jamProjects, err := hmndata.FetchProjects(c.Context(), c.Conn, c.CurrentUser, hmndata.ProjectsQuery{
|
||||||
// list snippets from jam projects
|
JamSlugs: []string{hmndata.WRJ2022.Slug},
|
||||||
// list forum posts from jam project threads
|
})
|
||||||
// timeline everything
|
if err != nil {
|
||||||
return FourOhFour(c)
|
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 {
|
func JamIndex2021(c *RequestContext) ResponseData {
|
||||||
|
|
Loading…
Reference in New Issue