Showcase on landing page
This commit is contained in:
parent
8aa4554934
commit
5162e7fba9
|
@ -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>
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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)
|
||||||
},
|
},
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue