Merge branch 'feature/time_machine_page'

This commit is contained in:
Ben Visness 2023-06-01 21:08:54 -05:00
commit 1ea9fbefbc
46 changed files with 747 additions and 4 deletions

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

View File

@ -91,6 +91,41 @@ func SendPasswordReset(toAddress string, toName string, username string, resetTo
return nil
}
type TimeMachineEmailData struct {
ProfileUrl string
Username string
UserEmail string
DiscordUsername string
MediaUrl string
DeviceInfo string
Description string
}
func SendTimeMachineEmail(profileUrl, username, userEmail, discordUsername, mediaUrl, deviceInfo, description string, perf *perf.RequestPerf) error {
perf.StartBlock("EMAIL", "Time machine email")
defer perf.EndBlock()
contents, err := renderTemplate("email_time_machine.html", TimeMachineEmailData{
ProfileUrl: profileUrl,
Username: username,
UserEmail: userEmail,
DiscordUsername: discordUsername,
MediaUrl: mediaUrl,
DeviceInfo: deviceInfo,
Description: description,
})
if err != nil {
return err
}
err = sendMail("team@handmade.network", "HMN Team", "[time machine] New submission", contents)
if err != nil {
return oops.New(err, "Failed to send email")
}
return nil
}
var EmailRegex = regexp.MustCompile(`^[^:\p{Cc} ]+@[^:\p{Cc} ]+\.[^:\p{Cc} ]+$`)
func IsEmail(address string) bool {

View File

@ -105,6 +105,27 @@ func BuildJamFeed2022() string {
return Url("/jam/2022/feed", nil)
}
var RegexTimeMachine = regexp.MustCompile("^/timemachine$")
func BuildTimeMachine() string {
defer CatchPanic()
return Url("/timemachine", nil)
}
var RegexTimeMachineForm = regexp.MustCompile("^/timemachine/submit$")
func BuildTimeMachineForm() string {
defer CatchPanic()
return Url("/timemachine/submit", nil)
}
var RegexTimeMachineFormDone = regexp.MustCompile("^/timemachine/thanks$")
func BuildTimeMachineFormDone() string {
defer CatchPanic()
return Url("/timemachine/thanks", nil)
}
// QUESTION(ben): Can we change these routes?
var RegexLoginAction = regexp.MustCompile("^/login$")

View File

Before

Width:  |  Height:  |  Size: 766 B

After

Width:  |  Height:  |  Size: 766 B

View File

Before

Width:  |  Height:  |  Size: 549 B

After

Width:  |  Height:  |  Size: 549 B

View File

Before

Width:  |  Height:  |  Size: 910 B

After

Width:  |  Height:  |  Size: 910 B

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 614 B

After

Width:  |  Height:  |  Size: 614 B

View File

Before

Width:  |  Height:  |  Size: 776 B

After

Width:  |  Height:  |  Size: 776 B

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 488 B

After

Width:  |  Height:  |  Size: 488 B

View File

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

Before

Width:  |  Height:  |  Size: 922 B

After

Width:  |  Height:  |  Size: 922 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 641 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 529 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 825 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 538 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 581 B

View File

Before

Width:  |  Height:  |  Size: 894 B

After

Width:  |  Height:  |  Size: 894 B

View File

@ -0,0 +1,6 @@
<p>Submission by <b><a href="{{ .ProfileUrl }}">{{ .Username }}</a></b> ({{ .UserEmail }}) {{ if .DiscordUsername }} On Discord: {{ .DiscordUsername }} {{ end }}</p>
<p>Submitted url: <a href="{{ .MediaUrl }}">{{ .MediaUrl }}</a></p>
Device info:<br />
<pre>{{ .DeviceInfo }}</pre>
Description:<br />
<pre>{{ .Description }}</pre>

View File

@ -1,6 +1,6 @@
<footer class="pa3 pa4-l tc">
<h2>
Community by <a href="{{ .Footer.HomepageUrl }}">handmade.network</a>
<a href="{{ .Footer.HomepageUrl }}">Handmade Network</a>
</h2>
<ul class="list">
{{ $footerClasses := "ma0 pa0 dib-ns" }}

View File

@ -111,6 +111,7 @@
</div>
*/}}
{{/*
<div class="mb3 ph3 ph0-ns">
<style>
#jam-banner {
@ -177,6 +178,7 @@
</div>
</a>
</div>
*/}}
<div class="mb3 ph3 ph0-ns">
<style>

View File

@ -0,0 +1,54 @@
{{/*
This is a copy-paste from base.html because we want to preserve the unique
style of landing pages if we change base.html in the future.
*/}}
<!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 "visjam2023/favicon-16x16.png" }}">
<link rel="icon" type="image/png" sizes="32x32" href="{{ static "visjam2023/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">
<script src="{{ static "js/templates.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'>
<link rel="stylesheet" type="text/css" href="{{ static "style.css" }}">
</head>
<body>
<div class="left white">
<div class="mt4-ns mw8 margin-center ph3-m ph4-l">
{{ 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>
</body>
</html>

View File

@ -0,0 +1,199 @@
{{/*
This is a copy-paste from base.html because we want to preserve the unique
style of landing pages if we change base.html in the future.
*/}}
<!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 "visjam2023/favicon-16x16.png" }}">
<link rel="icon" type="image/png" sizes="32x32" href="{{ static "visjam2023/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">
<script src="{{ static "js/templates.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'>
<link rel="stylesheet" type="text/css" href="{{ static "style.css" }}">
<style>
body {
/*
This is too light for accessiblity imo. I'll darken them a bit and see how Ben feels
about it. -- Jake 5/26/2023
background: linear-gradient(15deg, #b4c4fe, #eb8cbf ); */
background: linear-gradient(15deg, #7391fd, #e25ca4);
}
.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: #d661ad;
}
#logo {
width: 16rem;
}
h1,
h2,
h3 {
font-family: "MohaveHMN", sans-serif;
margin-bottom: 0;
font-weight: normal;
}
/* '95-esque styles */
.frame {
width: 100%;
background: #c3c3c3;
border-width: 2px;
border-style: solid;
border-color: #fff #333 #333 #fff;
border-image: url('{{ dataimg "timemachine/win95-border.gif" }}') 4;
padding: 1px;
color: black;
}
.frame ol,
.frame ul {
padding-left: 5px;
}
.frame ul {
list-style: disc;
}
.frame strong {
font-weight: bold;
}
.frame .title {
padding: 0 0.1875rem; /* 0 3px */
height: 1.125rem; /* 18px */
line-height: 1.125rem; /* 18px */
font-size: 0.8125rem; /* 13px */
width: 100%;
background: rgb(0, 0, 130);
color: #fff;
position: relative;
}
.frame .frame-close {
width: 1rem; /* 16px */
height: 0.875rem; /* 14px */
position: absolute;
right: 0.125rem; /* 2px */
top: 0.125rem; /* 2px */
cursor: pointer;
}
.frame * {
font-family: "MS Sans Serif", "Microsoft Sans Serif", sans-serif;
}
.inset {
border-width: 1px;
border-color: #828282 #ffffff #ffffff #828282;
border-style: solid;
border-image: url('{{ dataimg "timemachine/win95-border-inset.gif" }}') 2;
}
img.pixelated {
image-rendering: crisp-edges;
}
.drop-shadow {
box-shadow: 2px 2px black;
}
.win95-btn {
min-width: 5.6rem;
height: 1.4375rem; /* 23px */
border-width: 2px;
border-style: solid;
border-color: #fff #333 #333 #fff;
border-image: url('{{ dataimg "timemachine/win95-border-btn.gif" }}') 4;
text-align: center;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
font-weight: normal;
padding: 0 1rem !important;
}
.win95-btn:active {
border-image: url('{{ dataimg "timemachine/win95-border-btn-pressed.gif" }}') 4;
padding-top: 2px !important;
}
.win95-input {
border-width: 2px;
border-style: solid;
border-image: url('{{ dataimg "timemachine/win95-border-input.gif" }}') 4;
outline: none;
background-color: white !important;
resize: vertical; /* only applies to textareas so whatever */
}
.less-spacing p {
margin: 0.2rem 0;
}
</style>
</head>
<body>
<div class="left white">
<div class="mt4-ns mw7 margin-center ph3-m ph4-l">
{{ 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>
</body>
</html>

View File

@ -0,0 +1,158 @@
{{ template "timemachine_base.html" . }}
{{ define "frame title" }}
<div class="title">
{{ . }}
<img class="frame-close" src="{{ dataimg "timemachine/win95-close.gif" }}">
</div>
{{ end }}
{{ define "content" }}
<div class="center-layout content mw7 ph3 flex flex-column g3">
<div>
<div class="mv5 flex justify-center">
<img
class="pixelated drop-shadow"
src="{{ static "timemachine/splash.gif" }}"
alt="Time Machine"
>
</div>
<div class="frame mv4">
{{ template "frame title" "Overview" }}
<div class="post-content pa3 flex g3">
<div class="flex-shrink-0">
<img class="pixelated" src="{{ dataimg "timemachine/win95-computer.png" }}">
</div>
<div>
<p>
This summer, we're digging out old devices and seeing what they were actually like to use.
</p>
<p>
Our phones today are far more powerful than our desktop computers from decades ago. And yet, those old computers worked just fine! The Time Machine project is an opportunity to boot up those old devices and see how they feel to use.
</p>
<p>
As our hardware has improved, how has the user experience changed? Which parts have stayed the same? Which parts have gotten worse? There's only one way to find out.
</p>
</div>
</div>
</div>
<div class="frame mv4">
{{ template "frame title" "How To Participate" }}
<div class="post-content pa3">
<div class="flex flex-column g3">
<div class="flex g3">
<div class="flex-shrink-0">
<img class="pixelated" src="{{ dataimg "timemachine/win95-camera.png" }}">
</div>
<div>
<p>
<strong>Take video of yourself using an older device.</strong> Any personal device from any time period is great. We're particularly interested in the history of day-to-day devices like personal computers and mobile phones.
</p>
</div>
</div>
<div class="flex g3">
<div class="flex-shrink-0">
<img class="pixelated" src="{{ dataimg "timemachine/win95-upload.png" }}">
</div>
<div>
<p>
<strong>Upload the video somewhere.</strong> Upload to Google Drive, Dropbox, or even YouTube - anywhere we can download it from.
</p>
</div>
</div>
<div class="flex g3">
<div class="flex-shrink-0">
<img class="pixelated" src="{{ dataimg "timemachine/win95-pad.png" }}">
</div>
<div>
<p>
<strong>Send us the video link + extra info.</strong> Tell us about this device, when you got it, what specs it has, and anything else you think is interesting. We'll get the video posted here on the website for everyone to see.
</p>
<p>
At the end of the summer, we'll compile all the submissions into a final report.
</p>
</div>
</div>
<div class="flex justify-end g3">
<a class="win95-btn" href="{{ .SubmitUrl }}"><u>S</u>ubmit your own</a>
</div>
</div>
</div>
</div>
<div class="mv5 flex justify-center">
<div class="frame mw6">
{{ template "frame title" "New Videos" }}
<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>
1 video has been submitted! Would you like to see it?
</div>
<div class="flex justify-end g3">
<div class="win95-btn"><u>Y</u>es</div>
</div>
</div>
</div>
</div>
</div>
<div class="frame mv4">
{{ template "frame title" "2009 iPod Touch" }}
<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>
{{ end }}

View File

@ -0,0 +1,106 @@
{{ template "timemachine_base.html" . }}
{{ define "frame title" }}
<div class="title">
{{ . }}
<img class="frame-close" src="{{ dataimg "timemachine/win95-close.gif" }}">
</div>
{{ end }}
{{ define "content" }}
<div class="center-layout content mw7 ph3 flex flex-column g3">
<div class="frame mv3 mv4-ns mw6 margin-center">
{{ template "frame title" "Submit A Video" }}
<form class="post-content" action="" method="POST">
{{ csrftoken .Session }}
<div class="post-content pa3">
<div class="flex flex-column g3">
<div class="flex g3">
<div class="flex-shrink-0">
<img class="pixelated" src="{{ dataimg "timemachine/win95-media.png" }}">
</div>
<div class="flex flex-column flex-grow-1 g1">
<strong>Video URL</strong>
<input
class="win95-input"
type="url"
name="media_url"
required
>
<div class="f7 less-spacing">
<p>
Take video of yourself using an old device. The video should:
</p>
<ul>
<li>Show the entire device so viewers have necessary context</li>
<li>Clearly show the contents of the device's screen</li>
<li>Have minimal editing, no commentary, and clear sound</li>
</ul>
<p>
Upload the video somewhere publicly accessible, then share the link here. We can download videos from file-sharing services or any video service supported by youtube-dl.
</p>
</div>
</div>
</div>
<div class="flex g3">
<div class="flex-shrink-0">
<img class="pixelated" src="{{ dataimg "timemachine/win95-checklist.png" }}">
</div>
<div class="flex flex-column flex-grow-1 g1">
<strong>Device Info</strong>
<textarea class="win95-input h4" name="device_info" required></textarea>
<div class="f7 less-spacing">
<p>
Include specific info about the device such as its model number, release year, CPU speed, amount of memory, etc.
</p>
</div>
</div>
</div>
<div class="flex g3">
<div class="flex-shrink-0">
<img class="pixelated" src="{{ dataimg "timemachine/win95-scroll.png" }}">
</div>
<div class="flex flex-column flex-grow-1 g1">
<strong>Description</strong>
<textarea class="win95-input h4" name="description" required></textarea>
<div class="f7 less-spacing">
<p>
What's the story behind this device? When did you get it? What did you use it for? Is it in good shape? Is there anything interesting or notable about it?
</p>
</div>
</div>
</div>
</div>
<div class="mt3 flex justify-end g3">
<button class="win95-btn" type="submit" value="Submit"><u>S</u>ubmit</button>
</div>
</div>
</form>
</div>
</div>
<script>
let form = document.querySelector("form");
let mediaUrl = document.querySelector("[name=media_url]");
let deviceInfo = document.querySelector("[name=device_info]");
let description = document.querySelector("[name=description]");
let submitBtn = document.querySelector("[type=submit]");
function saveData() {
localStorage.setItem("tm_media_url", mediaUrl.value);
localStorage.setItem("tm_device_info", deviceInfo.value);
localStorage.setItem("tm_description", description.value);
}
form.addEventListener("submit", function() {
saveData();
submitBtn.disabled = true;
});
document.addEventListener("visibilitychange", function() {
saveData();
});
mediaUrl.value = localStorage.getItem("tm_media_url") ?? "";
deviceInfo.value = localStorage.getItem("tm_device_info") ?? "";
description.value = localStorage.getItem("tm_description") ?? "";
</script>
{{ end }}

View File

@ -0,0 +1,36 @@
{{ template "timemachine_base.html" . }}
{{ define "frame title" }}
<div class="title">
{{ . }}
<img class="frame-close" src="{{ dataimg "timemachine/win95-close.gif" }}">
</div>
{{ end }}
{{ define "content" }}
<div class="center-layout content mw7 ph3 flex flex-column g3">
<div class="mv5 flex justify-center">
<div class="frame mw6">
{{ template "frame title" "Submission Complete" }}
<div class="pa3 flex g3">
<div class="flex-shrink-0">
<img class="pixelated" src="{{ dataimg "timemachine/win95-check.png" }}">
</div>
<div class="flex flex-column flex-grow-1 g3">
<div>
Thank you for your submission! We will review it and get it published on the site soon.
</div>
<div class="flex justify-end g3">
<a class="win95-btn" href="{{ .TimeMachineUrl }}"><u>O</u>K</a>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
localStorage.removeItem("tm_media_url");
localStorage.removeItem("tm_device_info");
localStorage.removeItem("tm_description");
</script>
{{ end }}

View File

@ -2,9 +2,12 @@ package templates
import (
"embed"
"encoding/base64"
"fmt"
"html/template"
"io"
"io/fs"
"net/http"
"regexp"
"strings"
"time"
@ -105,8 +108,24 @@ func names(ts []*template.Template) []string {
return result
}
//go:embed svg/*
var SVGs embed.FS
//go:embed img/*
var Imgs embed.FS
func GetImg(filepath string) []byte {
var imgs fs.FS
if config.Config.DevConfig.LiveTemplates {
imgs = utils.DirFS("src/templates/img")
} else {
imgs = Imgs
}
img, err := imgs.Open(filepath)
if err != nil {
panic(err)
}
return utils.Must1(io.ReadAll(img))
}
var controlCharRegex = regexp.MustCompile(`\p{Cc}`)
@ -200,7 +219,7 @@ var HMNTemplateFuncs = template.FuncMap{
}
},
"svg": func(name string) template.HTML {
contents, err := SVGs.ReadFile(fmt.Sprintf("svg/%s.svg", name))
contents, err := Imgs.ReadFile(fmt.Sprintf("img/%s.svg", name))
if err != nil {
panic("SVG not found: " + name)
}
@ -218,6 +237,12 @@ var HMNTemplateFuncs = template.FuncMap{
"staticthemenobust": func(theme string, filepath string) string {
return hmnurl.BuildTheme(filepath, theme, false)
},
"dataimg": func(filepath string) template.URL {
contents := GetImg(filepath)
mime := http.DetectContentType(contents)
contentsBase64 := base64.StdEncoding.EncodeToString(contents)
return template.URL(fmt.Sprintf("data:%s;base64,%s", mime, contentsBase64))
},
"string2uuid": func(s string) string {
return uuid.NewSHA1(uuid.NameSpaceURL, []byte(s)).URN()
},

View File

@ -67,6 +67,11 @@ func NewWebsiteRoutes(conn *pgxpool.Pool) http.Handler {
hmnOnly.GET(hmnurl.RegexJamFeed2023_Visibility, JamFeed2023_Visibility)
hmnOnly.GET(hmnurl.RegexJamRecap2023_Visibility, JamRecap2023_Visibility)
hmnOnly.GET(hmnurl.RegexTimeMachine, TimeMachine)
hmnOnly.GET(hmnurl.RegexTimeMachineForm, needsAuth(TimeMachineForm))
hmnOnly.GET(hmnurl.RegexTimeMachineFormDone, needsAuth(TimeMachineFormDone))
hmnOnly.POST(hmnurl.RegexTimeMachineForm, needsAuth(csrfMiddleware(TimeMachineFormSubmit)))
hmnOnly.GET(hmnurl.RegexStaffRolesIndex, StaffRolesIndex)
hmnOnly.GET(hmnurl.RegexStaffRole, StaffRole)

View File

@ -0,0 +1,96 @@
package website
import (
"net/http"
"strings"
"git.handmade.network/hmn/hmn/src/email"
"git.handmade.network/hmn/hmn/src/hmnurl"
"git.handmade.network/hmn/hmn/src/oops"
"git.handmade.network/hmn/hmn/src/templates"
)
func TimeMachine(c *RequestContext) ResponseData {
baseData := getBaseDataAutocrumb(c, "Time Machine")
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)},
}
type TemplateData struct {
templates.BaseData
SubmitUrl string
}
tmpl := TemplateData{
BaseData: baseData,
SubmitUrl: hmnurl.BuildTimeMachineForm(),
}
var res ResponseData
res.MustWriteTemplate("timemachine.html", tmpl, c.Perf)
return res
}
func TimeMachineForm(c *RequestContext) ResponseData {
var res ResponseData
res.MustWriteTemplate(
"timemachine_submit.html",
getBaseDataAutocrumb(c, "Time Machine"),
c.Perf,
)
return res
}
func TimeMachineFormSubmit(c *RequestContext) ResponseData {
c.Req.ParseForm()
mediaUrl := strings.TrimSpace(c.Req.Form.Get("media_url"))
deviceInfo := strings.TrimSpace(c.Req.Form.Get("device_info"))
description := strings.TrimSpace(c.Req.Form.Get("description"))
discordUsername := ""
if c.CurrentUser.DiscordUser != nil {
discordUsername = c.CurrentUser.DiscordUser.Username
}
err := email.SendTimeMachineEmail(
hmnurl.BuildUserProfile(c.CurrentUser.Username),
c.CurrentUser.BestName(),
c.CurrentUser.Email,
discordUsername,
mediaUrl,
deviceInfo,
description,
c.Perf,
)
if err != nil {
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to send time machine email"))
}
return c.Redirect(hmnurl.BuildTimeMachineFormDone(), http.StatusSeeOther)
}
func TimeMachineFormDone(c *RequestContext) ResponseData {
type TemplateData struct {
templates.BaseData
TimeMachineUrl string
}
tmpl := TemplateData{
BaseData: getBaseDataAutocrumb(c, "Time Machine"),
TimeMachineUrl: hmnurl.BuildTimeMachine(),
}
var res ResponseData
res.MustWriteTemplate(
"timemachine_thanks.html",
tmpl,
c.Perf,
)
return res
}