Add time machine submissions page

This commit is contained in:
Ben Visness 2023-06-06 13:23:54 -05:00
parent dd6e5e3b66
commit 8be575875d
8 changed files with 382 additions and 222 deletions

View File

@ -415,6 +415,31 @@ func TestJamRecap2023_Visibility(t *testing.T) {
AssertSubdomain(t, BuildJamRecap2023_Visibility(), "") AssertSubdomain(t, BuildJamRecap2023_Visibility(), "")
} }
func TestTimeMachine(t *testing.T) {
AssertRegexMatch(t, BuildTimeMachine(), RegexTimeMachine, nil)
AssertSubdomain(t, BuildTimeMachine(), "")
}
func TestTimeMachineSubmissions(t *testing.T) {
AssertRegexMatch(t, BuildTimeMachineSubmissions(), RegexTimeMachineSubmissions, nil)
AssertSubdomain(t, BuildTimeMachineSubmissions(), "")
}
func TestTimeMachineForm(t *testing.T) {
AssertRegexMatch(t, BuildTimeMachineForm(), RegexTimeMachineForm, nil)
AssertSubdomain(t, BuildTimeMachineForm(), "")
}
func TestTimeMachineFormDone(t *testing.T) {
AssertRegexMatch(t, BuildTimeMachineFormDone(), RegexTimeMachineFormDone, nil)
AssertSubdomain(t, BuildTimeMachineFormDone(), "")
}
func TestNewsletterSignup(t *testing.T) {
AssertRegexMatch(t, BuildNewsletterSignup(), RegexNewsletterSignup, nil)
AssertSubdomain(t, BuildNewsletterSignup(), "")
}
func TestProjectNewJam(t *testing.T) { func TestProjectNewJam(t *testing.T) {
AssertRegexMatch(t, BuildProjectNewJam(), RegexProjectNew, nil) AssertRegexMatch(t, BuildProjectNewJam(), RegexProjectNew, nil)
AssertSubdomain(t, BuildProjectNewJam(), "") AssertSubdomain(t, BuildProjectNewJam(), "")

View File

@ -119,6 +119,13 @@ func BuildTimeMachine() string {
return Url("/timemachine", nil) return Url("/timemachine", nil)
} }
var RegexTimeMachineSubmissions = regexp.MustCompile("^/timemachine/submissions$")
func BuildTimeMachineSubmissions() string {
defer CatchPanic()
return Url("/timemachine/submissions", nil)
}
var RegexTimeMachineForm = regexp.MustCompile("^/timemachine/submit$") var RegexTimeMachineForm = regexp.MustCompile("^/timemachine/submit$")
func BuildTimeMachineForm() string { func BuildTimeMachineForm() string {

View File

@ -0,0 +1,30 @@
<div class="frame">
{{ template "frame title" .Title }}
<div>
<div class="pa3 flex flex-column g3">
<div class="flex flex-column flex-row-ns g3">
<div class="flex-shrink-0 relative">
<img
class="pixelated inset"
src="{{ static .Thumbnail }}"
alt="Video Thumbnail"
/>
<a href="{{ .Url }}" target="_blank">
<div class="absolute absolute--fill bg-black-30 flex justify-center items-center">
<img class="pixelated" alt="Play Video" src="{{ dataimg "timemachine/win95-play.png" }}">
</div>
</a>
</div>
<div class="flex flex-column g1">
{{ range .Details }}
<div><strong>{{ .Name }}:</strong> {{ .Content }}</div>
{{ end }}
</div>
</div>
<div class="post-content">
{{ .Description }}
</div>
</div>
</div>
</div>

View File

@ -5,195 +5,213 @@
<!DOCTYPE html{{ if .OpenGraphItems }} prefix="og: http://ogp.me/ns#"{{ end }}> <!DOCTYPE html{{ if .OpenGraphItems }} prefix="og: http://ogp.me/ns#"{{ end }}>
<html lang="en-US"> <html lang="en-US">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/png" sizes="16x16" href="{{ static "visjam2023/favicon-16x16.png" }}"> <link rel="icon" type="image/png" sizes="16x16" href="{{ static "visjam2023/favicon-16x16.png" }}">
<link rel="icon" type="image/png" sizes="32x32" href="{{ static "visjam2023/favicon-32x32.png" }}"> <link rel="icon" type="image/png" sizes="32x32" href="{{ static "visjam2023/favicon-32x32.png" }}">
{{ if .CanonicalLink }}<link rel="canonical" href="{{ .CanonicalLink }}">{{ end }} {{ if .CanonicalLink }}<link rel="canonical" href="{{ .CanonicalLink }}">{{ end }}
{{ range .OpenGraphItems }} {{ range .OpenGraphItems }}
{{ if .Property }} {{ if .Property }}
<meta property="{{ .Property }}" content="{{ .Value }}" /> <meta property="{{ .Property }}" content="{{ .Value }}" />
{{ else }}
<meta name="{{ .Name }}" content="{{ .Value }}" />
{{ end }}
{{ end }}
{{ if .Title }}
<title>{{ .Title }} | Handmade Network</title>
{{ else }} {{ else }}
<title>Handmade Network</title> <meta name="{{ .Name }}" content="{{ .Value }}" />
{{ end }} {{ end }}
<meta name="theme-color" content="#346ba6"> {{ end }}
{{ if .Title }}
<title>{{ .Title }} | Handmade Network</title>
{{ else }}
<title>Handmade Network</title>
{{ end }}
<meta name="theme-color" content="#346ba6">
<script src="{{ static "js/templates.js" }}"></script> <script src="{{ static "js/templates.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'>
<link rel="stylesheet" type="text/css" href="{{ static "style.css" }}"> <link rel="stylesheet" type="text/css" href="{{ static "style.css" }}">
<style> <style>
body { body {
/* /*
This is too light for accessiblity imo. I'll darken them a bit and see how Ben feels This is too light for accessiblity imo. I'll darken them a bit and see how Ben feels
about it. -- Jake 5/26/2023 about it. -- Jake 5/26/2023
background: linear-gradient(15deg, #b4c4fe, #eb8cbf ); */ background: linear-gradient(15deg, #b4c4fe, #eb8cbf ); */
background: linear-gradient(15deg, #7391fd, #e25ca4); background: linear-gradient(15deg, #7391fd, #e25ca4);
} }
.user-options, .user-options,
header form, header form,
header .menu-bar .wiki, header .menu-bar .wiki,
header .menu-bar .library { header .menu-bar .library {
display: none !important; display: none !important;
} }
header { header {
border-bottom-color: white; border-bottom-color: white;
margin-bottom: 0 !important; margin-bottom: 0 !important;
} }
.hmn-logo { .hmn-logo {
background-color: rgba(255, 255, 255, 0.1) !important; background-color: rgba(255, 255, 255, 0.1) !important;
} }
header a, header a,
footer a { footer a {
color: white !important; color: white !important;
} }
header .submenu { header .submenu {
background-color: #d661ad; background-color: #d661ad;
} }
#logo { #logo {
width: 16rem; width: 16rem;
} }
h1, h1,
h2, h2,
h3 { h3 {
font-family: "MohaveHMN", sans-serif; font-family: "MohaveHMN", sans-serif;
margin-bottom: 0; margin-bottom: 0;
font-weight: normal; font-weight: normal;
} }
/* '95-esque styles */ /* '95-esque styles */
.frame { .frame {
width: 100%; width: 100%;
background: #c3c3c3; background: #c3c3c3;
border-width: 2px; border-width: 2px;
border-style: solid; border-style: solid;
border-color: #fff #333 #333 #fff; border-color: #fff #333 #333 #fff;
border-image: url('{{ dataimg "timemachine/win95-border.gif" }}') 4; border-image: url('{{ dataimg "timemachine/win95-border.gif" }}') 4;
padding: 1px; padding: 1px;
color: black; color: black;
} }
.frame ol, .frame ol,
.frame ul { .frame ul {
padding-left: 5px; padding-left: 5px;
} }
.frame ul { .frame ul {
list-style: disc; list-style: disc;
} }
.frame strong { .frame strong {
font-weight: bold; font-weight: bold;
} }
.frame .title { .frame .title {
padding: 0 0.1875rem; /* 0 3px */ padding: 0 0.1875rem; /* 0 3px */
height: 1.125rem; /* 18px */ height: 1.125rem; /* 18px */
line-height: 1.125rem; /* 18px */ line-height: 1.125rem; /* 18px */
font-size: 0.8125rem; /* 13px */ font-size: 0.8125rem; /* 13px */
width: 100%; width: 100%;
background: rgb(0, 0, 130); background: rgb(0, 0, 130);
color: #fff; color: #fff;
position: relative; position: relative;
} }
.frame .frame-close { .frame .frame-close {
width: 1rem; /* 16px */ width: 1rem; /* 16px */
height: 0.875rem; /* 14px */ height: 0.875rem; /* 14px */
position: absolute; position: absolute;
right: 0.125rem; /* 2px */ right: 0.125rem; /* 2px */
top: 0.125rem; /* 2px */ top: 0.125rem; /* 2px */
cursor: pointer; cursor: pointer;
} }
.frame * { .frame * {
font-family: "MS Sans Serif", "Microsoft Sans Serif", sans-serif; font-family: "MS Sans Serif", "Microsoft Sans Serif", sans-serif;
} }
.inset { .inset {
border-width: 1px; border-width: 1px;
border-color: #828282 #ffffff #ffffff #828282; border-color: #828282 #ffffff #ffffff #828282;
border-style: solid; border-style: solid;
border-image: url('{{ dataimg "timemachine/win95-border-inset.gif" }}') 2; border-image: url('{{ dataimg "timemachine/win95-border-inset.gif" }}') 2;
} }
img.pixelated { img.pixelated {
image-rendering: crisp-edges; image-rendering: crisp-edges;
} }
.drop-shadow { .drop-shadow {
box-shadow: 2px 2px black; box-shadow: 2px 2px black;
} }
.win95-btn { .win95-btn {
min-width: 5.6rem; min-width: 5.6rem;
height: 1.4375rem; /* 23px */ height: 1.4375rem; /* 23px */
border-width: 2px; border-width: 2px;
border-style: solid; border-style: solid;
border-color: #fff #333 #333 #fff; border-color: #fff #333 #333 #fff;
border-image: url('{{ dataimg "timemachine/win95-border-btn.gif" }}') 4; border-image: url('{{ dataimg "timemachine/win95-border-btn.gif" }}') 4;
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
font-weight: normal; font-weight: normal;
padding: 0 1rem !important; padding: 0 1rem !important;
} }
.win95-btn:active { .win95-btn:active {
border-image: url('{{ dataimg "timemachine/win95-border-btn-pressed.gif" }}') 4; border-image: url('{{ dataimg "timemachine/win95-border-btn-pressed.gif" }}') 4;
padding-top: 2px !important; padding-top: 2px !important;
} }
.win95-input { .win95-input {
border-width: 2px; border-width: 2px;
border-style: solid; border-style: solid;
border-image: url('{{ dataimg "timemachine/win95-border-input.gif" }}') 4; border-image: url('{{ dataimg "timemachine/win95-border-input.gif" }}') 4;
outline: none; outline: none;
background-color: white !important; background-color: white !important;
resize: vertical; /* only applies to textareas so whatever */ resize: vertical; /* only applies to textareas so whatever */
} }
.less-spacing p {
margin: 0.2rem 0;
}
</style>
.less-spacing p {
margin: 0.2rem 0;
}
</style>
</head> </head>
<body> <body>
<div class="left white"> <div class="left white">
<div class="mt4-ns mw7 margin-center ph3-m ph4-l"> <div class="mt4-ns mw7 margin-center ph3-ns">
{{ template "header.html" . }} {{ template "header.html" . }}
</div>
<div class="jam-sections">
{{ block "content" . }}{{ end }}
</div>
<div class="mw8 margin-center ph3-m ph4-l">
{{ template "footer.html" . }}
</div>
</div> </div>
<div class="jam-sections">
<div class="center-layout content mw7 ph3 flex flex-column g3">
{{ with .Breadcrumbs }}
<div class="mt2 tc tl-ns ph2 ph0-ns">
{{ range $i, $e := . -}}
{{- if gt $i 0 -}}
<span class="ph2">&raquo;</span>
{{- end -}}
<a class="breadcrumb" href="{{ .Url }}">{{ .Name }}</a>
{{- end }}
</div>
{{ end }}
</div>
{{ block "content" . }}{{ end }}
<div class="mw8 margin-center ph3-ns">
{{ template "footer.html" . }}
</div>
</div>
</div>
</body> </body>
</html> </html>
{{ define "frame title" }}
<div class="title">
{{ . }}
<img class="frame-close" src="{{ dataimg "timemachine/win95-close.gif" }}">
</div>
{{ end }}

View File

@ -1,12 +1,5 @@
{{ template "timemachine_base.html" . }} {{ template "timemachine_base.html" . }}
{{ define "frame title" }}
<div class="title">
{{ . }}
<img class="frame-close" src="{{ dataimg "timemachine/win95-close.gif" }}">
</div>
{{ end }}
{{ define "content" }} {{ define "content" }}
<div class="center-layout content mw7 ph3 flex flex-column g3"> <div class="center-layout content mw7 ph3 flex flex-column g3">
@ -77,7 +70,7 @@
</div> </div>
</div> </div>
<div class="flex justify-end g3"> <div class="flex justify-end g3">
<a class="win95-btn" href="{{ .SubmitUrl }}"><u>S</u>ubmit your own</a> <a class="win95-btn" href="{{ .SubmitUrl }}"><u>S</u>ubmit Your Own</a>
</div> </div>
</div> </div>
</div> </div>
@ -92,66 +85,22 @@
</div> </div>
<div class="flex flex-column flex-grow-1 g3"> <div class="flex flex-column flex-grow-1 g3">
<div> <div>
1 video has been submitted! Would you like to see it? {{ if eq .NumSubmissions 1 }}
{{ .NumSubmissions }} video has been submitted! Would you like to see it?
{{ else }}
{{ .NumSubmissions }} videos have been submitted! Would you like to see them?
{{ end }}
</div> </div>
<div class="flex justify-end g3"> <div class="flex justify-end g3">
<div class="win95-btn"><u>Y</u>es</div> <a href="{{ .SubmissionsUrl }}"><div class="win95-btn"><u>Y</u>es</div></a>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="frame mv4"> <div class="mv4">
{{ template "frame title" "2009 iPod Touch" }} {{ template "timemachine_submission.html" .FeaturedSubmission }}
<div>
<div class="pa3 flex flex-column g3">
<div class="flex flex-column flex-row-ns g3">
<div class="flex-shrink-0 relative">
<img
class="pixelated inset"
src="{{ static "timemachine/ipodtouch-dither.gif" }}"
alt="Video Thumbnail"
/>
<a href="https://youtu.be/2eBFk1yV6mE" target="_blank">
<div class="absolute absolute--fill bg-black-30 flex justify-center items-center">
<img class="pixelated" alt="Play Video" src="{{ dataimg "timemachine/win95-play.png" }}">
</div>
</a>
</div>
<div class="flex flex-column g1">
<div><strong>Device:</strong> iPod Touch 3rd gen, model MC008LL</div>
<div><strong>Submitted by:</strong> Ben Visness</div>
<div><strong>Release year:</strong> 2009</div>
<div><strong>Processor:</strong> 600MHz Samsung S5L8922, single-core</div>
<div><strong>Memory:</strong> 256MB LPDDR2 @ 200 MHz</div>
<div><strong>Operating system:</strong> iOS 5</div>
</div>
</div>
<div class="post-content">
<p>
This is the iPod Touch I got when I was 13. It was my first major
tech purchase and an early device in the iOS lineup. When I
purchased this I think it was running iOS 3; at this point it has
iOS 5. I was pleased to see that the battery still holds a charge
quite well, and it consistently runs at about 30 to 60 frames per
second.
</p>
<p>
In the video you can see several built-in apps. Media playback
still works great, and scrubbing around in songs is instantaneous.
App switching works well. The calculator launches instantly (as
you would hope). I was shocked to see that the old Google Maps app
still works - apparently they have kept their old tile-based map
servers online. It even gave me public transit directions.
</p>
<p>
Overall, I would say this device feels only a hair slower than my
current iPhone.
</p>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -0,0 +1,36 @@
{{ template "timemachine_base.html" . }}
{{ define "content" }}
<div class="center-layout content mw7 ph3 flex flex-column g3">
<div>
<div class="mv4 mv5-ns flex justify-center">
<div class="frame mw6">
{{ template "frame title" "About This Page" }}
<div class="pa3 flex g3">
<div class="flex-shrink-0">
<img class="pixelated" src="{{ dataimg "timemachine/win95-info.png" }}">
</div>
<div class="flex flex-column flex-grow-1 g3">
<div>
The community has submitted the following
{{ with len .Submissions }}
{{ if eq . 1 }}video{{ else }}{{ . }} videos{{ end }}
{{ end }}
to the Time Machine project. If you have access to any older devices, you can participate too!
</div>
<div class="flex justify-end g3">
<a href="{{ .MainUrl }}"><div class="win95-btn">Learn&nbsp;<u>M</u>ore</div></a>
</div>
</div>
</div>
</div>
</div>
<div class="flex flex-column g3">
{{ range .Submissions }}
{{ template "timemachine_submission.html" . }}
{{ end }}
</div>
</div>
</div>
{{ end }}

View File

@ -71,6 +71,7 @@ func NewWebsiteRoutes(conn *pgxpool.Pool) http.Handler {
hmnOnly.GET(hmnurl.RegexJamRecap2023_Visibility, JamRecap2023_Visibility) hmnOnly.GET(hmnurl.RegexJamRecap2023_Visibility, JamRecap2023_Visibility)
hmnOnly.GET(hmnurl.RegexTimeMachine, TimeMachine) hmnOnly.GET(hmnurl.RegexTimeMachine, TimeMachine)
hmnOnly.GET(hmnurl.RegexTimeMachineSubmissions, TimeMachineSubmissions)
hmnOnly.GET(hmnurl.RegexTimeMachineForm, needsAuth(TimeMachineForm)) hmnOnly.GET(hmnurl.RegexTimeMachineForm, needsAuth(TimeMachineForm))
hmnOnly.GET(hmnurl.RegexTimeMachineFormDone, needsAuth(TimeMachineFormDone)) hmnOnly.GET(hmnurl.RegexTimeMachineFormDone, needsAuth(TimeMachineFormDone))
hmnOnly.POST(hmnurl.RegexTimeMachineForm, needsAuth(csrfMiddleware(TimeMachineFormSubmit))) hmnOnly.POST(hmnurl.RegexTimeMachineForm, needsAuth(csrfMiddleware(TimeMachineFormSubmit)))

View File

@ -1,6 +1,7 @@
package website package website
import ( import (
"html/template"
"net/http" "net/http"
"strings" "strings"
@ -11,7 +12,46 @@ import (
) )
func TimeMachine(c *RequestContext) ResponseData { func TimeMachine(c *RequestContext) ResponseData {
baseData := getBaseDataAutocrumb(c, "Time Machine") baseData := getBaseData(c, "Time Machine", nil)
baseData.OpenGraphItems = []templates.OpenGraphItem{
{Property: "og:title", Value: "Time Machine"},
{Property: "og:site_name", Value: "Handmade Network"},
{Property: "og:type", Value: "website"},
{Property: "og:image", Value: hmnurl.BuildPublic("timemachine/opengraph.png", true)},
{Property: "og:description", Value: "This summer, dig out your old devices and see what they were actually like to use."},
{Property: "og:url", Value: hmnurl.BuildTimeMachine()},
{Name: "twitter:card", Value: "summary_large_image"},
{Name: "twitter:image", Value: hmnurl.BuildPublic("timemachine/twittercard.png", true)},
}
featured := tmSubmissions[0]
featured.Title = "Latest Submission"
type TemplateData struct {
templates.BaseData
SubmitUrl string
SubmissionsUrl string
NumSubmissions int
FeaturedSubmission TimeMachineSubmission
}
tmpl := TemplateData{
BaseData: baseData,
SubmitUrl: hmnurl.BuildTimeMachineForm(),
SubmissionsUrl: hmnurl.BuildTimeMachineSubmissions(),
NumSubmissions: len(tmSubmissions),
FeaturedSubmission: featured,
}
var res ResponseData
res.MustWriteTemplate("timemachine.html", tmpl, c.Perf)
return res
}
func TimeMachineSubmissions(c *RequestContext) ResponseData {
baseData := getBaseData(c, "Time Machine - Submissions", []templates.Breadcrumb{
{"Time Machine", hmnurl.BuildTimeMachine()},
{"Submissions", hmnurl.BuildTimeMachineSubmissions()},
})
baseData.OpenGraphItems = []templates.OpenGraphItem{ baseData.OpenGraphItems = []templates.OpenGraphItem{
{Property: "og:title", Value: "Time Machine"}, {Property: "og:title", Value: "Time Machine"},
{Property: "og:site_name", Value: "Handmade Network"}, {Property: "og:site_name", Value: "Handmade Network"},
@ -25,15 +65,19 @@ func TimeMachine(c *RequestContext) ResponseData {
type TemplateData struct { type TemplateData struct {
templates.BaseData templates.BaseData
SubmitUrl string MainUrl string
SubmitUrl string
Submissions []TimeMachineSubmission
} }
tmpl := TemplateData{ tmpl := TemplateData{
BaseData: baseData, BaseData: baseData,
SubmitUrl: hmnurl.BuildTimeMachineForm(), MainUrl: hmnurl.BuildTimeMachine(),
SubmitUrl: hmnurl.BuildTimeMachineForm(),
Submissions: tmSubmissions,
} }
var res ResponseData var res ResponseData
res.MustWriteTemplate("timemachine.html", tmpl, c.Perf) res.MustWriteTemplate("timemachine_submissions.html", tmpl, c.Perf)
return res return res
} }
@ -94,3 +138,53 @@ func TimeMachineFormDone(c *RequestContext) ResponseData {
) )
return res return res
} }
type TimeMachineSubmission struct {
Title string
Url string
Thumbnail string
Details []TimeMachineSubmissionDetail
Description template.HTML
}
type TimeMachineSubmissionDetail struct {
Name string
Content template.HTML
}
var tmSubmissions = []TimeMachineSubmission{
{
Title: "2009 iPod Touch",
Url: "https://youtu.be/2eBFk1yV6mE",
Thumbnail: "timemachine/ipodtouch-dither.gif",
Details: []TimeMachineSubmissionDetail{
{"Device", "iPod Touch 3rd gen, model MC008LL"},
{"Submitted by", "Ben Visness"},
{"Release year", "2009"},
{"Processor", "600MHz Samsung S5L8922, single-core"},
{"Memory", "256MB LPDDR2 @ 200 MHz"},
{"Operating system", "iOS 5"},
},
Description: `
<p>
This is the iPod Touch I got when I was 13. It was my first major
tech purchase and an early device in the iOS lineup. When I
purchased this I think it was running iOS 3; at this point it has
iOS 5. I was pleased to see that the battery still holds a charge
quite well, and it consistently runs at about 30 to 60 frames per
second.
</p>
<p>
In the video you can see several built-in apps. Media playback
still works great, and scrubbing around in songs is instantaneous.
App switching works well. The calculator launches instantly (as
you would hope). I was shocked to see that the old Google Maps app
still works - apparently they have kept their old tile-based map
servers online. It even gave me public transit directions.
</p>
<p>
Overall, I would say this device feels only a hair slower than my
current iPhone.
</p>`,
},
}