Showcase on landing page

This commit is contained in:
Asaf Gartner 2021-06-23 22:31:59 +03:00
parent 8aa4554934
commit 5162e7fba9
7 changed files with 212 additions and 8 deletions

View File

@ -32,7 +32,7 @@
<a data-tmpl="discord_link" target="_blank">View original message on Discord</a> <a data-tmpl="discord_link" target="_blank">View original message on Discord</a>
</div> </div>
</div> </div>
<div data-tmpl="close" class="absolute right-0 top-0 w2 h2 flex justify-center items-center bg-black-80 white br-100 ma1 pointer svgicon svgicon-nofix"><img src="{{ static "close.svg" }}" /></div> <div data-tmpl="close" class="absolute right-0 top-0 w2 h2 flex justify-center items-center bg-black-80 white br-100 ma1 pointer svgicon svgicon-nofix">{{ svg "close" }}</div>
</div> </div>
</div> </div>
</template> </template>

View File

@ -21,14 +21,82 @@
{{ end }} {{ end }}
{{ end }} {{ end }}
</style> </style>
{{/* <script type="text/javascript" src="{{ static "js/templates.js" }}"></script>
<script type="text/javascript" src="{% static 'templates.js' %}?v={% cachebust %}"></script> <script type="text/javascript" src="{{ static "js/showcase.js" }}"></script>
<script type="text/javascript" src="{% static 'timeline.js' %}?v={% cachebust %}"></script>
<script type="text/javascript" src="{% static 'showcase.js' %}?v={% cachebust %}"></script>
*/}}
{{ end }} {{ end }}
{{ define "content" }} {{ define "content" }}
{{ template "showcase_templates.html" }}
{{ if .ShowcaseTimelineJson }}
<div class="content-block pb3">
<div class="tc tl-l w-100 pb2">
<h2 class="di-l mr2-l">Community Showcase</h2>
<ul class="list dib-l">
<li class="dib-ns ma0 ph2">
<a href="{{ .ShowcaseUrl }}">View all</a>
</li>
</ul>
</div>
<div class="showcase relative overflow-hidden">
<div id="showcase-items" class="flex relative pl3 pl0-ns"></div>
<div class="arrow-container left">
<a href="javascript:void(0)" class="arrow svgicon svgicon-nofix" onclick="scrollShowcase('left')">{{ svg "chevron-left" }}</a>
</div>
<div class="arrow-container right">
<a href="javascript:void(0)" class="arrow svgicon svgicon-nofix" onclick="scrollShowcase('right')">{{ svg "chevron-right" }}</a>
</div>
</div>
<div class="c--dimmer i pv2 ph3 ph0-ns">
This is a selection of recent work done by community members. Want to participate? <a href="{{ .DiscordUrl }}" target="_blank">Join us on Discord.</a>
</div>
</div>
<script>
const timelineData = JSON.parse("{{ .ShowcaseTimelineJson }}");
const showcaseEl = document.querySelector('#showcase-items');
for (const item of timelineData) {
const [itemEl, addThumbnail] = makeShowcaseItem(item);
addThumbnail();
itemEl.container.classList.add('mr3');
showcaseEl.appendChild(itemEl.root);
}
function rem2px(rem) {
return rem * parseFloat(getComputedStyle(document.documentElement).fontSize);
}
function scrollShowcase(direction = null) {
const ITEM_WIDTH = showcaseEl.querySelector('.showcase-item').getBoundingClientRect().width;
const ITEM_SPACING = rem2px(1);
const showcaseWidth = showcaseEl.getBoundingClientRect().width;
const numVisible = showcaseWidth / (ITEM_WIDTH + ITEM_SPACING);
const scrollMagnitude = Math.floor(numVisible) - 1;
const scrollDirection = (direction === 'right' ? 1 : (direction === 'left' ? -1 : 0));
const scrollAmount = scrollMagnitude * scrollDirection;
const minIndex = 0;
const maxIndex = timelineData.length - Math.floor(numVisible);
const currentScrollIndex = parseInt(showcaseEl.getAttribute('data-scroll-index'), 10) || 0;
const newScrollIndex = Math.max(minIndex, Math.min(maxIndex, currentScrollIndex + scrollAmount));
showcaseEl.style.transform = `translateX(${-newScrollIndex * (ITEM_WIDTH + ITEM_SPACING)}px)`;
showcaseEl.setAttribute('data-scroll-index', newScrollIndex);
const leftArrowEl = document.querySelector('.arrow-container.left');
const rightArrowEl = document.querySelector('.arrow-container.right');
leftArrowEl.classList.toggle('hide', newScrollIndex === minIndex);
rightArrowEl.classList.toggle('hide', newScrollIndex === maxIndex);
}
scrollShowcase(); // force a scroll as an easy way to initialize styles
window.addEventListener('resize', () => scrollShowcase());
</script>
{{ end }}
<div class="content-block"> <div class="content-block">
<div class="optionbar pb2"> <div class="optionbar pb2">
<div class="tc tl-l w-100"> <div class="tc tl-l w-100">

