Showcase page
This commit is contained in:
parent
77273cdb33
commit
8aa4554934
|
@ -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 |
|
@ -0,0 +1,124 @@
|
|||
const TimelineTypes = {
|
||||
UNKNOWN: 0,
|
||||
FORUM_THREAD: 1,
|
||||
FORUM_REPLY: 2,
|
||||
BLOG_POST: 3,
|
||||
BLOG_COMMENT: 4,
|
||||
WIKI_CREATE: 5,
|
||||
WIKI_EDIT: 6,
|
||||
WIKI_TALK: 7,
|
||||
LIBRARY_COMMENT: 8,
|
||||
SNIPPET_IMAGE: 9,
|
||||
SNIPPET_VIDEO: 10,
|
||||
SNIPPET_AUDIO: 11,
|
||||
SNIPPET_YOUTUBE: 12
|
||||
};
|
||||
|
||||
const showcaseItemTemplate = makeTemplateCloner("showcase_item");
|
||||
const modalTemplate = makeTemplateCloner("timeline_modal");
|
||||
|
||||
function showcaseTimestamp(rawDate) {
|
||||
const date = new Date(rawDate*1000);
|
||||
return date.toLocaleDateString([], { 'dateStyle': 'long' });
|
||||
}
|
||||
|
||||
function doOnce(f) {
|
||||
let did = false;
|
||||
return () => {
|
||||
if (!did) {
|
||||
f();
|
||||
did = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function makeShowcaseItem(timelineItem) {
|
||||
const timestamp = showcaseTimestamp(timelineItem.date);
|
||||
|
||||
const itemEl = showcaseItemTemplate();
|
||||
itemEl.avatar.style.backgroundImage = `url('${timelineItem.owner_avatar}')`;
|
||||
itemEl.username.textContent = timelineItem.owner_name;
|
||||
itemEl.when.textContent = timestamp;
|
||||
|
||||
let addThumbnailFunc = () => {};
|
||||
let createModalContentFunc = () => {};
|
||||
|
||||
switch (timelineItem.type) {
|
||||
case TimelineTypes.SNIPPET_IMAGE:
|
||||
addThumbnailFunc = () => {
|
||||
itemEl.thumbnail.style.backgroundImage = `url('${timelineItem.asset_url}')`;
|
||||
};
|
||||
|
||||
createModalContentFunc = () => {
|
||||
const modalImage = document.createElement('img');
|
||||
modalImage.src = timelineItem.asset_url;
|
||||
modalImage.classList.add('mw-100', 'mh-60vh');
|
||||
return modalImage;
|
||||
};
|
||||
|
||||
break;
|
||||
case TimelineTypes.SNIPPET_VIDEO:
|
||||
addThumbnailFunc = () => {
|
||||
const video = document.createElement('video');
|
||||
video.src = timelineItem.asset_url;
|
||||
video.controls = false;
|
||||
video.classList.add('h-100');
|
||||
video.preload = 'metadata';
|
||||
itemEl.thumbnail.appendChild(video);
|
||||
};
|
||||
|
||||
createModalContentFunc = () => {
|
||||
const modalVideo = document.createElement('video');
|
||||
modalVideo.src = timelineItem.asset_url;
|
||||
modalVideo.controls = true;
|
||||
modalVideo.preload = 'metadata';
|
||||
modalVideo.classList.add('mw-100', 'mh-60vh');
|
||||
return modalVideo;
|
||||
};
|
||||
|
||||
break;
|
||||
case TimelineTypes.SNIPPET_AUDIO:
|
||||
createModalContentFunc = () => {
|
||||
const modalAudio = document.createElement('audio');
|
||||
modalAudio.src = timelineItem.asset_url;
|
||||
modalAudio.controls = true;
|
||||
modalAudio.preload = 'metadata';
|
||||
modalAudio.classList.add('w-70');
|
||||
return modalAudio;
|
||||
};
|
||||
|
||||
break;
|
||||
// TODO(ben): Other snippet types?
|
||||
}
|
||||
|
||||
let modalEl = null;
|
||||
itemEl.container.addEventListener('click', function() {
|
||||
if (!modalEl) {
|
||||
modalEl = modalTemplate();
|
||||
modalEl.description.innerHTML = timelineItem.description;
|
||||
modalEl.asset_container.appendChild(createModalContentFunc());
|
||||
|
||||
modalEl.avatar.src = timelineItem.owner_avatar;
|
||||
modalEl.userLink.textContent = timelineItem.owner_name;
|
||||
modalEl.userLink.href = timelineItem.owner_url;
|
||||
modalEl.date.textContent = timestamp;
|
||||
modalEl.date.setAttribute("href", timelineItem.snippet_url);
|
||||
|
||||
modalEl.discord_link.href = timelineItem.discord_message_url;
|
||||
|
||||
function close() {
|
||||
modalEl.overlay.remove();
|
||||
}
|
||||
modalEl.overlay.addEventListener('click', close);
|
||||
modalEl.close.addEventListener('click', close);
|
||||
modalEl.container.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
}
|
||||
|
||||
document.body.appendChild(modalEl.overlay);
|
||||
});
|
||||
|
||||
return [itemEl, doOnce(addThumbnailFunc)];
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
var domLookupCache = {};
|
||||
var templatePathCache = {};
|
||||
|
||||
function getTemplateEl(id) {
|
||||
if (!domLookupCache[id]) {
|
||||
domLookupCache[id] = document.getElementById(id);
|
||||
}
|
||||
return domLookupCache[id];
|
||||
}
|
||||
|
||||
function collectElements(paths, rootElement) {
|
||||
var result = {};
|
||||
for (var i = 0; i < paths.length; ++i) {
|
||||
var path = paths[i];
|
||||
var current = rootElement;
|
||||
for (var j = 0; j < path[1].length; ++j) {
|
||||
current = current.children[path[1][j]];
|
||||
}
|
||||
result[path[0]] = current;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function getTemplatePaths(id, rootElement) {
|
||||
if (!templatePathCache[id]) {
|
||||
var paths = [];
|
||||
paths.push(["root", []]);
|
||||
|
||||
function descend(path, el) {
|
||||
for (var i = 0; i < el.children.length; ++i) {
|
||||
var child = el.children[i];
|
||||
var childPath = path.concat([i]);
|
||||
var tmplName = child.getAttribute("data-tmpl");
|
||||
if (tmplName) {
|
||||
paths.push([tmplName, childPath]);
|
||||
}
|
||||
if (child.children.length > 0) {
|
||||
descend(childPath, child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
descend([], rootElement);
|
||||
templatePathCache[id] = paths;
|
||||
}
|
||||
return templatePathCache[id];
|
||||
}
|
||||
|
||||
function makeTemplateCloner(id) {
|
||||
return function() {
|
||||
var templateEl = getTemplateEl(id);
|
||||
if (templateEl === null) {
|
||||
throw new Error(`Couldn\'t find template with ID '${id}'`);
|
||||
}
|
||||
|
||||
var root = templateEl.content.cloneNode(true);
|
||||
var paths = getTemplatePaths(id, root);
|
||||
var result = collectElements(paths, root);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
function emptyElement(el) {
|
||||
var newEl = el.cloneNode(false);
|
||||
el.parentElement.insertBefore(newEl, el);
|
||||
el.parentElement.removeChild(el);
|
||||
return newEl;
|
||||
}
|
|
@ -4,6 +4,8 @@ import (
|
|||
"html/template"
|
||||
"net"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.handmade.network/hmn/hmn/src/hmnurl"
|
||||
"git.handmade.network/hmn/hmn/src/models"
|
||||
|
@ -223,6 +225,72 @@ func LinkToTemplate(link *models.Link) Link {
|
|||
}
|
||||
}
|
||||
|
||||
func TimelineItemsToJSON(items []TimelineItem) string {
|
||||
// NOTE(asaf): As of 2021-06-22: This only serializes the data necessary for snippet showcase.
|
||||
builder := strings.Builder{}
|
||||
builder.WriteRune('[')
|
||||
for i, item := range items {
|
||||
if i > 0 {
|
||||
builder.WriteRune(',')
|
||||
}
|
||||
builder.WriteRune('{')
|
||||
|
||||
builder.WriteString(`"type":`)
|
||||
builder.WriteString(strconv.Itoa(int(item.Type)))
|
||||
builder.WriteRune(',')
|
||||
|
||||
builder.WriteString(`"date":`)
|
||||
builder.WriteString(strconv.FormatInt(item.Date.UTC().Unix(), 10))
|
||||
builder.WriteRune(',')
|
||||
|
||||
builder.WriteString(`"description":"`)
|
||||
jsonString := string(item.Description)
|
||||
jsonString = strings.ReplaceAll(jsonString, `\`, `\\`)
|
||||
jsonString = strings.ReplaceAll(jsonString, `"`, `\"`)
|
||||
jsonString = strings.ReplaceAll(jsonString, "\n", "\\n")
|
||||
jsonString = strings.ReplaceAll(jsonString, "\r", "\\r")
|
||||
jsonString = strings.ReplaceAll(jsonString, "\t", "\\t")
|
||||
builder.WriteString(jsonString)
|
||||
builder.WriteString(`",`)
|
||||
|
||||
builder.WriteString(`"owner_name":"`)
|
||||
builder.WriteString(item.OwnerName)
|
||||
builder.WriteString(`",`)
|
||||
|
||||
builder.WriteString(`"owner_avatar":"`)
|
||||
builder.WriteString(item.OwnerAvatarUrl)
|
||||
builder.WriteString(`",`)
|
||||
|
||||
builder.WriteString(`"owner_url":"`)
|
||||
builder.WriteString(item.OwnerUrl)
|
||||
builder.WriteString(`",`)
|
||||
|
||||
builder.WriteString(`"snippet_url":"`)
|
||||
builder.WriteString(item.Url)
|
||||
builder.WriteString(`",`)
|
||||
|
||||
builder.WriteString(`"width":`)
|
||||
builder.WriteString(strconv.Itoa(item.Width))
|
||||
builder.WriteRune(',')
|
||||
|
||||
builder.WriteString(`"height":`)
|
||||
builder.WriteString(strconv.Itoa(item.Height))
|
||||
builder.WriteRune(',')
|
||||
|
||||
builder.WriteString(`"asset_url":"`)
|
||||
builder.WriteString(item.AssetUrl)
|
||||
builder.WriteString(`",`)
|
||||
|
||||
builder.WriteString(`"discord_message_url":"`)
|
||||
builder.WriteString(item.DiscordMessageUrl)
|
||||
builder.WriteString(`"`)
|
||||
|
||||
builder.WriteRune('}')
|
||||
}
|
||||
builder.WriteRune(']')
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
func maybeString(s *string) string {
|
||||
if s == nil {
|
||||
return ""
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
<template id="showcase_item">
|
||||
<div data-tmpl="container" class="showcase-item ba b--theme flex-shrink-0 bg-dark-gray hide-child relative overflow-hidden pointer">
|
||||
<div data-tmpl="thumbnail" class="absolute absolute--fill z-0 flex justify-start items-center bg-left cover"></div>
|
||||
<div class="overlay absolute absolute--fill z-1 child">
|
||||
<div class="gradient relative">
|
||||
<div class="user-info flex pa2 white f7 lh-title items-center">
|
||||
<div data-tmpl="avatar" class="br-100 w2 h2 cover flex-shrink-0"></div>
|
||||
<div class="flex-grow-1 flex flex-column pl1">
|
||||
<div data-tmpl="username">Unknown User</div>
|
||||
<div data-tmpl="when" class="i f8">Unknown Time</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="timeline_modal">
|
||||
<div data-tmpl="overlay" class="timeline-modal fixed absolute--fill bg-black-80 z-999 flex flex-column justify-center items-center">
|
||||
<div data-tmpl="container" class="container timeline-item relative flex-shrink-0 shadow-1 flex flex-column items-stretch w-100 mh0 mh3-ns br2-ns overflow-hidden">
|
||||
<div data-tmpl="asset_container" class="bg-dark-gray flex justify-center"></div>
|
||||
<div class="bg--content pa3 overflow-y-auto">
|
||||
<div class="timeline-user-info mb2 flex items-center">
|
||||
<img class="avatar-icon lite mr2" data-tmpl="avatar"/>
|
||||
<a class="user" data-tmpl="userLink"></a>
|
||||
<a data-tmpl="date" class="datetime tr" style="flex: 1 1 auto;"></a>
|
||||
</div>
|
||||
<div data-tmpl="description">
|
||||
Unknown description
|
||||
</div>
|
||||
<div class="i f7 pt2">
|
||||
<a data-tmpl="discord_link" target="_blank">View original message on Discord</a>
|
||||
</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>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,171 @@
|
|||
{{ template "base.html" . }}
|
||||
|
||||
{{ define "extrahead" }}
|
||||
<script src="{{ static "js/templates.js" }}"></script>
|
||||
<script src="{{ static "js/showcase.js" }}"></script>
|
||||
{{ end }}
|
||||
|
||||
{{ define "content" }}
|
||||
<div class="content-block">
|
||||
<div class="ph2 ph0-ns pb4">
|
||||
<div class="optionbar">
|
||||
<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="{{ .ShowcaseAtomFeedUrl }}"><span class="icon big">4</span> Showcase Feed</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div id="showcase-container" class="mh2 mh0-ns pb4"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ template "showcase_templates.html" }}
|
||||
|
||||
<template id="showcase-month">
|
||||
<h3 data-tmpl="dateHeader" class="mt3 f4 fw5">Unknown Date</h3>
|
||||
<div data-tmpl="itemsContainer" class="month-container"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const ROW_HEIGHT = 300;
|
||||
const ITEM_SPACING = 4;
|
||||
|
||||
const monthTemplate = makeTemplateCloner('showcase-month');
|
||||
|
||||
const showcaseItems = JSON.parse("{{ .ShowcaseItems }}");
|
||||
const addThumbnailFuncs = new Array(showcaseItems.length);
|
||||
|
||||
let showcaseContainer = document.querySelector('#showcase-container');
|
||||
|
||||
const itemElementsByMonth = []; // array of arrays
|
||||
let currentMonthElements = [];
|
||||
let currentMonth = null;
|
||||
let currentYear = null;
|
||||
for (let i = 0; i < showcaseItems.length; i++) {
|
||||
const item = showcaseItems[i];
|
||||
const date = new Date(item.date * 1000); // TODO(asaf): Verify that this is still correct with our new JSON marshalling
|
||||
|
||||
if (date.getMonth() !== currentMonth || date.getFullYear() !== currentYear) {
|
||||
if (currentMonthElements.length > 0) {
|
||||
itemElementsByMonth.push(currentMonthElements);
|
||||
}
|
||||
|
||||
currentMonthElements = [];
|
||||
currentMonth = date.getMonth();
|
||||
currentYear = date.getFullYear();
|
||||
}
|
||||
|
||||
const [itemEl, addThumbnail] = makeShowcaseItem(item);
|
||||
itemEl.container.setAttribute('data-index', i);
|
||||
itemEl.container.setAttribute('data-date', item.date);
|
||||
|
||||
addThumbnailFuncs[i] = addThumbnail;
|
||||
|
||||
currentMonthElements.push(itemEl.container);
|
||||
}
|
||||
if (currentMonthElements.length > 0) {
|
||||
itemElementsByMonth.push(currentMonthElements);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
for (const monthEls of itemElementsByMonth) {
|
||||
const month = monthTemplate();
|
||||
|
||||
const firstDate = new Date(parseFloat(monthEls[0].getAttribute('data-date')) * 1000);
|
||||
month.dateHeader.textContent = firstDate.toLocaleDateString([], { month: 'long', year: 'numeric' });
|
||||
|
||||
let rowItemEls = [];
|
||||
let rowWidth = 0;
|
||||
|
||||
for (const itemEl of monthEls) {
|
||||
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, month.itemsContainer);
|
||||
|
||||
rowItemEls = [];
|
||||
rowWidth = 0;
|
||||
}
|
||||
}
|
||||
|
||||
addRow(rowItemEls, rowWidth, month.itemsContainer);
|
||||
|
||||
showcaseContainer.appendChild(month.root);
|
||||
}
|
||||
}
|
||||
|
||||
function tryLoadImages() {
|
||||
const OFFSCREEN_THRESHOLD = 0;
|
||||
|
||||
const months = document.querySelectorAll('.month-container');
|
||||
for (const month of months) {
|
||||
const rect = month.getBoundingClientRect();
|
||||
const offscreen = (
|
||||
rect.bottom < -OFFSCREEN_THRESHOLD
|
||||
|| rect.top > window.innerHeight + OFFSCREEN_THRESHOLD
|
||||
);
|
||||
|
||||
if (!offscreen) {
|
||||
const items = month.querySelectorAll('.showcase-item');
|
||||
for (const item of items) {
|
||||
const i = parseInt(item.getAttribute('data-index'), 10);
|
||||
addThumbnailFuncs[i]();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
layout();
|
||||
layout(); // scrollbars are fun!!
|
||||
|
||||
tryLoadImages();
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
layout();
|
||||
tryLoadImages();
|
||||
});
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
tryLoadImages();
|
||||
});
|
||||
</script>
|
||||
{{ end }}
|
|
@ -107,6 +107,7 @@ func NewWebsiteRoutes(conn *pgxpool.Pool, perfCollector *perf.PerfCollector) htt
|
|||
// NOTE(asaf): HMN-only routes:
|
||||
mainRoutes.GET(hmnurl.RegexFeed, Feed)
|
||||
mainRoutes.GET(hmnurl.RegexAtomFeed, AtomFeed)
|
||||
mainRoutes.GET(hmnurl.RegexShowcase, Showcase)
|
||||
mainRoutes.GET(hmnurl.RegexProjectIndex, ProjectIndex)
|
||||
mainRoutes.GET(hmnurl.RegexUserProfile, UserProfile)
|
||||
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
package website
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"git.handmade.network/hmn/hmn/src/db"
|
||||
"git.handmade.network/hmn/hmn/src/hmnurl"
|
||||
"git.handmade.network/hmn/hmn/src/models"
|
||||
"git.handmade.network/hmn/hmn/src/oops"
|
||||
"git.handmade.network/hmn/hmn/src/templates"
|
||||
)
|
||||
|
||||
type ShowcaseData struct {
|
||||
templates.BaseData
|
||||
ShowcaseItems string // NOTE(asaf): JSON string
|
||||
ShowcaseAtomFeedUrl string
|
||||
}
|
||||
|
||||
func Showcase(c *RequestContext) ResponseData {
|
||||
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")
|
||||
jsonItems := templates.TimelineItemsToJSON(showcaseItems)
|
||||
c.Perf.EndBlock()
|
||||
|
||||
baseData := getBaseData(c)
|
||||
baseData.Title = "Community Showcase"
|
||||
var res ResponseData
|
||||
err = res.WriteTemplate("showcase.html", ShowcaseData{
|
||||
BaseData: baseData,
|
||||
ShowcaseItems: jsonItems,
|
||||
ShowcaseAtomFeedUrl: hmnurl.BuildAtomFeedForShowcase(),
|
||||
}, c.Perf)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return res
|
||||
}
|
Loading…
Reference in New Issue