View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Capa_1"
viewBox="0 0 34.075 34.075"
sodipodi:docname="chevron-left.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<metadata
id="metadata3746">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs3744" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1017"
id="namedview3742"
showgrid="false"
inkscape:zoom="1.7314747"
inkscape:cx="77.681159"
inkscape:cy="112.3003"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="Capa_1" />
<g
id="g3739">
<path
d="M24.57,34.075c-0.505,0-1.011-0.191-1.396-0.577L8.11,18.432c-0.771-0.771-0.771-2.019,0-2.79 L23.174,0.578c0.771-0.771,2.02-0.771,2.791,0s0.771,2.02,0,2.79l-13.67,13.669l13.67,13.669c0.771,0.771,0.771,2.021,0,2.792 C25.58,33.883,25.075,34.075,24.57,34.075z"
id="path3737" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 185.343 185.343">
<g>
<path d="M51.707,185.343c-2.741,0-5.493-1.044-7.593-3.149c-4.194-4.194-4.194-10.981,0-15.175
l74.352-74.347L44.114,18.32c-4.194-4.194-4.194-10.987,0-15.175c4.194-4.194,10.987-4.194,15.18,0l81.934,81.934
c4.194,4.194,4.194,10.987,0,15.175l-81.934,81.939C57.201,184.293,54.454,185.343,51.707,185.343z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 614 B

11
src/templates/svg/close.svg Executable file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 47.971 47.971">
<g>
<path d="M28.228,23.986L47.092,5.122c1.172-1.171,1.172-3.071,0-4.242c-1.172-1.172-3.07-1.172-4.242,0L23.986,19.744L5.121,0.88
c-1.172-1.172-3.07-1.172-4.242,0c-1.172,1.171-1.172,3.071,0,4.242l18.865,18.864L0.879,42.85c-1.172,1.171-1.172,3.071,0,4.242
C1.465,47.677,2.233,47.97,3,47.97s1.535-0.293,2.121-0.879l18.865-18.864L42.85,47.091c0.586,0.586,1.354,0.879,2.121,0.879
s1.535-0.293,2.121-0.879c1.172-1.171,1.172-3.071,0-4.242L28.228,23.986z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 776 B

View File

@ -71,6 +71,21 @@ func names(ts []*template.Template) []string {
return result return result
} }
//go:embed svg/close.svg
var SVGClose string
//go:embed svg/chevron-left.svg
var SVGChevronLeft string
//go:embed svg/chevron-right.svg
var SVGChevronRight string
var SVGMap = map[string]string{
"close": SVGClose,
"chevron-left": SVGChevronLeft,
"chevron-right": SVGChevronRight,
}
var HMNTemplateFuncs = template.FuncMap{ var HMNTemplateFuncs = template.FuncMap{
"add": func(a int, b ...int) int { "add": func(a int, b ...int) int {
for _, num := range b { for _, num := range b {
@ -148,6 +163,13 @@ var HMNTemplateFuncs = template.FuncMap{
return str(int(delta/Yearish), "year", int((delta%Yearish)/Monthish), "month") return str(int(delta/Yearish), "year", int((delta%Yearish)/Monthish), "month")
} }
}, },
"svg": func(name string) template.HTML {
contents, found := SVGMap[name]
if !found {
panic("SVG not found: " + name)
}
return template.HTML(contents)
},
"static": func(filepath string) string { "static": func(filepath string) string {
return hmnurl.BuildPublic(filepath, true) return hmnurl.BuildPublic(filepath, true)
}, },

View File

@ -278,6 +278,42 @@ func Index(c *RequestContext) ResponseData {
newsPostResult := newsPostRow.(*newsPostQuery) newsPostResult := newsPostRow.(*newsPostQuery)
c.Perf.EndBlock() c.Perf.EndBlock()
c.Perf.StartBlock("SQL", "Fetch showcase snippets")
type snippetQuery struct {
Owner models.User `db:"owner"`
Snippet models.Snippet `db:"snippet"`
Asset *models.Asset `db:"asset"`
DiscordMessage *models.DiscordMessage `db:"discord_message"`
}
snippetQueryResult, err := db.Query(c.Context(), c.Conn, snippetQuery{},
`
SELECT $columns
FROM
handmade_snippet AS snippet
INNER JOIN auth_user AS owner ON owner.id = snippet.owner_id
LEFT JOIN handmade_asset AS asset ON asset.id = snippet.asset_id
LEFT JOIN handmade_discordmessage AS discord_message ON discord_message.id = snippet.discord_message_id
ORDER BY snippet.when DESC
`,
)
if err != nil {
return ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch snippets"))
}
snippetQuerySlice := snippetQueryResult.ToSlice()
showcaseItems := make([]templates.TimelineItem, 0, len(snippetQuerySlice))
for _, s := range snippetQuerySlice {
row := s.(*snippetQuery)
timelineItem := SnippetToTimelineItem(&row.Snippet, row.Asset, row.DiscordMessage, &row.Owner, c.Theme)
if timelineItem.Type != templates.TimelineTypeSnippetYoutube {
showcaseItems = append(showcaseItems, timelineItem)
}
}
c.Perf.EndBlock()
c.Perf.StartBlock("SHOWCASE", "Convert to json")
showcaseJson := templates.TimelineItemsToJSON(showcaseItems)
c.Perf.EndBlock()
baseData := getBaseData(c) baseData := getBaseData(c)
baseData.BodyClasses = append(baseData.BodyClasses, "hmdev", "landing") // TODO: Is "hmdev" necessary any more? baseData.BodyClasses = append(baseData.BodyClasses, "hmdev", "landing") // TODO: Is "hmdev" necessary any more?
@ -299,10 +335,11 @@ func Index(c *RequestContext) ResponseData {
Unread: true, // TODO Unread: true, // TODO
Content: template.HTML(newsPostResult.PostVersion.TextParsed), Content: template.HTML(newsPostResult.PostVersion.TextParsed),
}, },
PostColumns: cols, PostColumns: cols,
ShowcaseTimelineJson: showcaseJson,
}, c.Perf) }, c.Perf)
if err != nil { if err != nil {
panic(err) return ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to render landing page template"))
} }
return res return res