3884 lines
133 KiB
JavaScript
3884 lines
133 KiB
JavaScript
// Processing (Searching / Filtering)
|
||
//
|
||
var CineraProps = {
|
||
IsMobile: IsMobile(),
|
||
Orientation: null,
|
||
};
|
||
|
||
var Search = {
|
||
ResultsSummary: null,
|
||
ResultsContainer: null,
|
||
ProjectsContainer: null,
|
||
QueryElement: null,
|
||
|
||
Projects: [],
|
||
|
||
LastQuery: null,
|
||
MarkerList: null,
|
||
ProjectContainer: null,
|
||
ResultsMarkerIndex: -1,
|
||
RenderHandle: undefined,
|
||
Rendering: false,
|
||
|
||
Prototypes: {
|
||
ProjectContainer: null,
|
||
DayContainer: null,
|
||
Marker: null,
|
||
Highlight: null,
|
||
}
|
||
};
|
||
|
||
function hideEntriesOfProject(ProjectElement)
|
||
{
|
||
if(!ProjectElement.classList.contains("off"))
|
||
{
|
||
ProjectElement.classList.add("off");
|
||
}
|
||
var baseURL = ProjectElement.attributes.getNamedItem("data-baseURL").value;
|
||
var searchLocation = ProjectElement.attributes.getNamedItem("data-searchLocation").value;
|
||
var playerLocation = ProjectElement.attributes.getNamedItem("data-playerLocation").value;
|
||
for(var i = 0; i < Search.Projects.length; ++i)
|
||
{
|
||
var ThisProject = Search.Projects[i];
|
||
if(baseURL === ThisProject.baseURL && searchLocation === ThisProject.searchLocation && playerLocation === ThisProject.playerLocation)
|
||
{
|
||
ThisProject.filteredOut = true;
|
||
if(ThisProject.entriesContainer != null)
|
||
{
|
||
ThisProject.entriesContainer.style.display = "none";
|
||
disableSprite(ThisProject.entriesContainer.parentElement);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
function showEntriesOfProject(ProjectElement)
|
||
{
|
||
if(ProjectElement.classList.contains("off"))
|
||
{
|
||
ProjectElement.classList.remove("off");
|
||
}
|
||
var baseURL = ProjectElement.attributes.getNamedItem("data-baseURL").value;
|
||
var searchLocation = ProjectElement.attributes.getNamedItem("data-searchLocation").value;
|
||
var playerLocation = ProjectElement.attributes.getNamedItem("data-playerLocation").value;
|
||
for(var i = 0; i < Search.Projects.length; ++i)
|
||
{
|
||
var ThisProject = Search.Projects[i];
|
||
if(baseURL === ThisProject.baseURL && searchLocation === ThisProject.searchLocation && playerLocation === ThisProject.playerLocation)
|
||
{
|
||
ThisProject.filteredOut = false;
|
||
if(ThisProject.entriesContainer != null)
|
||
{
|
||
ThisProject.entriesContainer.style.display = "flex";
|
||
enableSprite(ThisProject.entriesContainer.parentElement);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
function hideProjectSearchResults(baseURL, searchLocation, playerLocation)
|
||
{
|
||
var cineraResults = document.getElementById("cineraResults");
|
||
if(cineraResults)
|
||
{
|
||
var cineraResultsProjects = cineraResults.querySelectorAll(".projectContainer");
|
||
for(var i = 0; i < cineraResultsProjects.length; ++i)
|
||
{
|
||
var resultBaseURL = cineraResultsProjects[i].attributes.getNamedItem("data-baseURL").value;
|
||
var resultSearchLocation = cineraResultsProjects[i].attributes.getNamedItem("data-searchLocation").value;
|
||
var resultPlayerLocation = cineraResultsProjects[i].attributes.getNamedItem("data-playerLocation").value;
|
||
if(baseURL === resultBaseURL && searchLocation === resultSearchLocation && playerLocation === resultPlayerLocation)
|
||
{
|
||
cineraResultsProjects[i].style.display = "none";
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
function showProjectSearchResults(baseURL, searchLocation, playerLocation)
|
||
{
|
||
var cineraResults = document.getElementById("cineraResults");
|
||
if(cineraResults)
|
||
{
|
||
var cineraResultsProjects = cineraResults.querySelectorAll(".projectContainer");
|
||
for(var i = 0; i < cineraResultsProjects.length; ++i)
|
||
{
|
||
var resultBaseURL = cineraResultsProjects[i].attributes.getNamedItem("data-baseURL").value;
|
||
var resultSearchLocation = cineraResultsProjects[i].attributes.getNamedItem("data-searchLocation").value;
|
||
var resultPlayerLocation = cineraResultsProjects[i].attributes.getNamedItem("data-playerLocation").value;
|
||
if(baseURL === resultBaseURL && searchLocation === resultSearchLocation && playerLocation === resultPlayerLocation)
|
||
{
|
||
cineraResultsProjects[i].style.display = "flex";
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
function toggleEntriesOfProjectAndChildren(ProjectFilterElement)
|
||
{
|
||
var baseURL = ProjectFilterElement.attributes.getNamedItem("data-baseURL").value;
|
||
var searchLocation = ProjectFilterElement.attributes.getNamedItem("data-searchLocation").value;
|
||
var playerLocation = ProjectFilterElement.attributes.getNamedItem("data-playerLocation").value;
|
||
var shouldShow = ProjectFilterElement.classList.contains("off");
|
||
if(shouldShow)
|
||
{
|
||
ProjectFilterElement.classList.remove("off");
|
||
enableSprite(ProjectFilterElement);
|
||
}
|
||
else
|
||
{
|
||
ProjectFilterElement.classList.add("off");
|
||
disableSprite(ProjectFilterElement);
|
||
}
|
||
|
||
for(var i = 0; i < Search.Projects.length; ++i)
|
||
{
|
||
var ThisProject = Search.Projects[i];
|
||
|
||
if(baseURL === ThisProject.baseURL && searchLocation === ThisProject.searchLocation && playerLocation === ThisProject.playerLocation)
|
||
{
|
||
if(shouldShow)
|
||
{
|
||
ThisProject.filteredOut = false;
|
||
enableSprite(ThisProject.projectTitleElement.parentElement);
|
||
if(ThisProject.entriesContainer != null)
|
||
{
|
||
ThisProject.entriesContainer.style.display = "flex";
|
||
}
|
||
showProjectSearchResults(ThisProject.baseURL, ThisProject.searchLocation, ThisProject.playerLocation);
|
||
}
|
||
else
|
||
{
|
||
ThisProject.filteredOut = true;
|
||
disableSprite(ThisProject.projectTitleElement.parentElement);
|
||
if(ThisProject.entriesContainer != null)
|
||
{
|
||
ThisProject.entriesContainer.style.display = "none";
|
||
}
|
||
hideProjectSearchResults(ThisProject.baseURL, ThisProject.searchLocation, ThisProject.playerLocation);
|
||
}
|
||
}
|
||
}
|
||
|
||
var indexChildFilterProjects = ProjectFilterElement.querySelectorAll(".cineraFilterProject");
|
||
|
||
for(var j = 0; j < indexChildFilterProjects.length; ++j)
|
||
{
|
||
var ThisElement = indexChildFilterProjects[j];
|
||
var baseURL = ThisElement.attributes.getNamedItem("data-baseURL").value;
|
||
var searchLocation = ThisElement.attributes.getNamedItem("data-searchLocation").value;
|
||
var playerLocation = ThisElement.attributes.getNamedItem("data-playerLocation").value;
|
||
if(shouldShow)
|
||
{
|
||
showEntriesOfProject(ThisElement);
|
||
showProjectSearchResults(baseURL, searchLocation, playerLocation);
|
||
}
|
||
else
|
||
{
|
||
hideEntriesOfProject(ThisElement);
|
||
hideProjectSearchResults(baseURL, searchLocation, playerLocation);
|
||
}
|
||
}
|
||
}
|
||
|
||
function prepareToParseIndexFile(project)
|
||
{
|
||
project.xhr.addEventListener("load", function() {
|
||
var contents = project.xhr.response;
|
||
var lines = contents.split("\n");
|
||
var mode = "none";
|
||
var episode = null;
|
||
for (var i = 0; i < lines.length; ++i) {
|
||
var line = lines[i];
|
||
if (line.trim().length == 0) { continue; }
|
||
if (line == "---") {
|
||
if (episode != null && episode.filename != null && episode.number != null && episode.title != null) {
|
||
episode.day = getEpisodeName(project.unit, episode.number);
|
||
episode.dayContainerPrototype = project.dayContainerPrototype;
|
||
episode.markerPrototype = Search.Prototypes.Marker;
|
||
episode.playerURLPrefix = project.playerURLPrefix;
|
||
project.episodes.push(episode);
|
||
}
|
||
episode = {};
|
||
mode = "none";
|
||
} else if (line.startsWith("location:")) {
|
||
episode.filename = line.slice(10);
|
||
} else if (line.startsWith("number:")) {
|
||
episode.number = line.slice(8).trim().slice(1, -1);
|
||
} else if (line.startsWith("title:")) {
|
||
episode.title = line.slice(7).trim().slice(1, -1);
|
||
} else if (line.startsWith("markers")) {
|
||
mode = "markers";
|
||
episode.markers = [];
|
||
} else if (mode == "markers") {
|
||
var match = line.match(/"(\d+)": "(.+)"/);
|
||
if (match == null) {
|
||
console.log(name, line);
|
||
} else {
|
||
var totalTime = parseInt(line.slice(1));
|
||
var marker = {
|
||
totalTime: totalTime,
|
||
prettyTime: markerTime(totalTime),
|
||
text: match[2].replace(/\\"/g, "\"")
|
||
}
|
||
episode.markers.push(marker);
|
||
}
|
||
}
|
||
}
|
||
document.querySelector(".spinner").classList.remove("show");
|
||
project.parsed = true;
|
||
runSearch(true);
|
||
});
|
||
project.xhr.addEventListener("error", function() {
|
||
console.error("Failed to load content");
|
||
});
|
||
}
|
||
|
||
function prepareProjects()
|
||
{
|
||
for(var i = 0; i < Search.ProjectsContainer.length; ++i)
|
||
{
|
||
var ID = Search.ProjectsContainer[i].attributes.getNamedItem("data-project").value;
|
||
var baseURL = Search.ProjectsContainer[i].attributes.getNamedItem("data-baseURL").value;
|
||
var searchLocation = Search.ProjectsContainer[i].attributes.getNamedItem("data-searchLocation").value;
|
||
var playerLocation = Search.ProjectsContainer[i].attributes.getNamedItem("data-playerLocation").value;
|
||
var unit = Search.ProjectsContainer[i].attributes.getNamedItem("data-unit").value;
|
||
var theme = Search.ProjectsContainer[i].classList.item(1);
|
||
|
||
Search.Projects[i] =
|
||
{
|
||
baseURL: baseURL,
|
||
searchLocation: searchLocation,
|
||
playerLocation: playerLocation,
|
||
unit: unit,
|
||
playerURLPrefix: (baseURL ? baseURL + "/" : "") + (playerLocation ? playerLocation + "/" : ""),
|
||
indexLocation: (baseURL ? baseURL + "/" : "") + (searchLocation ? searchLocation + "/" : "") + ID + ".index",
|
||
projectTitleElement: Search.ProjectsContainer[i].querySelector(":scope > .cineraProjectTitle"),
|
||
entriesContainer: Search.ProjectsContainer[i].querySelector(":scope > .cineraIndexEntries"),
|
||
dayContainerPrototype: Search.Prototypes.DayContainer.cloneNode(true),
|
||
filteredOut: false,
|
||
parsed: false,
|
||
searched: false,
|
||
resultsToRender: [],
|
||
resultsIndex: 0,
|
||
theme: theme,
|
||
episodes: [],
|
||
xhr: new XMLHttpRequest(),
|
||
}
|
||
|
||
Search.Projects[i].dayContainerPrototype.classList.add(theme);
|
||
Search.Projects[i].dayContainerPrototype.children[1].classList.add(theme);
|
||
|
||
document.querySelector(".spinner").classList.add("show");
|
||
Search.Projects[i].xhr.open("GET", Search.Projects[i].indexLocation);
|
||
Search.Projects[i].xhr.setRequestHeader("Content-Type", "text/plain");
|
||
Search.Projects[i].xhr.send();
|
||
prepareToParseIndexFile(Search.Projects[i]);
|
||
}
|
||
}
|
||
|
||
function getEpisodeName(unit, number) {
|
||
var day = null;
|
||
if(unit)
|
||
{
|
||
day = unit + " " + number;
|
||
}
|
||
return day;
|
||
}
|
||
|
||
function markerTime(totalTime) {
|
||
var markTime = "(";
|
||
var hours = Math.floor(totalTime / 60 / 60);
|
||
var minutes = Math.floor(totalTime / 60) % 60;
|
||
var seconds = totalTime % 60;
|
||
if (hours > 0) {
|
||
markTime += padTimeComponent(hours) + ":";
|
||
}
|
||
|
||
markTime += padTimeComponent(minutes) + ":" + padTimeComponent(seconds) + ")";
|
||
|
||
return markTime;
|
||
}
|
||
|
||
function padTimeComponent(component) {
|
||
return (component < 10 ? "0" + component : component);
|
||
}
|
||
|
||
function resetProjectsForSearch()
|
||
{
|
||
for(var i = 0; i < Search.Projects.length; ++i)
|
||
{
|
||
var project = Search.Projects[i];
|
||
project.searched = false;
|
||
project.resultsToRender = [];
|
||
}
|
||
}
|
||
|
||
function
|
||
IsQuery()
|
||
{
|
||
return Search.LastQuery && Search.LastQuery.length > 0;
|
||
}
|
||
|
||
function runSearch(refresh) {
|
||
var queryStr = document.getElementById("query").value;
|
||
if (refresh || Search.LastQuery != queryStr) {
|
||
var oldResultsContainer = Search.ResultsContainer;
|
||
Search.ResultsContainer = oldResultsContainer.cloneNode(false);
|
||
oldResultsContainer.parentNode.insertBefore(Search.ResultsContainer, oldResultsContainer);
|
||
oldResultsContainer.remove();
|
||
for(var i = 0; i < Search.Projects.length; ++i)
|
||
{
|
||
Search.Projects[i].resultsIndex = 0;
|
||
}
|
||
Search.ResultsMarkerIndex = -1;
|
||
}
|
||
Search.LastQuery = queryStr;
|
||
|
||
resetProjectsForSearch();
|
||
|
||
var numEpisodes = 0;
|
||
var numMarkers = 0;
|
||
var totalSeconds = 0;
|
||
|
||
// NOTE(matt): Function defined within runSearch() so that we can modify numEpisodes, numMarkers and totalSeconds
|
||
function runSearchInterior(resultsToRender, query, episode)
|
||
{
|
||
var matches = [];
|
||
for (var k = 0; k < episode.markers.length; ++k) {
|
||
query.lastIndex = 0;
|
||
var result = query.exec(episode.markers[k].text);
|
||
if (result && result[0].length > 0) {
|
||
numMarkers++;
|
||
matches.push(episode.markers[k]);
|
||
if (k < episode.markers.length-1) {
|
||
totalSeconds += episode.markers[k+1].totalTime - episode.markers[k].totalTime;
|
||
}
|
||
}
|
||
}
|
||
if (matches.length > 0) {
|
||
numEpisodes++;
|
||
resultsToRender.push({
|
||
query: query,
|
||
episode: episode,
|
||
matches: matches
|
||
});
|
||
}
|
||
}
|
||
|
||
if (IsQuery()) {
|
||
switch(Nav.ViewType)
|
||
{
|
||
case view_type.LIST:
|
||
{
|
||
Nav.List.classList.add("hidden");
|
||
} break;
|
||
case view_type.GRID:
|
||
{
|
||
Nav.GridContainer.classList.add("hidden");
|
||
} break;
|
||
}
|
||
Search.ResultsSummary.style.display = "block";
|
||
var shouldRender = false;
|
||
var query = new RegExp(Search.LastQuery.replace("(", "\\(").replace(")", "\\)").replace(/\|+/, "\|").replace(/\|$/, "").replace(/(^|[^\\])\\$/, "$1"), "gi");
|
||
|
||
// Visible
|
||
for(var i = 0; i < Search.Projects.length; ++i)
|
||
{
|
||
var project = Search.Projects[i];
|
||
if(project.parsed && !project.filteredOut && project.episodes.length > 0) {
|
||
if(Nav.SortChronological)
|
||
{
|
||
for(var j = 0; j < project.episodes.length; ++j) {
|
||
var episode = project.episodes[j];
|
||
runSearchInterior(project.resultsToRender, query, episode);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
for(var j = project.episodes.length; j > 0; --j) {
|
||
var episode = project.episodes[j - 1];
|
||
runSearchInterior(project.resultsToRender, query, episode);
|
||
}
|
||
}
|
||
|
||
shouldRender = true;
|
||
project.searched = true;
|
||
|
||
}
|
||
}
|
||
|
||
// Invisible
|
||
for(var i = 0; i < Search.Projects.length; ++i)
|
||
{
|
||
var project = Search.Projects[i];
|
||
if(project.parsed && project.filteredOut && !project.searched && project.episodes.length > 0) {
|
||
if(Nav.SortChronological)
|
||
{
|
||
for(var j = 0; j < project.episodes.length; ++j) {
|
||
var episode = project.episodes[j];
|
||
runSearchInterior(project.resultsToRender, query, episode);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
for(var j = project.episodes.length; j > 0; --j) {
|
||
var episode = project.episodes[j - 1];
|
||
runSearchInterior(project.resultsToRender, query, episode);
|
||
}
|
||
}
|
||
|
||
shouldRender = true;
|
||
project.searched = true;
|
||
|
||
}
|
||
}
|
||
|
||
if(shouldRender)
|
||
{
|
||
if (Search.Rendering) {
|
||
clearTimeout(Search.RenderHandle);
|
||
}
|
||
renderResults();
|
||
}
|
||
}
|
||
else
|
||
{
|
||
switch(Nav.ViewType)
|
||
{
|
||
case view_type.LIST:
|
||
{
|
||
Nav.List.classList.remove("hidden");
|
||
} break;
|
||
case view_type.GRID:
|
||
{
|
||
Nav.GridContainer.classList.remove("hidden");
|
||
} break;
|
||
}
|
||
Search.ResultsSummary.style.display = "none";
|
||
}
|
||
|
||
var totalTime = Math.floor(totalSeconds/60/60) + "h " + Math.floor(totalSeconds/60)%60 + "m " + totalSeconds%60 + "s ";
|
||
|
||
Search.ResultsSummary.textContent = "Found: " + numEpisodes + " episodes, " + numMarkers + " markers, " + totalTime + "total.";
|
||
}
|
||
|
||
function renderResults() {
|
||
var maxItems = 42;
|
||
var numItems = 0;
|
||
for(var i = 0; i < Search.Projects.length; ++i)
|
||
{
|
||
var project = Search.Projects[i];
|
||
if (project.resultsIndex < project.resultsToRender.length) {
|
||
Search.Rendering = true;
|
||
while (numItems < maxItems && project.resultsIndex < project.resultsToRender.length) {
|
||
var query = project.resultsToRender[project.resultsIndex].query;
|
||
var episode = project.resultsToRender[project.resultsIndex].episode;
|
||
var matches = project.resultsToRender[project.resultsIndex].matches;
|
||
if (Search.ResultsMarkerIndex == -1) {
|
||
if(project.resultsIndex == 0 || project.resultsToRender[project.resultsIndex - 1].episode.playerURLPrefix != episode.playerURLPrefix)
|
||
{
|
||
Search.ProjectContainer = Search.Prototypes.ProjectContainer.cloneNode(true);
|
||
for(var i = 0; i < Search.Projects.length; ++i)
|
||
{
|
||
if(Search.Projects[i].playerURLPrefix === episode.playerURLPrefix)
|
||
{
|
||
Search.ProjectContainer.setAttribute("data-baseURL", Search.Projects[i].baseURL);
|
||
Search.ProjectContainer.setAttribute("data-searchLocation", Search.Projects[i].searchLocation);
|
||
Search.ProjectContainer.setAttribute("data-playerLocation", Search.Projects[i].playerLocation);
|
||
if(Search.Projects[i].filteredOut)
|
||
{
|
||
Search.ProjectContainer.style.display = "none";
|
||
}
|
||
}
|
||
}
|
||
Search.ResultsContainer.appendChild(Search.ProjectContainer);
|
||
}
|
||
else
|
||
{
|
||
Search.ProjectContainer = Search.ResultsContainer.lastElementChild;
|
||
}
|
||
|
||
|
||
var dayContainer = episode.dayContainerPrototype.cloneNode(true);
|
||
var dayName = dayContainer.children[0];
|
||
Search.MarkerList = dayContainer.children[1];
|
||
|
||
// TODO(matt): Maybe prepend the entire lineage?
|
||
dayName.textContent = (project.projectTitleElement.textContent ? project.projectTitleElement.textContent + " / " : "") + (episode.day ? episode.day + ": " : "") + episode.title;
|
||
|
||
Search.ProjectContainer.appendChild(dayContainer);
|
||
Search.ResultsMarkerIndex = 0;
|
||
numItems++;
|
||
}
|
||
|
||
while (numItems < maxItems && Search.ResultsMarkerIndex < matches.length) {
|
||
var match = matches[Search.ResultsMarkerIndex];
|
||
var marker = episode.markerPrototype.cloneNode(true);
|
||
marker.setAttribute("href", episode.playerURLPrefix + episode.filename.replace(/"/g, "") + "/#" + match.totalTime);
|
||
query.lastIndex = 0;
|
||
var cursor = 0;
|
||
var text = match.text;
|
||
var result = null;
|
||
marker.appendChild(document.createTextNode(match.prettyTime + " "));
|
||
while (result = query.exec(text)) {
|
||
if (result.index > cursor) {
|
||
marker.appendChild(document.createTextNode(text.slice(cursor, result.index)));
|
||
}
|
||
var highlightEl = Search.Prototypes.Highlight.cloneNode();
|
||
highlightEl.textContent = result[0];
|
||
marker.appendChild(highlightEl);
|
||
cursor = result.index + result[0].length;
|
||
}
|
||
|
||
if (cursor < text.length) {
|
||
marker.appendChild(document.createTextNode(text.slice(cursor, text.length)));
|
||
}
|
||
Search.MarkerList.appendChild(marker);
|
||
numItems++;
|
||
Search.ResultsMarkerIndex++;
|
||
}
|
||
|
||
if (Search.ResultsMarkerIndex == matches.length) {
|
||
Search.ResultsMarkerIndex = -1;
|
||
project.resultsIndex++;
|
||
}
|
||
}
|
||
Search.RenderHandle = setTimeout(renderResults, 0);
|
||
} else {
|
||
Search.Rendering = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
function
|
||
InitQuery(QueryElement)
|
||
{
|
||
if(location.hash && location.hash.length > 0)
|
||
{
|
||
var initialQuery = location.hash;
|
||
if(initialQuery[0] == "#")
|
||
{
|
||
initialQuery = initialQuery.slice(1);
|
||
}
|
||
QueryElement.value = decodeURIComponent(initialQuery);
|
||
}
|
||
|
||
if(document.hasFocus() && IsVisible(QueryElement, GetWindowDim(CineraProps.IsMobile))) { QueryElement.focus(); }
|
||
}
|
||
|
||
function
|
||
InitPrototypes(ResultsContainer)
|
||
{
|
||
Search.Prototypes.ProjectContainer = document.createElement("DIV");
|
||
Search.Prototypes.ProjectContainer.classList.add("projectContainer");
|
||
|
||
Search.Prototypes.DayContainer = document.createElement("DIV");
|
||
Search.Prototypes.DayContainer.classList.add("dayContainer");
|
||
|
||
var DayName = document.createElement("SPAN");
|
||
DayName.classList.add("dayName");
|
||
Search.Prototypes.DayContainer.appendChild(DayName);
|
||
|
||
var MarkerList = document.createElement("DIV");
|
||
MarkerList.classList.add("markerList");
|
||
Search.Prototypes.DayContainer.appendChild(MarkerList);
|
||
|
||
Search.Prototypes.Marker = document.createElement("A");
|
||
Search.Prototypes.Marker.classList.add("marker");
|
||
if(ResultsContainer.getAttribute("data-single") == 0)
|
||
{
|
||
Search.Prototypes.Marker.setAttribute("target", "_blank");
|
||
}
|
||
|
||
Search.Prototypes.Highlight = document.createElement("B");
|
||
}
|
||
//
|
||
// Processing (Searching / Filtering)
|
||
|
||
// Presenting / Navigating (Laying out and traversing the grid, and sorting)
|
||
//
|
||
var state_bit =
|
||
{
|
||
DISABLE_ANIMATIONS: 1 << 0,
|
||
SORT_REVERSED: 1 << 1,
|
||
VIEW_LIST: 1 << 2,
|
||
VIEW_GRID: 1 << 3,
|
||
|
||
NO_SAVE: 1 << 31,
|
||
};
|
||
|
||
var siblings =
|
||
{
|
||
PREV: 0,
|
||
NEXT: 1,
|
||
};
|
||
|
||
var item_type =
|
||
{
|
||
PROJECT: 0,
|
||
ENTRY: 1,
|
||
};
|
||
|
||
var item_end =
|
||
{
|
||
HEAD: 0,
|
||
TAIL: 1,
|
||
};
|
||
|
||
var view_type =
|
||
{
|
||
LIST: 0,
|
||
GRID: 1,
|
||
};
|
||
|
||
var transition_type =
|
||
{
|
||
SIBLING_SHIFT_PREV: 0,
|
||
SIBLING_SHIFT_NEXT: 1,
|
||
PROJECT_ENTRY: 2,
|
||
PROJECT_EXIT: 3,
|
||
SUBDIVISION_DESCENT: 4,
|
||
SUBDIVISION_ASCENT: 5,
|
||
};
|
||
|
||
var interaction_type =
|
||
{
|
||
PUSH_BUTTON: 0,
|
||
SIBLING_SHIFT_PREV: 1,
|
||
SIBLING_SHIFT_NEXT: 2,
|
||
ASCEND: 3,
|
||
SORT: 4,
|
||
};
|
||
|
||
var Nav = {
|
||
Nexus: null,
|
||
GridContainer: null,
|
||
ButtonsContainer: null,
|
||
GridSize: {
|
||
X: null,
|
||
Y: null,
|
||
},
|
||
GridMinCellsPerDimension: 1,
|
||
GridMaxCellsPerDimension: 4,
|
||
GridDim: {
|
||
X: null,
|
||
Y: null,
|
||
},
|
||
MinButtonDim: 100,
|
||
ButtonDim: null,
|
||
GridColumnGap: null,
|
||
GridRowGap: null,
|
||
|
||
SortChronological: true,
|
||
ViewType: view_type.LIST,
|
||
List: null,
|
||
Grid: null,
|
||
|
||
// NOTE(matt): Controls
|
||
Controls: {
|
||
Header: null,
|
||
Sort: null,
|
||
View: null,
|
||
Anim: null,
|
||
Save: null,
|
||
|
||
Help: null,
|
||
HelpDocumentation: null,
|
||
HelpKeys: [],
|
||
|
||
GridTraversal: {
|
||
Container: null,
|
||
Header: null,
|
||
Ascend: null,
|
||
Prev: null,
|
||
PrevAscends: false,
|
||
Next: null,
|
||
NextAscends: false,
|
||
},
|
||
},
|
||
|
||
Buttons: [],
|
||
|
||
Transition: {
|
||
Enabled: true,
|
||
ButtonsTransitionContainer: null,
|
||
ButtonsContainerCloneElement: null,
|
||
RelevantButtonElement: null,
|
||
|
||
StageDurations: [],
|
||
|
||
Transforms: {
|
||
ButtonsTransitionContainer: {
|
||
Initial: {
|
||
Pos: { X: 0, Y: 0, },
|
||
Scale: { X: 1, Y: 1, },
|
||
Rotation: { X: 0, Y: 0, Z: 0, },
|
||
Opacity: 1,
|
||
ScrollX: 0,
|
||
ZIndex: 0,
|
||
},
|
||
Current: {
|
||
Pos: { X: 0, Y: 0, },
|
||
Scale: { X: 1, Y: 1, },
|
||
Rotation: { X: 0, Y: 0, Z: 0, },
|
||
Opacity: 1,
|
||
ScrollX: 0,
|
||
ZIndex: 0,
|
||
},
|
||
TargetStages: [],
|
||
},
|
||
ButtonsContainer: {
|
||
Initial: {
|
||
Pos: { X: 0, Y: 0, },
|
||
Scale: { X: 1, Y: 1, },
|
||
Rotation: { X: 0, Y: 0, Z: 0, },
|
||
Opacity: 1,
|
||
ScrollX: 0,
|
||
ZIndex: 0,
|
||
},
|
||
Current: {
|
||
Pos: { X: 0, Y: 0, },
|
||
Scale: { X: 1, Y: 1, },
|
||
Rotation: { X: 0, Y: 0, Z: 0, },
|
||
Opacity: 1,
|
||
ScrollX: 0,
|
||
ZIndex: 0,
|
||
},
|
||
TargetStages: [],
|
||
},
|
||
ButtonsContainerClone: {
|
||
Initial: {
|
||
Pos: { X: 0, Y: 0, },
|
||
Scale: { X: 1, Y: 1, },
|
||
Rotation: { X: 0, Y: 0, Z: 0, },
|
||
Opacity: 1,
|
||
ScrollX: 0,
|
||
ZIndex: 0,
|
||
},
|
||
Current: {
|
||
Pos: { X: 0, Y: 0, },
|
||
Scale: { X: 1, Y: 1, },
|
||
Rotation: { X: 0, Y: 0, Z: 0, },
|
||
Opacity: 1,
|
||
ScrollX: 0,
|
||
ZIndex: 0,
|
||
},
|
||
TargetStages: [],
|
||
},
|
||
RelevantButton: {
|
||
Initial: {
|
||
Pos: { X: 0, Y: 0, },
|
||
Scale: { X: 1, Y: 1, },
|
||
Rotation: { X: 0, Y: 0, Z: 0, },
|
||
Opacity: 1,
|
||
ScrollX: 0,
|
||
ZIndex: 0,
|
||
},
|
||
Current: {
|
||
Pos: { X: 0, Y: 0, },
|
||
Scale: { X: 1, Y: 1, },
|
||
Rotation: { X: 0, Y: 0, Z: 0, },
|
||
Opacity: 1,
|
||
ScrollX: 0,
|
||
ZIndex: 0,
|
||
},
|
||
TargetStages: [],
|
||
},
|
||
},
|
||
|
||
StartTime: undefined,
|
||
RequestedFrame: undefined,
|
||
},
|
||
|
||
InteractionQueue: [],
|
||
TraversalStack: [],
|
||
State: null,
|
||
};
|
||
|
||
function
|
||
StateBitIsSet(Bit)
|
||
{
|
||
return Nav.State & Bit;
|
||
}
|
||
|
||
function
|
||
StateBitIsClear(Bit)
|
||
{
|
||
return !(Nav.State & Bit);
|
||
}
|
||
|
||
function
|
||
MaintainingState()
|
||
{
|
||
return StateBitIsClear(state_bit.NO_SAVE);
|
||
}
|
||
|
||
function
|
||
SaveState()
|
||
{
|
||
localStorage.setItem("CineraState", Nav.State);
|
||
}
|
||
|
||
function
|
||
SetStateBit(Bit)
|
||
{
|
||
if(MaintainingState()) { Nav.State |= Bit; SaveState(); }
|
||
}
|
||
|
||
function
|
||
ClearStateBit(Bit)
|
||
{
|
||
if(MaintainingState()) { Nav.State &= ~Bit; SaveState(); }
|
||
}
|
||
|
||
function
|
||
SetHelpKeyAvailability(GridSize)
|
||
{
|
||
for(var i = 0; i < Nav.Controls.HelpKeys.length; ++i)
|
||
{
|
||
Nav.Controls.HelpKeys[i].classList.remove("unavailable");
|
||
}
|
||
|
||
/* NOTE(matt): Key layout:
|
||
0 4 8 12
|
||
1 5 9 13
|
||
2 6 10 14
|
||
3 7 11 15
|
||
*/
|
||
|
||
if(GridSize.X < 4)
|
||
{
|
||
Nav.Controls.HelpKeys[12].classList.add("unavailable");
|
||
Nav.Controls.HelpKeys[13].classList.add("unavailable");
|
||
Nav.Controls.HelpKeys[14].classList.add("unavailable");
|
||
Nav.Controls.HelpKeys[15].classList.add("unavailable");
|
||
|
||
if(GridSize.X < 3)
|
||
{
|
||
Nav.Controls.HelpKeys[8].classList.add("unavailable");
|
||
Nav.Controls.HelpKeys[9].classList.add("unavailable");
|
||
Nav.Controls.HelpKeys[10].classList.add("unavailable");
|
||
Nav.Controls.HelpKeys[11].classList.add("unavailable");
|
||
|
||
if(GridSize.X < 2)
|
||
{
|
||
Nav.Controls.HelpKeys[4].classList.add("unavailable");
|
||
Nav.Controls.HelpKeys[5].classList.add("unavailable");
|
||
Nav.Controls.HelpKeys[6].classList.add("unavailable");
|
||
Nav.Controls.HelpKeys[7].classList.add("unavailable");
|
||
|
||
if(GridSize.X < 1)
|
||
{
|
||
Nav.Controls.HelpKeys[0].classList.add("unavailable");
|
||
Nav.Controls.HelpKeys[1].classList.add("unavailable");
|
||
Nav.Controls.HelpKeys[2].classList.add("unavailable");
|
||
Nav.Controls.HelpKeys[3].classList.add("unavailable");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if(GridSize.Y < 4)
|
||
{
|
||
Nav.Controls.HelpKeys[3].classList.add("unavailable");
|
||
Nav.Controls.HelpKeys[7].classList.add("unavailable");
|
||
Nav.Controls.HelpKeys[11].classList.add("unavailable");
|
||
Nav.Controls.HelpKeys[15].classList.add("unavailable");
|
||
|
||
if(GridSize.Y < 3)
|
||
{
|
||
Nav.Controls.HelpKeys[2].classList.add("unavailable");
|
||
Nav.Controls.HelpKeys[6].classList.add("unavailable");
|
||
Nav.Controls.HelpKeys[10].classList.add("unavailable");
|
||
Nav.Controls.HelpKeys[14].classList.add("unavailable");
|
||
|
||
if(GridSize.Y < 2)
|
||
{
|
||
Nav.Controls.HelpKeys[1].classList.add("unavailable");
|
||
Nav.Controls.HelpKeys[5].classList.add("unavailable");
|
||
Nav.Controls.HelpKeys[9].classList.add("unavailable");
|
||
Nav.Controls.HelpKeys[13].classList.add("unavailable");
|
||
|
||
if(GridSize.Y < 1)
|
||
{
|
||
Nav.Controls.HelpKeys[0].classList.add("unavailable");
|
||
Nav.Controls.HelpKeys[4].classList.add("unavailable");
|
||
Nav.Controls.HelpKeys[8].classList.add("unavailable");
|
||
Nav.Controls.HelpKeys[12].classList.add("unavailable");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
function
|
||
InitHelpKeys(HelpDocumentation)
|
||
{
|
||
var Paragraph = HelpDocumentation.querySelector(".help_paragraph");
|
||
Nav.Controls.HelpKeys = Paragraph.querySelectorAll(".help_key");
|
||
}
|
||
|
||
function
|
||
InitView()
|
||
{
|
||
// NOTE(matt): Nav.ViewType is initialised to view_type.LIST and InitNexus() leaves the List View visible
|
||
if(!StateBitIsSet(state_bit.VIEW_LIST))
|
||
{
|
||
if(GridSizeIsSupported(Nav.GridSize))
|
||
{
|
||
PickGridView();
|
||
}
|
||
else
|
||
{
|
||
// NOTE(matt): Silently swap the state bits, leaving the default List View visible
|
||
ClearStateBit(state_bit.VIEW_GRID);
|
||
SetStateBit(state_bit.VIEW_LIST);
|
||
}
|
||
}
|
||
}
|
||
|
||
function
|
||
SyncNavState()
|
||
{
|
||
Nav.State = localStorage.getItem("CineraState");
|
||
if(Nav.State)
|
||
{
|
||
if(MaintainingState())
|
||
{
|
||
if(StateBitIsSet(state_bit.DISABLE_ANIMATIONS)) { ToggleAnimations(); }
|
||
if(StateBitIsSet(state_bit.SORT_REVERSED)) { Sort(true); }
|
||
InitView();
|
||
}
|
||
else
|
||
{
|
||
Nav.Controls.Save.textContent = "Save Settings: ✘";
|
||
// Nav.ViewType was initialised to view_type.LIST
|
||
if(Nav.ViewType == view_type.GRID && GridSizeIsSupported(Nav.GridSize))
|
||
{
|
||
PickGridView();
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Nav.State = 0;
|
||
switch(Nav.ViewType)
|
||
{
|
||
case view_type.LIST: SetStateBit(state_bit.VIEW_LIST); break
|
||
case view_type.GRID: SetStateBit(state_bit.VIEW_GRID); break
|
||
}
|
||
InitView();
|
||
}
|
||
}
|
||
|
||
function
|
||
InitTraversalStack()
|
||
{
|
||
Nav.List = document.getElementById("cineraIndexList");
|
||
|
||
var Projects = Nav.List.querySelectorAll(":scope > .cineraIndexProject");
|
||
|
||
var Level = {
|
||
Projects: null,
|
||
Entries: null,
|
||
HeadIndex: null,
|
||
TailIndex: null,
|
||
}
|
||
|
||
if(Projects.length === 1)
|
||
{
|
||
// NOTE(matt): Automatically descend into the lone project
|
||
var QueriedProjects = Projects[0].querySelectorAll(":scope > .cineraIndexProject");
|
||
if(QueriedProjects.length > 0)
|
||
{
|
||
Level.Projects = QueriedProjects;
|
||
}
|
||
Level.Entries = Projects[0].querySelectorAll(":scope > .cineraIndexEntries > div");
|
||
}
|
||
else
|
||
{
|
||
Level.Projects = Projects;
|
||
// NOTE(matt): The top-level "root" cannot itself contain any entries
|
||
}
|
||
|
||
Nav.TraversalStack.push(Level);
|
||
}
|
||
|
||
function
|
||
ComputeFullButtonItemCount(ParentItemCount, AvailableButtonCount)
|
||
{
|
||
return ParentItemCount > 0 ? Math.ceil(ParentItemCount / AvailableButtonCount) : 0;
|
||
}
|
||
|
||
function
|
||
EmptyElement(Element)
|
||
{
|
||
while(Element.firstChild) { Element.removeChild(Element.firstChild); }
|
||
}
|
||
|
||
function
|
||
SetDim(Element, X, Y)
|
||
{
|
||
Element.style.width = X;
|
||
Element.style.height = Y;
|
||
}
|
||
|
||
function
|
||
EmptyAndResetButton(Button)
|
||
{
|
||
EmptyElement(Button.Element);
|
||
Button.Element.style.fontSize = null;
|
||
Button.Element.style.fontWeight = null;
|
||
|
||
SetDim(Button.Element, null, null);
|
||
|
||
for(var i = 0; i < Button.Element.classList.length;)
|
||
{
|
||
var Class = Button.Element.classList[i];
|
||
if(Class != "cineraButton" && Class != "subdivision")
|
||
{
|
||
Button.Element.classList.remove(Class);
|
||
}
|
||
else
|
||
{
|
||
++i;
|
||
}
|
||
}
|
||
|
||
Button.Projects = null;
|
||
Button.Entries = null;
|
||
Button.HeadIndex = null;
|
||
Button.TailIndex = null;
|
||
}
|
||
|
||
function
|
||
HasPrevSibling(Level)
|
||
{
|
||
return Level.HeadIndex && Level.HeadIndex > 0;
|
||
}
|
||
|
||
function
|
||
HasNextSibling(Level)
|
||
{
|
||
return Level.TailIndex && Level.TailIndex < (Level.Entries ? Level.Entries.length : Level.Projects.length) - 1;
|
||
}
|
||
|
||
function
|
||
Diff(A, B)
|
||
{
|
||
return Math.abs(A - B);
|
||
}
|
||
|
||
function
|
||
SetButtonInfo(NewButton, Prev, Level, Distribution)
|
||
{
|
||
var Result = {
|
||
HeadIndex: null,
|
||
TailIndex: null,
|
||
ItemCount: null,
|
||
Theme: null,
|
||
}
|
||
var ItemsToPlace = null;
|
||
var FullButtonItemCount = null;
|
||
|
||
if(Distribution.ProjectsToPlace)
|
||
{
|
||
ItemsToPlace = Distribution.ProjectsToPlace;
|
||
FullButtonItemCount = Distribution.FullButtonProjectCount;
|
||
}
|
||
else if(Distribution.EntriesToPlace)
|
||
{
|
||
ItemsToPlace = Distribution.EntriesToPlace;
|
||
FullButtonItemCount = Distribution.FullButtonEntryCount;
|
||
Result.Theme = Level.Entries[0].parentElement.parentElement.classList[1];
|
||
}
|
||
|
||
Result.ItemCount = ItemsToPlace > FullButtonItemCount ? FullButtonItemCount : ItemsToPlace;
|
||
if(Result.ItemCount > 0)
|
||
{
|
||
Result.HeadIndex = Prev ? Prev.TailIndex + 1 : Level.HeadIndex ? Level.HeadIndex : 0;
|
||
Result.TailIndex = Result.HeadIndex + Result.ItemCount - 1;
|
||
if(Result.ItemCount > 1)
|
||
{
|
||
if(Result.Theme == null)
|
||
{
|
||
Result.Theme = Level.Projects[Result.HeadIndex].classList[1];
|
||
}
|
||
NewButton.HeadIndex = Result.HeadIndex;
|
||
NewButton.TailIndex = Result.TailIndex;
|
||
}
|
||
else if(Level.Projects && Level.Projects.length > 0)
|
||
{
|
||
if(Distribution.ProjectsToPlace)
|
||
{
|
||
NewButton.Projects = Level.Projects[Result.HeadIndex].querySelectorAll(":scope > .cineraIndexProject");
|
||
Result.Theme = Level.Projects[Result.HeadIndex].classList[1];
|
||
}
|
||
else
|
||
{
|
||
NewButton.Entries = Level.Projects[Result.HeadIndex].querySelectorAll(":scope > .cineraIndexEntries > div");
|
||
}
|
||
}
|
||
}
|
||
return Result;
|
||
}
|
||
|
||
function
|
||
ComputeItemDistribution(Level)
|
||
{
|
||
var Result = {
|
||
ProjectsToPlace: Level.Projects ? Level.HeadIndex !== null && Level.TailIndex !== null ? Diff(Level.HeadIndex, Level.TailIndex) + 1 : Level.Projects.length : 0,
|
||
ButtonsForProjects: 0,
|
||
FullButtonProjectCount: 0,
|
||
|
||
EntriesToPlace: Level.Entries ? Level.HeadIndex !== null && Level.TailIndex !== null ? Diff(Level.HeadIndex, Level.TailIndex) + 1 : Level.Entries.length : 0,
|
||
ButtonsForEntries: 0,
|
||
FullButtonEntryCount: 0,
|
||
};
|
||
|
||
// NOTE(matt): Reserving the top row for projects
|
||
if(Result.ProjectsToPlace > 0 && Result.EntriesToPlace > 0)
|
||
{
|
||
Result.ButtonsForProjects = Math.min(Nav.GridSize.X, Result.ProjectsToPlace);
|
||
}
|
||
Result.ButtonsForEntries = Nav.Buttons.length - Result.ButtonsForProjects;
|
||
if(Result.EntriesToPlace < Result.ButtonsForEntries)
|
||
{
|
||
Result.ButtonsForProjects += (Result.ButtonsForEntries - Result.EntriesToPlace);
|
||
Result.ButtonsForEntries = Nav.Buttons.length - Result.ButtonsForProjects;
|
||
}
|
||
|
||
Result.FullButtonProjectCount = ComputeFullButtonItemCount(Result.ProjectsToPlace, Result.ButtonsForProjects);
|
||
Result.FullButtonEntryCount = ComputeFullButtonItemCount(Result.EntriesToPlace, Result.ButtonsForEntries);
|
||
|
||
return Result;
|
||
}
|
||
|
||
function
|
||
ResetTransform(Transform, IgnoreSorting)
|
||
{
|
||
Transform.Pos.X = 0;
|
||
Transform.Pos.Y = 0;
|
||
|
||
Transform.Scale.X = 1;
|
||
Transform.Scale.Y = 1;
|
||
|
||
Transform.Rotation.X = 0;
|
||
Transform.Rotation.Y = 0;
|
||
Transform.Rotation.Z = (Nav.SortChronological || IgnoreSorting) ? 0 : 180;
|
||
|
||
Transform.Opacity = 1;
|
||
|
||
Transform.ScrollX = 0;
|
||
|
||
Transform.ZIndex = null;
|
||
}
|
||
|
||
function
|
||
ResetButtonsContainerClone()
|
||
{
|
||
EmptyElement(Nav.Transition.ButtonsContainerCloneElement);
|
||
|
||
ResetTransform(Nav.Transition.Transforms.ButtonsTransitionContainer.Current, true);
|
||
ResetTransform(Nav.Transition.Transforms.ButtonsTransitionContainer.Initial, true);
|
||
ApplyTransform(Nav.Transition.ButtonsTransitionContainerElement, Nav.Transition.Transforms.ButtonsTransitionContainer.Current);
|
||
|
||
ResetTransform(Nav.Transition.Transforms.ButtonsContainer.Current);
|
||
ResetTransform(Nav.Transition.Transforms.ButtonsContainer.Initial);
|
||
ApplyTransform(Nav.ButtonsContainer, Nav.Transition.Transforms.ButtonsContainer.Current);
|
||
|
||
ResetTransform(Nav.Transition.Transforms.ButtonsContainerClone.Current);
|
||
ResetTransform(Nav.Transition.Transforms.ButtonsContainerClone.Initial);
|
||
ApplyTransform(Nav.Transition.ButtonsContainerCloneElement, Nav.Transition.Transforms.ButtonsContainerClone.Current);
|
||
|
||
Nav.Transition.ButtonsContainerCloneElement.style.gridTemplateColumns = Nav.ButtonsContainer.style.gridTemplateColumns;
|
||
Nav.Transition.ButtonsContainerCloneElement.style.gridTemplateRows = Nav.ButtonsContainer.style.gridTemplateRows;
|
||
|
||
Nav.Transition.ButtonsContainerCloneElement.style.paddingRight = null;
|
||
Nav.Transition.ButtonsContainerCloneElement.style.paddingLeft = null;
|
||
Nav.Transition.ButtonsContainerCloneElement.style.position = "absolute";
|
||
|
||
if(Nav.Transition.RelevantButtonElement)
|
||
{
|
||
ResetTransform(Nav.Transition.Transforms.RelevantButton.Current, true);
|
||
ResetTransform(Nav.Transition.Transforms.RelevantButton.Initial, true);
|
||
ApplyTransform(Nav.Transition.RelevantButtonElement, Nav.Transition.Transforms.RelevantButton.Current);
|
||
Nav.Transition.RelevantButtonElement = null;
|
||
}
|
||
|
||
Nav.ButtonsContainer.style.zIndex = 1;
|
||
Nav.ButtonsContainer.style.order = 1;
|
||
Nav.Transition.ButtonsContainerCloneElement.style.order = 0;
|
||
Nav.Transition.ButtonsContainerCloneElement.style.display = "none";
|
||
}
|
||
|
||
function
|
||
CloneButtonsContainer()
|
||
{
|
||
ResetButtonsContainerClone();
|
||
for(var i = 0; i < Nav.ButtonsContainer.children.length; ++i)
|
||
{
|
||
var ChildClone = Nav.ButtonsContainer.children[i].cloneNode(true);
|
||
Nav.Transition.ButtonsContainerCloneElement.appendChild(ChildClone);
|
||
}
|
||
|
||
CopyTransform(Nav.Transition.Transforms.ButtonsContainerClone.Current, Nav.Transition.Transforms.ButtonsContainer.Current);
|
||
ApplyTransform(Nav.Transition.ButtonsContainerCloneElement, Nav.Transition.Transforms.ButtonsContainerClone.Current);
|
||
Nav.Transition.ButtonsContainerCloneElement.style.zIndex = 1;
|
||
Nav.Transition.ButtonsContainerCloneElement.style.display = "grid";
|
||
}
|
||
|
||
function
|
||
GetIndexOfElement(ParentNodeList, Element)
|
||
{
|
||
var Result = null;
|
||
for(var i = 0; i < ParentNodeList.length; ++i)
|
||
{
|
||
if(Element == ParentNodeList[i])
|
||
{
|
||
Result = i;
|
||
break;
|
||
}
|
||
}
|
||
return Result;
|
||
}
|
||
|
||
function
|
||
GetIndexOfButton(Button)
|
||
{
|
||
var Result = null;
|
||
for(var i = 0; i < Nav.Buttons.length; ++i)
|
||
{
|
||
if(Button == Nav.Buttons[i])
|
||
{
|
||
Result = i
|
||
break;
|
||
}
|
||
}
|
||
return Result;
|
||
}
|
||
|
||
function
|
||
ApplyTransform(Element, Transform)
|
||
{
|
||
var TranslateStyle = "translate(" + Transform.Pos.X + "px, " + Transform.Pos.Y + "px)";
|
||
var ScaleStyle = "scale(" + Transform.Scale.X + "," + Transform.Scale.Y + ")";
|
||
var RotateX = "rotate3d(1, 0, 0, " + Transform.Rotation.X + "deg)";
|
||
var RotateY = "rotate3d(0, 1, 0, " + Transform.Rotation.Y + "deg)";
|
||
var RotateZ = "rotate3d(0, 0, 1, " + Transform.Rotation.Z + "deg)";
|
||
var RotateStyle = RotateX + " " + RotateY + " " + RotateZ;
|
||
|
||
var TransformString = TranslateStyle + " " + ScaleStyle + " " + RotateStyle;
|
||
|
||
Element.style.transform = TransformString;
|
||
Element.style.opacity = Transform.Opacity;
|
||
Element.style.zIndex = Transform.ZIndex;
|
||
|
||
if(Transform.ScrollX !== null)
|
||
{
|
||
Element.scrollLeft = Transform.ScrollX;
|
||
}
|
||
}
|
||
|
||
function
|
||
CopyTransform(Dest, Src)
|
||
{
|
||
Dest.Pos.X = Src.Pos.X;
|
||
Dest.Pos.Y = Src.Pos.Y;
|
||
|
||
Dest.Scale.X = Src.Scale.X;
|
||
Dest.Scale.Y = Src.Scale.Y;
|
||
|
||
Dest.Rotation.X = Src.Rotation.X;
|
||
Dest.Rotation.Y = Src.Rotation.Y;
|
||
Dest.Rotation.Z = Src.Rotation.Z;
|
||
|
||
Dest.Opacity = Src.Opacity;
|
||
|
||
Dest.ScrollX = Src.ScrollX;
|
||
|
||
Dest.ZIndex = Src.ZIndex;
|
||
}
|
||
|
||
function
|
||
TransformsMatch(Current, Target)
|
||
{
|
||
var Result = true;
|
||
if((Target.Pos.X != null && Target.Pos.X != Current.Pos.X) ||
|
||
(Target.Pos.Y != null && Target.Pos.Y != Current.Pos.Y) ||
|
||
(Target.Scale.X != null && Target.Scale.X != Current.Scale.X) ||
|
||
(Target.Scale.Y != null && Target.Scale.Y != Current.Scale.Y) ||
|
||
(Target.Rotation.X != null && Target.Rotation.X != Current.Rotation.X) ||
|
||
(Target.Rotation.Y != null && Target.Rotation.Y != Current.Rotation.Y) ||
|
||
(Target.Rotation.Z != null && Target.Rotation.Z != Current.Rotation.Z) ||
|
||
(Target.Opacity != null && Target.Opacity != Current.Opacity) ||
|
||
(Target.ScrollX != null && Target.ScrollX != Current.ScrollX) ||
|
||
(Target.ZIndex != null && Target.ZIndex != Current.ZIndex))
|
||
{
|
||
Result = false;
|
||
}
|
||
return Result;
|
||
}
|
||
|
||
function
|
||
TransformsComplete(TransformSet)
|
||
{
|
||
var Result = true;
|
||
|
||
var ButtonsContainer = TransformSet.ButtonsContainer;
|
||
var ButtonsContainerClone = TransformSet.ButtonsContainerClone;
|
||
var ButtonsTransitionContainer = TransformSet.ButtonsTransitionContainer;
|
||
var RelevantButton = TransformSet.RelevantButton;
|
||
|
||
if((ButtonsContainer.TargetStages.length && !TransformsMatch(ButtonsContainer.Current, ButtonsContainer.TargetStages[0])) ||
|
||
(ButtonsContainerClone.TargetStages.length && !TransformsMatch(ButtonsContainerClone.Current, ButtonsContainerClone.TargetStages[0])) ||
|
||
(ButtonsTransitionContainer.TargetStages.length && !TransformsMatch(ButtonsTransitionContainer.Current, ButtonsTransitionContainer.TargetStages[0])) ||
|
||
(RelevantButton.TargetStages.length && !TransformsMatch(RelevantButton.Current, RelevantButton.TargetStages[0])))
|
||
{
|
||
Result = false;
|
||
}
|
||
return Result;
|
||
}
|
||
|
||
function
|
||
FinaliseTransforms(TransformsSet)
|
||
{
|
||
Nav.Transition.StartTime = undefined;
|
||
CopyTransform(TransformsSet.ButtonsTransitionContainer.Initial, TransformsSet.ButtonsTransitionContainer.Current);
|
||
CopyTransform(TransformsSet.ButtonsContainer.Initial, TransformsSet.ButtonsContainer.Current);
|
||
CopyTransform(TransformsSet.ButtonsContainerClone.Initial, TransformsSet.ButtonsContainerClone.Current);
|
||
CopyTransform(TransformsSet.RelevantButton.Initial, TransformsSet.RelevantButton.Current);
|
||
ShiftStage();
|
||
}
|
||
|
||
function
|
||
LerpTransforms(TransformSet, t)
|
||
{
|
||
var Result = false;
|
||
if(TransformSet.TargetStages.length)
|
||
{
|
||
var Initial = TransformSet.Initial;
|
||
var Current = TransformSet.Current;
|
||
var Target = TransformSet.TargetStages[0];
|
||
if(Target.Pos.X !== null) { Current.Pos.X = Lerp(Initial.Pos.X, t, Target.Pos.X); Result = true; }
|
||
if(Target.Pos.Y !== null) { Current.Pos.Y = Lerp(Initial.Pos.Y, t, Target.Pos.Y); Result = true; }
|
||
|
||
if(Target.Scale.X !== null) { Current.Scale.X = Lerp(Initial.Scale.X, t, Target.Scale.X); Result = true; }
|
||
if(Target.Scale.Y !== null) { Current.Scale.Y = Lerp(Initial.Scale.Y, t, Target.Scale.Y); Result = true; }
|
||
|
||
if(Target.Rotation.X !== null) { Current.Rotation.X = Lerp(Initial.Rotation.X, t, Target.Rotation.X); Result = true; }
|
||
if(Target.Rotation.Y !== null) { Current.Rotation.Y = Lerp(Initial.Rotation.Y, t, Target.Rotation.Y); Result = true; }
|
||
if(Target.Rotation.Z !== null) { Current.Rotation.Z = Lerp(Initial.Rotation.Z, t, Target.Rotation.Z); Result = true; }
|
||
|
||
if(Target.Opacity !== null) { Current.Opacity = Lerp(Initial.Opacity, t, Target.Opacity); Result = true; }
|
||
|
||
if(Target.ScrollX !== null) { Current.ScrollX = Lerp(Initial.ScrollX, t, Target.ScrollX); Result = true; }
|
||
|
||
if(Target.ZIndex !== null) { Current.ZIndex = Lerp(Initial.ZIndex, t, Target.ZIndex); Result = true; }
|
||
}
|
||
return Result;
|
||
}
|
||
|
||
function
|
||
ShiftStage()
|
||
{
|
||
if(Nav.Transition.StageDurations.length) { Nav.Transition.StageDurations.shift(); }
|
||
if(Nav.Transition.Transforms.ButtonsTransitionContainer.TargetStages.length) { Nav.Transition.Transforms.ButtonsTransitionContainer.TargetStages.shift(); }
|
||
if(Nav.Transition.Transforms.ButtonsContainer.TargetStages.length) { Nav.Transition.Transforms.ButtonsContainer.TargetStages.shift(); }
|
||
if(Nav.Transition.Transforms.ButtonsContainerClone.TargetStages.length) { Nav.Transition.Transforms.ButtonsContainerClone.TargetStages.shift(); }
|
||
if(Nav.Transition.Transforms.RelevantButton.TargetStages.length) { Nav.Transition.Transforms.RelevantButton.TargetStages.shift(); }
|
||
}
|
||
|
||
function
|
||
MergeTransform(Dest, Src)
|
||
{
|
||
if(Src.Pos.X !== null) { Dest.Pos.X = Src.Pos.X; }
|
||
if(Src.Pos.Y !== null) { Dest.Pos.Y = Src.Pos.Y; }
|
||
|
||
if(Src.Scale.X !== null) { Dest.Scale.X = Src.Scale.X; }
|
||
if(Src.Scale.Y !== null) { Dest.Scale.Y = Src.Scale.Y; }
|
||
|
||
if(Src.Rotation.X !== null) { Dest.Rotation.X = Src.Rotation.X; }
|
||
if(Src.Rotation.Y !== null) { Dest.Rotation.Y = Src.Rotation.Y; }
|
||
if(Src.Rotation.Z !== null) { Dest.Rotation.Z = Src.Rotation.Z; }
|
||
|
||
if(Src.Opacity !== null) { Dest.Opacity = Src.Opacity; }
|
||
|
||
if(Src.ScrollX !== null) { Dest.ScrollX = Src.ScrollX; }
|
||
|
||
if(Src.ZIndex !== null) { Dest.ZIndex = Src.ZIndex; }
|
||
}
|
||
|
||
function
|
||
DoTransitionStage(Now)
|
||
{
|
||
if(Nav.Transition.StageDurations.length)
|
||
{
|
||
if(Nav.Transition.StartTime === undefined)
|
||
{
|
||
Nav.Transition.StartTime = Now;
|
||
}
|
||
|
||
var Elapsed = Now - Nav.Transition.StartTime;
|
||
var Duration = Nav.Transition.StageDurations[0];
|
||
if(Duration === 0)
|
||
{
|
||
// Instant transform
|
||
if(Nav.Transition.Transforms.ButtonsTransitionContainer.TargetStages.length)
|
||
{
|
||
MergeTransform(Nav.Transition.Transforms.ButtonsTransitionContainer.Current, Nav.Transition.Transforms.ButtonsTransitionContainer.TargetStages[0]);
|
||
ApplyTransform(Nav.Transition.ButtonsTransitionContainerElement, Nav.Transition.Transforms.ButtonsTransitionContainer.Current);
|
||
}
|
||
if(Nav.Transition.Transforms.ButtonsContainer.TargetStages.length)
|
||
{
|
||
MergeTransform(Nav.Transition.Transforms.ButtonsContainer.Current, Nav.Transition.Transforms.ButtonsContainer.TargetStages[0]);
|
||
ApplyTransform(Nav.ButtonsContainer, Nav.Transition.Transforms.ButtonsContainer.Current);
|
||
}
|
||
if(Nav.Transition.Transforms.ButtonsContainerClone.TargetStages.length)
|
||
{
|
||
MergeTransform(Nav.Transition.Transforms.ButtonsContainerClone.Current, Nav.Transition.Transforms.ButtonsContainerClone.TargetStages[0]);
|
||
ApplyTransform(Nav.Transition.ButtonsContainerCloneElement, Nav.Transition.Transforms.ButtonsContainerClone.Current);
|
||
}
|
||
if(Nav.Transition.Transforms.RelevantButton.TargetStages.length)
|
||
{
|
||
MergeTransform(Nav.Transition.Transforms.RelevantButton.Current, Nav.Transition.Transforms.RelevantButton.TargetStages[0]);
|
||
ApplyTransform(Nav.Transition.RelevantButtonElement, Nav.Transition.Transforms.RelevantButton.Current);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// Lerp
|
||
var t = Clamp01(Elapsed / Duration);
|
||
if(LerpTransforms(Nav.Transition.Transforms.ButtonsTransitionContainer, t))
|
||
{
|
||
ApplyTransform(Nav.Transition.ButtonsTransitionContainerElement, Nav.Transition.Transforms.ButtonsTransitionContainer.Current);
|
||
}
|
||
if(LerpTransforms(Nav.Transition.Transforms.ButtonsContainer, t))
|
||
{
|
||
ApplyTransform(Nav.ButtonsContainer, Nav.Transition.Transforms.ButtonsContainer.Current);
|
||
}
|
||
if(LerpTransforms(Nav.Transition.Transforms.ButtonsContainerClone, t))
|
||
{
|
||
ApplyTransform(Nav.Transition.ButtonsContainerCloneElement, Nav.Transition.Transforms.ButtonsContainerClone.Current);
|
||
}
|
||
if(LerpTransforms(Nav.Transition.Transforms.RelevantButton, t))
|
||
{
|
||
ApplyTransform(Nav.Transition.RelevantButtonElement, Nav.Transition.Transforms.RelevantButton.Current);
|
||
}
|
||
}
|
||
if(TransformsComplete(Nav.Transition.Transforms))
|
||
{
|
||
FinaliseTransforms(Nav.Transition.Transforms);
|
||
}
|
||
Nav.Transition.RequestedFrame = window.requestAnimationFrame(DoTransitionStage);
|
||
}
|
||
else
|
||
{
|
||
Nav.Transition.RequestedFrame = undefined;
|
||
ResetButtonsContainerClone();
|
||
DequeueInteraction();
|
||
}
|
||
}
|
||
|
||
function
|
||
CompressTransitionStages()
|
||
{
|
||
while(Nav.Transition.StageDurations.length > 1)
|
||
{
|
||
Nav.Transition.StageDurations.shift()
|
||
}
|
||
Nav.Transition.StageDurations[0] = 0;
|
||
|
||
while(Nav.Transition.Transforms.ButtonsTransitionContainer.TargetStages.length > 1)
|
||
{
|
||
Nav.Transition.Transforms.ButtonsTransitionContainer.TargetStages.shift();
|
||
}
|
||
|
||
while(Nav.Transition.Transforms.ButtonsContainer.TargetStages.length > 1)
|
||
{
|
||
Nav.Transition.Transforms.ButtonsContainer.TargetStages.shift();
|
||
}
|
||
|
||
while(Nav.Transition.Transforms.ButtonsContainerClone.TargetStages.length > 1)
|
||
{
|
||
Nav.Transition.Transforms.ButtonsContainerClone.TargetStages.shift();
|
||
}
|
||
|
||
while(Nav.Transition.Transforms.RelevantButton.TargetStages.length > 1)
|
||
{
|
||
Nav.Transition.Transforms.RelevantButton.TargetStages.shift();
|
||
}
|
||
}
|
||
|
||
function
|
||
DoTransition()
|
||
{
|
||
if(Nav.Transition.Enabled == false)
|
||
{
|
||
CompressTransitionStages();
|
||
}
|
||
|
||
CopyTransform(Nav.Transition.Transforms.ButtonsTransitionContainer.Initial, Nav.Transition.Transforms.ButtonsTransitionContainer.Current);
|
||
CopyTransform(Nav.Transition.Transforms.ButtonsContainer.Initial, Nav.Transition.Transforms.ButtonsContainer.Current);
|
||
CopyTransform(Nav.Transition.Transforms.ButtonsContainerClone.Initial, Nav.Transition.Transforms.ButtonsContainerClone.Current);
|
||
CopyTransform(Nav.Transition.Transforms.RelevantButton.Initial, Nav.Transition.Transforms.RelevantButton.Current);
|
||
|
||
Nav.Transition.StartTime = undefined;
|
||
Nav.Transition.RequestedFrame = window.requestAnimationFrame(DoTransitionStage);
|
||
}
|
||
|
||
function
|
||
NodesMatch(A, B)
|
||
{
|
||
var Result = false;
|
||
var i = 0;
|
||
if(A && B)
|
||
{
|
||
Result = true;
|
||
for(; i < A.length && i < B.length && A[i] == B[i];)
|
||
{
|
||
++i;
|
||
}
|
||
if(i != A.length || i != B.length)
|
||
{
|
||
Result = false;
|
||
}
|
||
}
|
||
else if(!A && !B)
|
||
{
|
||
Result = true;
|
||
}
|
||
return Result;
|
||
}
|
||
|
||
function
|
||
ButtonAndLevelMatch(Button, Level)
|
||
{
|
||
var Result = true;
|
||
if(Button && Level)
|
||
{
|
||
var ButtonProjects = Button.Projects;
|
||
var ButtonEntries = Button.Entries;
|
||
if(Button.Projects && Button.Projects.length === undefined)
|
||
{
|
||
ButtonProjects = Button.Projects.querySelectorAll(":scope > .cineraIndexProject");
|
||
ButtonEntries = Button.Projects.querySelectorAll(":scope > .cineraIndexEntries > div");
|
||
if(ButtonProjects.length == 0)
|
||
{
|
||
ButtonProjects = null;
|
||
}
|
||
if(ButtonEntries.length == 0)
|
||
{
|
||
ButtonEntries = null;
|
||
}
|
||
}
|
||
|
||
if(!NodesMatch(Level.Projects, ButtonProjects))
|
||
{
|
||
Result = false;
|
||
}
|
||
|
||
if(!NodesMatch(Level.Entries, ButtonEntries))
|
||
{
|
||
Result = false;
|
||
}
|
||
|
||
if(Button.HeadIndex != Level.HeadIndex)
|
||
{
|
||
Result = false;
|
||
}
|
||
if(Button.TailIndex != Level.TailIndex)
|
||
{
|
||
Result = false;
|
||
}
|
||
}
|
||
return Result;
|
||
}
|
||
|
||
function
|
||
NullTarget()
|
||
{
|
||
let Result = {
|
||
Pos: { X: null, Y: null, }, Scale: { X: null, Y: null, }, Rotation: { X: null, Y: null, Z: null, },
|
||
Opacity: null, ScrollX: null, ZIndex: null,
|
||
};
|
||
return Result;
|
||
}
|
||
|
||
function
|
||
ComputeButtonGeometryRelativeToGrid(Button)
|
||
{
|
||
var Result = {
|
||
Pos: {
|
||
X: null,
|
||
Y: null,
|
||
},
|
||
Scale: {
|
||
X: null,
|
||
Y: null,
|
||
},
|
||
};
|
||
|
||
var ButtonStyle = window.getComputedStyle(Button);
|
||
|
||
var GridDimX = Nav.GridDim.X;
|
||
var GridDimY = Nav.GridDim.Y;
|
||
|
||
var ButtonDimX = parseInt(ButtonStyle.width);
|
||
var ButtonDimY = parseInt(ButtonStyle.height);
|
||
|
||
Result.Scale.X = ButtonDimX / GridDimX;
|
||
Result.Scale.Y = ButtonDimY / GridDimY;
|
||
|
||
var ButtonPosX = Button.offsetLeft;
|
||
var ButtonPosY = Button.offsetTop;
|
||
|
||
if(!Nav.SortChronological)
|
||
{
|
||
ButtonPosX = GridDimX - ButtonPosX - ButtonDimX;
|
||
ButtonPosY = GridDimY - ButtonPosY - ButtonDimY;
|
||
}
|
||
|
||
var ButtonCentreX = ButtonDimX / 2 + ButtonPosX;
|
||
var ButtonCentreY = ButtonDimY / 2 + ButtonPosY;
|
||
|
||
var GridCentreX = GridDimX / 2;
|
||
var GridCentreY = GridDimY / 2;
|
||
|
||
Result.Pos.X = ButtonCentreX - GridCentreX;
|
||
Result.Pos.Y = ButtonCentreY - GridCentreY;
|
||
|
||
return Result;
|
||
}
|
||
|
||
function
|
||
GetItemType(Level)
|
||
{
|
||
var Result = null;
|
||
if(Level.Projects && Level.Projects.length !== undefined && Level.Projects.length > 0)
|
||
{
|
||
Result = item_type.PROJECT;
|
||
}
|
||
else if(Level.Entries && Level.Entries.length !== undefined && Level.Entries.length > 0)
|
||
{
|
||
Result = item_type.ENTRY;
|
||
}
|
||
return Result;
|
||
}
|
||
|
||
function
|
||
GetTraversalLevelBundle()
|
||
{
|
||
var Result = {
|
||
Generation: null,
|
||
This: null,
|
||
Parent: null,
|
||
Type: null
|
||
};
|
||
Result.Generation = Nav.TraversalStack.length;
|
||
Result.This = Nav.TraversalStack[Result.Generation - 1];
|
||
Result.Parent = Nav.TraversalStack[Result.Generation - 2];
|
||
Result.Type = GetItemType(Result.This);
|
||
return Result;
|
||
}
|
||
|
||
function
|
||
FitText(Element, ItemEnd)
|
||
{
|
||
var Paragraph = Element.firstElementChild;
|
||
var ParagraphStyle = window.getComputedStyle(Paragraph);
|
||
var ElementStyle = window.getComputedStyle(Element);
|
||
var Width = parseInt(ElementStyle.width);
|
||
var Height = parseInt(ElementStyle.height);
|
||
|
||
Element.style.alignItems = "flex-start"; // NOTE(matt): Allows IsOverflowed() to work on a flex-end Element
|
||
var FontSize = parseInt(window.getComputedStyle(Element).fontSize);
|
||
while(FontSize >= 10.2 && (IsOverflowed(Element) || parseInt(ParagraphStyle.width) > Width || parseInt(ParagraphStyle.height) > Height))
|
||
{
|
||
FontSize -= 0.2;
|
||
Element.style.fontSize = FontSize + "px";
|
||
}
|
||
|
||
if(IsOverflowed(Element) || parseInt(ParagraphStyle.width) > Width || parseInt(ParagraphStyle.height) > Height)
|
||
{
|
||
Element.style.fontWeight = "normal";
|
||
}
|
||
|
||
var IsHeadOrTailAndTooTallForElement = ItemEnd !== undefined && (Element.scrollHeight > Element.clientHeight || parseInt(ParagraphStyle.height) > Height);
|
||
// NOTE(matt): Leave "flex-start" in place for tall items, to keep their beginning visible
|
||
if(!IsHeadOrTailAndTooTallForElement)
|
||
{
|
||
Element.style.alignItems = null;
|
||
}
|
||
}
|
||
|
||
function
|
||
AppendItemToButton(Button, Text, ItemEnd)
|
||
{
|
||
var ButtonElementStyle = window.getComputedStyle(Button.Element);
|
||
var VerticalBorderAllowance = parseInt(ButtonElementStyle.borderLeftWidth) + parseInt(ButtonElementStyle.borderRightWidth);
|
||
var HorizontalBorderAllowance;
|
||
var ItemElement = document.createElement("div");
|
||
ItemElement.classList.add("cineraText");
|
||
switch(ItemEnd)
|
||
{
|
||
case item_end.HEAD:
|
||
{
|
||
HorizonalBorderAllowance = parseInt(ButtonElementStyle.borderTopWidth);
|
||
ItemElement.classList.add("head-item");
|
||
} break;
|
||
case item_end.TAIL:
|
||
{
|
||
HorizonalBorderAllowance = parseInt(ButtonElementStyle.borderBottomWidth);
|
||
ItemElement.classList.add("tail-item");
|
||
} break;
|
||
}
|
||
|
||
if(!Nav.SortChronological)
|
||
{
|
||
ItemElement.style.transform = "rotate3d(0, 0, 1, 180deg)";
|
||
}
|
||
|
||
var ParagraphNode = document.createElement("p");
|
||
ParagraphNode.textContent = Text;
|
||
ItemElement.appendChild(ParagraphNode);
|
||
var Item = Button.Element.appendChild(ItemElement);
|
||
|
||
// NOTE(matt): This enables Safari to apply height 50% to the head / tail items
|
||
var ButtonWidth = parseInt(ButtonElementStyle.width);
|
||
var ButtonHeight = parseInt(ButtonElementStyle.height);
|
||
SetDim(Button.Element, ButtonWidth + "px", ButtonHeight + "px");
|
||
////
|
||
|
||
FitText(Item, ItemEnd);
|
||
}
|
||
|
||
function
|
||
UpdateButtons(TransitionType, RelevantButton, PoppedLevel)
|
||
{
|
||
if(GridSizeIsSupported(Nav.GridSize))
|
||
{
|
||
var LevelBundle = GetTraversalLevelBundle();
|
||
|
||
Nav.Controls.GridTraversal.Prev.children[0].textContent = "←";
|
||
Nav.Controls.GridTraversal.PrevAscends = false;
|
||
Nav.Controls.GridTraversal.NextAscends = false;
|
||
if(LevelBundle.Generation <= 1)
|
||
{
|
||
Nav.Controls.GridTraversal.Ascend.classList.add("nowhere");
|
||
Nav.Controls.GridTraversal.Prev.classList.add("nowhere");
|
||
Nav.Controls.GridTraversal.Next.classList.add("nowhere");
|
||
Nav.Controls.GridTraversal.Next.classList.remove("ascension");
|
||
}
|
||
else
|
||
{
|
||
Nav.Controls.GridTraversal.Ascend.classList.remove("nowhere");
|
||
Nav.Controls.GridTraversal.Prev.classList.remove("nowhere");
|
||
Nav.Controls.GridTraversal.Next.classList.remove("nowhere");
|
||
|
||
if(!HasPrevSibling(LevelBundle.This))
|
||
{
|
||
Nav.Controls.GridTraversal.Prev.classList.add("nowhere");
|
||
}
|
||
else if(SiblingIsLeaf(siblings.PREV))
|
||
{
|
||
Nav.Controls.GridTraversal.PrevAscends = true;
|
||
Nav.Controls.GridTraversal.Prev.children[0].textContent = Nav.SortChronological ? "↰" : "↲";
|
||
}
|
||
|
||
if(!HasNextSibling(LevelBundle.This))
|
||
{
|
||
Nav.Controls.GridTraversal.Next.classList.add("nowhere");
|
||
Nav.Controls.GridTraversal.Next.classList.remove("ascension");
|
||
}
|
||
else if(SiblingIsLeaf(siblings.NEXT))
|
||
{
|
||
Nav.Controls.GridTraversal.NextAscends = true;
|
||
Nav.Controls.GridTraversal.Next.classList.add("ascension");
|
||
}
|
||
else
|
||
{
|
||
Nav.Controls.GridTraversal.Next.classList.remove("ascension");
|
||
}
|
||
}
|
||
|
||
var Distribution = ComputeItemDistribution(LevelBundle.This);
|
||
|
||
// NOTE(matt): Centre-alignment. If people would prefer left-alignment, we do that here
|
||
//
|
||
// We're doing simple 1D centring here, so would need to do correct 2D centring
|
||
var HalfEmptyButtonCount = (Nav.Buttons.length - (Distribution.ProjectsToPlace + Distribution.EntriesToPlace)) / 2;
|
||
var EmptyPadding = 0;
|
||
//EmptyPadding = Math.floor(HalfEmptyButtonCount); // NOTE(matt): Comment out to disable centring
|
||
//
|
||
|
||
var Prev = null;
|
||
var ButtonInfo = { HeadIndex: null, TailIndex: null, ItemCount: null, Theme: null, };
|
||
var DoingEntries = false;
|
||
|
||
if(TransitionType !== undefined)
|
||
{
|
||
CloneButtonsContainer();
|
||
}
|
||
|
||
for(var ButtonIndex = 0; ButtonIndex < Nav.Buttons.length; ++ButtonIndex)
|
||
{
|
||
let This = Nav.Buttons[ButtonIndex];
|
||
EmptyAndResetButton(This);
|
||
if(EmptyPadding > 0)
|
||
{
|
||
--EmptyPadding;
|
||
}
|
||
else
|
||
{
|
||
if(Distribution.ProjectsToPlace > 0 || Distribution.EntriesToPlace > 0)
|
||
{
|
||
if(Distribution.ProjectsToPlace > 0)
|
||
{
|
||
This.Projects = LevelBundle.This.Projects;
|
||
if(Distribution.ProjectsToPlace == 1 || Distribution.ProjectsToPlace == Distribution.ButtonsForProjects - ButtonIndex)
|
||
{
|
||
Distribution.FullButtonProjectCount = 1;
|
||
}
|
||
|
||
ButtonInfo = SetButtonInfo(This, Prev, LevelBundle.This, Distribution);
|
||
if(ButtonInfo.ItemCount == 1)
|
||
{
|
||
This.Projects = LevelBundle.This.Projects[ButtonInfo.HeadIndex];
|
||
}
|
||
Distribution.ProjectsToPlace -= ButtonInfo.ItemCount;
|
||
|
||
if(Distribution.FullButtonProjectCount == 1)
|
||
{
|
||
This.Element.classList.add("leaf");
|
||
var TextElement = document.createElement("p");
|
||
TextElement.classList.add("cineraText");
|
||
if(!Nav.SortChronological)
|
||
{
|
||
TextElement.style.transform = "rotate3d(0, 0, 1, 180deg)";
|
||
}
|
||
var Text = LevelBundle.This.Projects[ButtonInfo.HeadIndex].querySelector(".cineraProjectTitle").innerText;
|
||
var TextNode = document.createTextNode(Text);
|
||
TextElement.appendChild(TextNode);
|
||
This.Element.appendChild(TextElement);
|
||
FitText(This.Element);
|
||
}
|
||
else
|
||
{
|
||
var HeadText = LevelBundle.This.Projects[ButtonInfo.HeadIndex].querySelector(".cineraProjectTitle").innerText;
|
||
AppendItemToButton(This, HeadText, item_end.HEAD);
|
||
|
||
var TailText = LevelBundle.This.Projects[ButtonInfo.TailIndex].querySelector(".cineraProjectTitle").innerText;
|
||
AppendItemToButton(This, TailText, item_end.TAIL);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
This.Entries = LevelBundle.This.Entries;
|
||
if(!DoingEntries)
|
||
{
|
||
Prev = null;
|
||
DoingEntries = true;
|
||
}
|
||
|
||
if(Distribution.EntriesToPlace == 1 || Distribution.EntriesToPlace == Distribution.ButtonsForEntries - ButtonIndex)
|
||
{
|
||
Distribution.FullButtonEntryCount = 1;
|
||
}
|
||
|
||
ButtonInfo = SetButtonInfo(This, Prev, LevelBundle.This, Distribution);
|
||
if(ButtonInfo.ItemCount == 1)
|
||
{
|
||
This.Entries = This.Entries[ButtonInfo.HeadIndex];
|
||
}
|
||
Distribution.EntriesToPlace -= ButtonInfo.ItemCount;
|
||
|
||
if(Distribution.FullButtonEntryCount == 1)
|
||
{
|
||
This.Element.classList.add("leaf");
|
||
var ButtonLink = document.createElement("a");
|
||
ButtonLink.classList.add("cineraText");
|
||
if(!Nav.SortChronological)
|
||
{
|
||
ButtonLink.style.transform = "rotate3d(0, 0, 1, 180deg)";
|
||
}
|
||
var EntryAddress = LevelBundle.This.Entries[ButtonInfo.HeadIndex].lastElementChild.getAttribute("href");
|
||
ButtonLink.setAttribute("href", EntryAddress);
|
||
var Text = LevelBundle.This.Entries[ButtonInfo.HeadIndex].innerText;
|
||
var TextNode = document.createTextNode(Text);
|
||
ButtonLink.appendChild(TextNode);
|
||
|
||
This.Element.appendChild(ButtonLink);
|
||
FitText(This.Element);
|
||
}
|
||
else
|
||
{
|
||
var HeadText = LevelBundle.This.Entries[ButtonInfo.HeadIndex].innerText;
|
||
AppendItemToButton(This, HeadText, item_end.HEAD);
|
||
|
||
var TailText = LevelBundle.This.Entries[ButtonInfo.TailIndex].innerText;
|
||
AppendItemToButton(This, TailText, item_end.TAIL);
|
||
}
|
||
}
|
||
|
||
This.Element.classList.add(ButtonInfo.Theme);
|
||
Prev = ButtonInfo;
|
||
}
|
||
else
|
||
{
|
||
This.Element.classList.add(Nav.Nexus.classList[0]);
|
||
}
|
||
}
|
||
if(PoppedLevel && !Nav.Transition.RelevantButtonElement && ButtonAndLevelMatch(This, PoppedLevel))
|
||
{
|
||
Nav.Transition.RelevantButtonElement = This.Element;
|
||
}
|
||
}
|
||
if(TransitionType !== undefined)
|
||
{
|
||
switch(TransitionType)
|
||
{
|
||
case transition_type.SIBLING_SHIFT_PREV:
|
||
{
|
||
// Init targets
|
||
//// ButtonsTransitionContainer
|
||
let TargetA0 = NullTarget();
|
||
var Padding = Nav.GridColumnGap;
|
||
if(Nav.SortChronological)
|
||
{
|
||
TargetA0.ScrollX = 0;
|
||
}
|
||
else
|
||
{
|
||
TargetA0.ScrollX = Nav.GridDim.X + Padding;
|
||
}
|
||
|
||
Nav.Transition.StageDurations.push(320);
|
||
Nav.Transition.Transforms.ButtonsTransitionContainer.TargetStages.push(TargetA0);
|
||
|
||
// Prep
|
||
Nav.Transition.ButtonsContainerCloneElement.style.position = "relative";
|
||
|
||
var ScrollX;
|
||
if(Nav.SortChronological)
|
||
{
|
||
Nav.ButtonsContainer.style.order = 0;
|
||
Nav.Transition.ButtonsContainerCloneElement.style.order = 1;
|
||
ScrollX = Nav.GridDim.X + Padding;
|
||
}
|
||
else
|
||
{
|
||
Nav.ButtonsContainer.style.order = 1;
|
||
Nav.Transition.ButtonsContainerCloneElement.style.order = 0;
|
||
ScrollX = 0;
|
||
}
|
||
|
||
Nav.Transition.ButtonsContainerCloneElement.style.paddingLeft = Padding + "px";
|
||
|
||
Nav.Transition.Transforms.ButtonsTransitionContainer.Current.ScrollX = ScrollX;
|
||
ApplyTransform(Nav.Transition.ButtonsTransitionContainerElement, Nav.Transition.Transforms.ButtonsTransitionContainer.Current);
|
||
} break;
|
||
case transition_type.SIBLING_SHIFT_NEXT:
|
||
{
|
||
// Init targets
|
||
////
|
||
let TargetA0 = NullTarget();
|
||
var Padding = Nav.GridColumnGap;
|
||
if(Nav.SortChronological)
|
||
{
|
||
TargetA0.ScrollX = Nav.GridDim.X + Padding;
|
||
}
|
||
else
|
||
{
|
||
TargetA0.ScrollX = 0;
|
||
}
|
||
|
||
Nav.Transition.StageDurations.push(320);
|
||
Nav.Transition.Transforms.ButtonsTransitionContainer.TargetStages.push(TargetA0);
|
||
|
||
// Prep
|
||
Nav.Transition.ButtonsContainerCloneElement.style.position = "relative";
|
||
|
||
var ScrollX;
|
||
|
||
if(Nav.SortChronological)
|
||
{
|
||
Nav.ButtonsContainer.style.order = 1;
|
||
Nav.Transition.ButtonsContainerCloneElement.style.order = 0;
|
||
ScrollX = 0;
|
||
}
|
||
else
|
||
{
|
||
Nav.ButtonsContainer.style.order = 0;
|
||
Nav.Transition.ButtonsContainerCloneElement.style.order = 1;
|
||
ScrollX = Nav.GridDim.X + Padding;
|
||
}
|
||
|
||
Nav.Transition.ButtonsContainerCloneElement.style.paddingRight = Padding + "px";
|
||
|
||
Nav.Transition.Transforms.ButtonsTransitionContainer.Current.ScrollX = ScrollX;
|
||
ApplyTransform(Nav.Transition.ButtonsTransitionContainerElement, Nav.Transition.Transforms.ButtonsTransitionContainer.Current);
|
||
} break;
|
||
case transition_type.PROJECT_ENTRY:
|
||
{
|
||
// Init targets
|
||
//// ButtonsContainer
|
||
let TargetA0 = NullTarget();
|
||
let TargetA1 = NullTarget();
|
||
let TargetA2 = NullTarget();
|
||
let TargetA3 = NullTarget();
|
||
|
||
if(Nav.SortChronological)
|
||
{
|
||
TargetA0.Rotation.Y = 90;
|
||
}
|
||
else
|
||
{
|
||
TargetA0.Rotation.Y = -90;
|
||
}
|
||
TargetA1.ZIndex = 1;
|
||
TargetA2.Rotation.Y = 0;
|
||
TargetA3.Pos.X = 0;
|
||
TargetA3.Pos.Y = 0;
|
||
TargetA3.Scale.X = 1;
|
||
TargetA3.Scale.Y = 1;
|
||
|
||
//// RelevantButton
|
||
let TargetB0 = NullTarget();
|
||
let TargetB1 = NullTarget();
|
||
let TargetB2 = NullTarget();
|
||
|
||
if(Nav.SortChronological)
|
||
{
|
||
TargetB0.Rotation.Y = -90;
|
||
TargetB2.Rotation.Y = -180;
|
||
}
|
||
else
|
||
{
|
||
TargetB0.Rotation.Y = 90;
|
||
TargetB2.Rotation.Y = 180;
|
||
}
|
||
|
||
//// ButtonsContainerClone
|
||
let TargetC0 = NullTarget();
|
||
let TargetC1 = NullTarget();
|
||
|
||
TargetC1.ZIndex = 0;
|
||
|
||
|
||
Nav.Transition.StageDurations.push(80);
|
||
Nav.Transition.StageDurations.push(0);
|
||
Nav.Transition.StageDurations.push(80);
|
||
Nav.Transition.StageDurations.push(160);
|
||
|
||
Nav.Transition.Transforms.ButtonsContainer.TargetStages.push(TargetA0);
|
||
Nav.Transition.Transforms.ButtonsContainer.TargetStages.push(TargetA1);
|
||
Nav.Transition.Transforms.ButtonsContainer.TargetStages.push(TargetA2);
|
||
Nav.Transition.Transforms.ButtonsContainer.TargetStages.push(TargetA3);
|
||
Nav.Transition.Transforms.RelevantButton.TargetStages.push(TargetB0);
|
||
Nav.Transition.Transforms.RelevantButton.TargetStages.push(TargetB1);
|
||
Nav.Transition.Transforms.RelevantButton.TargetStages.push(TargetB2);
|
||
Nav.Transition.Transforms.ButtonsContainerClone.TargetStages.push(TargetC0);
|
||
Nav.Transition.Transforms.ButtonsContainerClone.TargetStages.push(TargetC1);
|
||
|
||
// Prep
|
||
var RelevantButtonIndex = GetIndexOfButton(RelevantButton);
|
||
Nav.Transition.RelevantButtonElement = Nav.Transition.ButtonsContainerCloneElement.children[RelevantButtonIndex];
|
||
|
||
var ButtonGeometry = ComputeButtonGeometryRelativeToGrid(Nav.Transition.RelevantButtonElement);
|
||
|
||
Nav.Transition.Transforms.ButtonsContainer.Current.Pos.X = ButtonGeometry.Pos.X;
|
||
Nav.Transition.Transforms.ButtonsContainer.Current.Pos.Y = ButtonGeometry.Pos.Y;
|
||
Nav.Transition.Transforms.ButtonsContainer.Current.Scale.X = ButtonGeometry.Scale.X;
|
||
Nav.Transition.Transforms.ButtonsContainer.Current.Scale.Y = ButtonGeometry.Scale.Y;
|
||
if(Nav.SortChronological)
|
||
{
|
||
Nav.Transition.Transforms.ButtonsContainer.Current.Rotation.Y = 180;
|
||
}
|
||
else
|
||
{
|
||
Nav.Transition.Transforms.ButtonsContainer.Current.Rotation.Y = -180;
|
||
}
|
||
|
||
Nav.Transition.Transforms.ButtonsContainer.Current.ZIndex = 0;
|
||
|
||
Nav.Transition.Transforms.ButtonsContainerClone.Current.ZIndex = 1;
|
||
|
||
ApplyTransform(Nav.Transition.ButtonsContainerCloneElement, Nav.Transition.Transforms.ButtonsContainerClone.Current);
|
||
ApplyTransform(Nav.ButtonsContainer, Nav.Transition.Transforms.ButtonsContainer.Current);
|
||
} break;
|
||
case transition_type.PROJECT_EXIT:
|
||
{
|
||
// Init targets
|
||
//// ButtonsContainer
|
||
let TargetA0 = NullTarget();
|
||
let TargetA1 = NullTarget();
|
||
let TargetA2 = NullTarget();
|
||
TargetA2.ZIndex = 1;
|
||
|
||
//// RelevantButton
|
||
let TargetB0 = NullTarget();
|
||
let TargetB1 = NullTarget();
|
||
let TargetB2 = NullTarget();
|
||
let TargetB3 = NullTarget();
|
||
|
||
if(Nav.SortChronological)
|
||
{
|
||
TargetB1.Rotation.Y = -90;
|
||
}
|
||
else
|
||
{
|
||
TargetB1.Rotation.Y = 90;
|
||
}
|
||
TargetB3.Rotation.Y = 0;
|
||
|
||
//// ButtonsContainerClone
|
||
let TargetC0 = NullTarget();
|
||
let TargetC1 = NullTarget();
|
||
let TargetC2 = NullTarget();
|
||
let TargetC3 = NullTarget();
|
||
|
||
var ButtonGeometry = ComputeButtonGeometryRelativeToGrid(Nav.Transition.RelevantButtonElement);
|
||
TargetC0.Pos.X = ButtonGeometry.Pos.X;
|
||
TargetC0.Pos.Y = ButtonGeometry.Pos.Y;
|
||
TargetC0.Scale.X = ButtonGeometry.Scale.X;
|
||
TargetC0.Scale.Y = ButtonGeometry.Scale.Y;
|
||
if(Nav.SortChronological)
|
||
{
|
||
TargetC1.Rotation.Y = 90;
|
||
TargetC3.Rotation.Y = 180;
|
||
}
|
||
else
|
||
{
|
||
TargetC1.Rotation.Y = -90;
|
||
TargetC3.Rotation.Y = -180;
|
||
}
|
||
TargetC2.ZIndex = 0;
|
||
|
||
|
||
Nav.Transition.StageDurations.push(160);
|
||
Nav.Transition.StageDurations.push(80);
|
||
Nav.Transition.StageDurations.push(0);
|
||
Nav.Transition.StageDurations.push(80);
|
||
|
||
Nav.Transition.Transforms.ButtonsContainer.TargetStages.push(TargetA0);
|
||
Nav.Transition.Transforms.ButtonsContainer.TargetStages.push(TargetA1);
|
||
Nav.Transition.Transforms.ButtonsContainer.TargetStages.push(TargetA2);
|
||
Nav.Transition.Transforms.RelevantButton.TargetStages.push(TargetB0);
|
||
Nav.Transition.Transforms.RelevantButton.TargetStages.push(TargetB1);
|
||
Nav.Transition.Transforms.RelevantButton.TargetStages.push(TargetB2);
|
||
Nav.Transition.Transforms.RelevantButton.TargetStages.push(TargetB3);
|
||
Nav.Transition.Transforms.ButtonsContainerClone.TargetStages.push(TargetC0);
|
||
Nav.Transition.Transforms.ButtonsContainerClone.TargetStages.push(TargetC1);
|
||
Nav.Transition.Transforms.ButtonsContainerClone.TargetStages.push(TargetC2);
|
||
Nav.Transition.Transforms.ButtonsContainerClone.TargetStages.push(TargetC3);
|
||
|
||
// Prep
|
||
if(Nav.SortChronological)
|
||
{
|
||
Nav.Transition.Transforms.RelevantButton.Current.Rotation.Y = -180;
|
||
}
|
||
else
|
||
{
|
||
Nav.Transition.Transforms.RelevantButton.Current.Rotation.Y = 180;
|
||
}
|
||
ApplyTransform(Nav.Transition.RelevantButtonElement, Nav.Transition.Transforms.RelevantButton.Current);
|
||
|
||
Nav.Transition.Transforms.ButtonsContainer.Current.ZIndex = 0;
|
||
ApplyTransform(Nav.ButtonsContainer, Nav.Transition.Transforms.ButtonsContainer.Current);
|
||
|
||
Nav.Transition.Transforms.ButtonsContainerClone.Current.ZIndex = 1;
|
||
ApplyTransform(Nav.Transition.ButtonsContainerCloneElement, Nav.Transition.Transforms.ButtonsContainerClone.Current);
|
||
} break;
|
||
case transition_type.SUBDIVISION_DESCENT:
|
||
{
|
||
// Init targets
|
||
//// ButtonsContainer
|
||
let TargetA0 = NullTarget();
|
||
let TargetA1 = NullTarget();
|
||
|
||
TargetA0.Opacity = 1;
|
||
TargetA1.Pos.X = 0;
|
||
TargetA1.Pos.Y = 0;
|
||
TargetA1.Scale.X = 1;
|
||
TargetA1.Scale.Y = 1;
|
||
|
||
Nav.Transition.StageDurations.push(160);
|
||
Nav.Transition.StageDurations.push(160);
|
||
|
||
Nav.Transition.Transforms.ButtonsContainer.TargetStages.push(TargetA0);
|
||
Nav.Transition.Transforms.ButtonsContainer.TargetStages.push(TargetA1);
|
||
|
||
// Prep
|
||
var RelevantButtonIndex = GetIndexOfButton(RelevantButton);
|
||
Nav.Transition.RelevantButtonElement = Nav.Transition.ButtonsContainerCloneElement.children[RelevantButtonIndex];
|
||
|
||
var ButtonGeometry = ComputeButtonGeometryRelativeToGrid(Nav.Transition.RelevantButtonElement);
|
||
|
||
Nav.Transition.Transforms.ButtonsContainer.Current.Pos.X = ButtonGeometry.Pos.X;
|
||
Nav.Transition.Transforms.ButtonsContainer.Current.Pos.Y = ButtonGeometry.Pos.Y;
|
||
Nav.Transition.Transforms.ButtonsContainer.Current.Scale.X = ButtonGeometry.Scale.X;
|
||
Nav.Transition.Transforms.ButtonsContainer.Current.Scale.Y = ButtonGeometry.Scale.Y;
|
||
Nav.Transition.Transforms.ButtonsContainer.Current.Opacity = 0;
|
||
Nav.Transition.Transforms.ButtonsContainer.Current.ZIndex = 1;
|
||
|
||
Nav.Transition.Transforms.ButtonsContainerClone.Current.ZIndex = 0;
|
||
|
||
ApplyTransform(Nav.Transition.ButtonsContainerCloneElement, Nav.Transition.Transforms.ButtonsContainerClone.Current);
|
||
ApplyTransform(Nav.ButtonsContainer, Nav.Transition.Transforms.ButtonsContainer.Current);
|
||
} break;
|
||
case transition_type.SUBDIVISION_ASCENT:
|
||
{
|
||
// Init targets
|
||
//// ButtonsContainer
|
||
let TargetA0 = NullTarget();
|
||
let TargetA1 = NullTarget();
|
||
let TargetA2 = NullTarget();
|
||
TargetA2.ZIndex = 1;
|
||
|
||
//// ButtonsContainerClone
|
||
let TargetC0 = NullTarget();
|
||
let TargetC1 = NullTarget();
|
||
let TargetC2 = NullTarget();
|
||
|
||
var ButtonGeometry = ComputeButtonGeometryRelativeToGrid(Nav.Transition.RelevantButtonElement);
|
||
TargetC0.Pos.X = ButtonGeometry.Pos.X;
|
||
TargetC0.Pos.Y = ButtonGeometry.Pos.Y;
|
||
TargetC0.Scale.X = ButtonGeometry.Scale.X;
|
||
TargetC0.Scale.Y = ButtonGeometry.Scale.Y;
|
||
TargetC1.Opacity = 0;
|
||
TargetC2.ZIndex = 0;
|
||
|
||
|
||
Nav.Transition.StageDurations.push(160);
|
||
Nav.Transition.StageDurations.push(160);
|
||
Nav.Transition.StageDurations.push(0);
|
||
|
||
Nav.Transition.Transforms.ButtonsContainer.TargetStages.push(TargetA0);
|
||
Nav.Transition.Transforms.ButtonsContainer.TargetStages.push(TargetA1);
|
||
Nav.Transition.Transforms.ButtonsContainer.TargetStages.push(TargetA2);
|
||
Nav.Transition.Transforms.ButtonsContainerClone.TargetStages.push(TargetC0);
|
||
Nav.Transition.Transforms.ButtonsContainerClone.TargetStages.push(TargetC1);
|
||
Nav.Transition.Transforms.ButtonsContainerClone.TargetStages.push(TargetC2);
|
||
|
||
// Prep
|
||
Nav.Transition.Transforms.ButtonsContainer.Current.ZIndex = 0;
|
||
Nav.Transition.Transforms.ButtonsContainerClone.Current.ZIndex = 1;
|
||
ApplyTransform(Nav.Transition.ButtonsContainerCloneElement, Nav.Transition.Transforms.ButtonsContainerClone.Current);
|
||
ApplyTransform(Nav.ButtonsContainer, Nav.Transition.Transforms.ButtonsContainer.Current);
|
||
} break;
|
||
}
|
||
DoTransition();
|
||
}
|
||
}
|
||
}
|
||
|
||
function
|
||
PushButton(ButtonElement, Button)
|
||
{
|
||
var Level = {
|
||
Projects: Button.Projects,
|
||
Entries: Button.Entries,
|
||
HeadIndex: Button.HeadIndex,
|
||
TailIndex: Button.TailIndex,
|
||
};
|
||
|
||
var TransitionType = undefined;
|
||
if(Level.Projects !== null || Level.Entries !== null)
|
||
{
|
||
if(Level.Projects !== null)
|
||
{
|
||
if(Level.Projects.length === undefined)
|
||
{
|
||
var Entries = Level.Projects.querySelectorAll(":scope > .cineraIndexEntries > div");
|
||
var Projects = Level.Projects.querySelectorAll(":scope > .cineraIndexProject");
|
||
Level.Entries = Entries.length ? Entries : null;
|
||
Level.Projects = Projects.length ? Projects : null;
|
||
TransitionType = transition_type.PROJECT_ENTRY;
|
||
}
|
||
else
|
||
{
|
||
TransitionType = transition_type.SUBDIVISION_DESCENT;
|
||
}
|
||
Nav.TraversalStack.push(Level);
|
||
UpdateButtons(TransitionType, Button);
|
||
}
|
||
else
|
||
{
|
||
if(Level.Entries.length === undefined)
|
||
{
|
||
var Address = ButtonElement.lastElementChild.getAttribute("href");
|
||
location = Address;
|
||
}
|
||
else
|
||
{
|
||
Nav.TraversalStack.push(Level);
|
||
TransitionType = transition_type.SUBDIVISION_DESCENT;
|
||
UpdateButtons(TransitionType, Button);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
function
|
||
RangeContains(Range, Target)
|
||
{
|
||
return (Range.HeadIndex == null && Range.TailIndex == null) || (Range.HeadIndex <= Target && Range.TailIndex >= Target);
|
||
}
|
||
|
||
function
|
||
LengthOf(Level)
|
||
{
|
||
var Result = 0;
|
||
var Items = Level.Projects && Level.Projects.length > 0 ? Level.Projects : Level.Entries;
|
||
if(Items)
|
||
{
|
||
if(Level.HeadIndex !== null && Level.TailIndex !== null)
|
||
{
|
||
Result = Diff(Level.HeadIndex, Level.TailIndex) + 1;
|
||
}
|
||
else if(Items.length !== undefined)
|
||
{
|
||
Result = Items.length;
|
||
}
|
||
else
|
||
{
|
||
Result = 1;
|
||
}
|
||
}
|
||
return Result;
|
||
}
|
||
|
||
function
|
||
DereferenceLevel(Generation)
|
||
{
|
||
var Level = Nav.TraversalStack[Generation - 1];
|
||
var Result = {
|
||
Projects: Level.Projects,
|
||
Entries: Level.Entries,
|
||
HeadIndex: Level.HeadIndex,
|
||
TailIndex: Level.TailIndex,
|
||
};
|
||
return Result;
|
||
}
|
||
|
||
function
|
||
GetSubdivisionFor(LevelBundle, Index, TargetGeneration)
|
||
{
|
||
var Result = {
|
||
Projects: LevelBundle.This.Projects,
|
||
Entries: LevelBundle.This.Entries,
|
||
HeadIndex: null,
|
||
TailIndex: null,
|
||
};
|
||
|
||
var GenerationsToDistribute = 1;
|
||
var Generation = TargetGeneration ? TargetGeneration : LevelBundle.Generation;
|
||
var Parent = DereferenceLevel(Generation - GenerationsToDistribute);
|
||
while(!RangeContains(Parent, Index))
|
||
{
|
||
++GenerationsToDistribute;
|
||
Parent = DereferenceLevel(Generation - GenerationsToDistribute);
|
||
}
|
||
|
||
while(GenerationsToDistribute > 0)
|
||
{
|
||
var Distribution = ComputeItemDistribution(Parent);
|
||
|
||
var ToPlace = null;
|
||
var FullButtonItemCount = null;
|
||
var ButtonCount = null;
|
||
if(LevelBundle.Type == item_type.PROJECT)
|
||
{
|
||
ToPlace = Distribution.ProjectsToPlace;
|
||
FullButtonItemCount = Distribution.FullButtonProjectCount;
|
||
ButtonCount = Distribution.ButtonsForProjects;
|
||
}
|
||
else if(LevelBundle.Type = item_type.ENTRY)
|
||
{
|
||
ToPlace = Distribution.EntriesToPlace;
|
||
FullButtonItemCount = Distribution.FullButtonEntryCount;
|
||
ButtonCount = Distribution.ButtonsForEntries;
|
||
}
|
||
|
||
Result.HeadIndex = null;
|
||
Result.TailIndex = null;
|
||
for(var i = 0; i < ButtonCount; ++i)
|
||
{
|
||
if(ToPlace == ButtonCount - i) { FullButtonItemCount = 1; }
|
||
Result.HeadIndex = Result.TailIndex !== null ? Result.TailIndex + 1 : Parent.HeadIndex !== null ? Parent.HeadIndex : 0;
|
||
Result.TailIndex = Result.HeadIndex + Math.min(FullButtonItemCount, ToPlace) - 1;
|
||
if(RangeContains(Result, Index)) { break; }
|
||
ToPlace -= LengthOf(Result);
|
||
}
|
||
--GenerationsToDistribute;
|
||
Parent.HeadIndex = Result.HeadIndex;
|
||
Parent.TailIndex = Result.TailIndex;
|
||
}
|
||
|
||
return Result;
|
||
}
|
||
|
||
function
|
||
SiblingIsLeaf(SiblingID)
|
||
{
|
||
var LevelBundle = GetTraversalLevelBundle();
|
||
return LengthOf(GetSubdivisionFor(LevelBundle,
|
||
SiblingID == siblings.PREV ? LevelBundle.This.HeadIndex - 1 : LevelBundle.This.TailIndex + 1)) == 1;
|
||
}
|
||
|
||
function
|
||
ShiftToSibling(SiblingID)
|
||
{
|
||
var LevelBundle = GetTraversalLevelBundle();
|
||
|
||
if((SiblingID == siblings.PREV && HasPrevSibling(LevelBundle.This)) ||
|
||
(SiblingID == siblings.NEXT && HasNextSibling(LevelBundle.This)))
|
||
{
|
||
var TransitionType = SiblingID == siblings.PREV ? transition_type.SIBLING_SHIFT_PREV : transition_type.SIBLING_SHIFT_NEXT;
|
||
var CurrentItem = LevelBundle.Type == item_type.PROJECT ? LevelBundle.This.Projects : LevelBundle.This.Entries;
|
||
|
||
var TargetIndex = SiblingID == siblings.PREV ? LevelBundle.This.HeadIndex - 1 : LevelBundle.This.TailIndex + 1;
|
||
LevelBundle.Parent = Nav.TraversalStack[0];
|
||
var TransitionLevel;
|
||
for(let i = 0; i < LevelBundle.Generation; ++i)
|
||
{
|
||
LevelBundle.This = Nav.TraversalStack[i];
|
||
|
||
if((LevelBundle.Type == item_type.PROJECT && CurrentItem == LevelBundle.Parent.Projects) ||
|
||
(LevelBundle.Type == item_type.ENTRY && CurrentItem == LevelBundle.Parent.Entries))
|
||
{
|
||
if(!RangeContains(LevelBundle.This, TargetIndex))
|
||
{
|
||
Nav.TraversalStack[i] = GetSubdivisionFor(LevelBundle, TargetIndex, i + 1);
|
||
if(LengthOf(Nav.TraversalStack[i]) == 1)
|
||
{
|
||
TransitionType = transition_type.SUBDIVISION_ASCENT;
|
||
TransitionLevel = LevelBundle.This;
|
||
Nav.TraversalStack.pop();
|
||
}
|
||
}
|
||
}
|
||
LevelBundle.Parent = Nav.TraversalStack[i];
|
||
}
|
||
UpdateButtons(TransitionType, undefined, TransitionLevel);
|
||
}
|
||
else
|
||
{
|
||
DequeueInteraction();
|
||
}
|
||
}
|
||
|
||
function
|
||
Ascend()
|
||
{
|
||
var LevelBundle = GetTraversalLevelBundle();
|
||
if(LevelBundle.Generation > 1)
|
||
{
|
||
var TransitionType = LevelBundle.This.HeadIndex !== null && LevelBundle.This.TailIndex !== null ? transition_type.SUBDIVISION_ASCENT : transition_type.PROJECT_EXIT;
|
||
var PoppedLevel = Nav.TraversalStack.pop();
|
||
UpdateButtons(TransitionType, undefined, PoppedLevel);
|
||
}
|
||
}
|
||
|
||
function
|
||
ShiftToPrevSibling()
|
||
{
|
||
var LevelBundle = GetTraversalLevelBundle();
|
||
if(LevelBundle.Generation > 1)
|
||
{
|
||
if(Nav.Controls.GridTraversal.PrevAscends == true)
|
||
{
|
||
Ascend();
|
||
}
|
||
}
|
||
ShiftToSibling(siblings.PREV);
|
||
}
|
||
|
||
function
|
||
ShiftToNextSibling()
|
||
{
|
||
ShiftToSibling(siblings.NEXT);
|
||
}
|
||
|
||
function
|
||
InputIsFocused()
|
||
{
|
||
return document.activeElement == Search.QueryElement;
|
||
}
|
||
|
||
function
|
||
ShouldFireGridEvents()
|
||
{
|
||
return Nav.ViewType == view_type.GRID && !InputIsFocused() && !IsQuery();
|
||
}
|
||
|
||
function
|
||
ModifyControlKeybinding(Event)
|
||
{
|
||
// TODO(matt): Settle on the final sets of bindings
|
||
var Chron = Nav.SortChronological;
|
||
var Key = Event.key;
|
||
var IT = interaction_type.PUSH_BUTTON;
|
||
var ID = { Element: null, Button: null, }; // NOTE(matt): InteractionData
|
||
|
||
switch(Key)
|
||
{
|
||
case "?": if(!InputIsFocused()) { Nav.Controls.HelpDocumentation.classList.toggle("visible"); } break;
|
||
case "t": if(!InputIsFocused()) { EnqueueInteraction(interaction_type.SORT); } break;
|
||
case "y": if(!InputIsFocused()) { ToggleView(); } break;
|
||
case "m": if(!InputIsFocused()) { ToggleAnimations(); } break;
|
||
case "h": if(ShouldFireGridEvents()) { EnqueueInteraction(Chron ? interaction_type.SIBLING_SHIFT_PREV : interaction_type.SIBLING_SHIFT_NEXT); } break;
|
||
case "k": if(ShouldFireGridEvents()) { EnqueueInteraction(interaction_type.ASCEND); } break;
|
||
case "l": if(ShouldFireGridEvents()) { EnqueueInteraction(Chron ? interaction_type.SIBLING_SHIFT_NEXT : interaction_type.SIBLING_SHIFT_PREV); } break;
|
||
}
|
||
}
|
||
|
||
function
|
||
BindControlKeys()
|
||
{
|
||
document.addEventListener("keydown", ModifyControlKeybinding);
|
||
}
|
||
|
||
function
|
||
UnbindControlKeys()
|
||
{
|
||
document.removeEventListener("keydown", ModifyControlKeybinding);
|
||
}
|
||
|
||
function
|
||
RebindControlKeys()
|
||
{
|
||
UnbindControlKeys();
|
||
BindControlKeys();
|
||
}
|
||
|
||
function
|
||
KeyIsInGrid(GridSize, KeyPos)
|
||
{
|
||
return GridSize.X > KeyPos.X && GridSize.Y > KeyPos.Y;
|
||
}
|
||
|
||
function
|
||
ComputeNaturalKeyIndex(GridSize, KeyPos)
|
||
{
|
||
return KeyPos.Y * GridSize.X + KeyPos.X;
|
||
}
|
||
|
||
function
|
||
EnqueueGridInteraction(GridSize, KeyPos)
|
||
{
|
||
var Chron = Nav.SortChronological;
|
||
var LastButtonIndex = Nav.Buttons.length - 1;
|
||
var NaturalIndex = ComputeNaturalKeyIndex(GridSize, KeyPos);
|
||
var IT = interaction_type.PUSH_BUTTON;
|
||
var ID = { Element: null, Button: null, }; // NOTE(matt): InteractionData
|
||
ID.Element = Nav.Buttons[Chron ? NaturalIndex : LastButtonIndex - NaturalIndex].Element;
|
||
ID.Button = Nav.Buttons[Chron ? NaturalIndex : LastButtonIndex - NaturalIndex];
|
||
EnqueueInteraction(IT, ID);
|
||
}
|
||
|
||
function
|
||
Get2DPosFromIndex(Layout, Index)
|
||
{
|
||
var Result = {
|
||
X: null,
|
||
Y: null,
|
||
};
|
||
|
||
Result.X = Index % Layout.X;
|
||
Result.Y = (Index - Result.X) / Layout.Y;
|
||
|
||
return Result;
|
||
}
|
||
|
||
function
|
||
ModifyGridKeybinding(Event)
|
||
{
|
||
var Key = Event.key;
|
||
// TODO(matt): With this, we could probably easily add a setting for keyboard layout: e.g. Dvorak
|
||
var PhysicalKeys = [
|
||
"1", "2", "3", "4",
|
||
"q", "w", "e", "r",
|
||
"a", "s", "d", "f",
|
||
"z", "x", "c", "v"
|
||
];
|
||
|
||
var KeyLayout = { X: 4, Y: 4 };
|
||
for(var i = 0; i < PhysicalKeys.length; ++i)
|
||
{
|
||
if(Key == PhysicalKeys[i])
|
||
{
|
||
var KeyPos = Get2DPosFromIndex(KeyLayout, i);
|
||
if(KeyIsInGrid(Nav.GridSize, KeyPos) && ShouldFireGridEvents())
|
||
{
|
||
EnqueueGridInteraction(Nav.GridSize, KeyPos);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
function
|
||
BindGridKeys()
|
||
{
|
||
document.addEventListener("keydown", ModifyGridKeybinding);
|
||
}
|
||
|
||
function
|
||
UnbindGridKeys()
|
||
{
|
||
document.removeEventListener("keydown", ModifyGridKeybinding);
|
||
}
|
||
|
||
function
|
||
DoRotationStage(Now)
|
||
{
|
||
if(Nav.Transition.StageDurations.length)
|
||
{
|
||
if(Nav.Transition.StartTime === undefined)
|
||
{
|
||
Nav.Transition.StartTime = Now;
|
||
}
|
||
|
||
var Elapsed = Now - Nav.Transition.StartTime;
|
||
var Duration = Nav.Transition.StageDurations[0];
|
||
|
||
if(Duration === 0)
|
||
{
|
||
if(Nav.Transition.Transforms.ButtonsContainer.TargetStages.length)
|
||
{
|
||
MergeTransform(Nav.Transition.Transforms.ButtonsContainer.Current, Nav.Transition.Transforms.ButtonsContainer.TargetStages[0]);
|
||
ApplyTransform(Nav.ButtonsContainer, Nav.Transition.Transforms.ButtonsContainer.Current);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
var t = Clamp01(Elapsed / Duration);
|
||
if(LerpTransforms(Nav.Transition.Transforms.ButtonsContainer, t))
|
||
{
|
||
ApplyTransform(Nav.ButtonsContainer, Nav.Transition.Transforms.ButtonsContainer.Current);
|
||
}
|
||
}
|
||
|
||
for(var i = 0; i < Nav.ButtonsContainer.children.length; ++i)
|
||
{
|
||
var This = Nav.ButtonsContainer.children[i];
|
||
var ThisTexts = This.querySelectorAll(".cineraText");
|
||
for(var j = 0; j < ThisTexts.length; ++j)
|
||
{
|
||
ThisTexts[j].style.transform = "rotate3d(0, 0, 1, " + -Nav.Transition.Transforms.ButtonsContainer.Current.Rotation.Z + "deg)";
|
||
}
|
||
}
|
||
|
||
if(TransformsComplete(Nav.Transition.Transforms))
|
||
{
|
||
FinaliseTransforms(Nav.Transition.Transforms);
|
||
}
|
||
Nav.Transition.RequestedFrame = window.requestAnimationFrame(DoRotationStage);
|
||
}
|
||
else
|
||
{
|
||
Nav.Transition.RequestedFrame = undefined;
|
||
ResetButtonsContainerClone();
|
||
DequeueInteraction();
|
||
Nav.Controls.Header.style.overflow = null;
|
||
}
|
||
}
|
||
|
||
function
|
||
RotateButtons(Initialising)
|
||
{
|
||
// TODO(matt): Consider pushing this through DoTransition(), aware that it'd need to transform ".cineraText" children
|
||
CopyTransform(Nav.Transition.Transforms.ButtonsContainer.Initial, Nav.Transition.Transforms.ButtonsContainer.Current);
|
||
Nav.Transition.StartTime = undefined;
|
||
if(!Initialising && Nav.Transition.Enabled)
|
||
{
|
||
Nav.Transition.StageDurations.push(320);
|
||
}
|
||
else
|
||
{
|
||
Nav.Transition.StageDurations.push(0);
|
||
}
|
||
|
||
let Target = NullTarget();
|
||
if(Nav.SortChronological)
|
||
{
|
||
Target.Rotation.Z = 0;
|
||
}
|
||
else
|
||
{
|
||
Target.Rotation.Z = 180;
|
||
}
|
||
Nav.Transition.Transforms.ButtonsContainer.TargetStages.push(Target);
|
||
|
||
CopyTransform(Nav.Transition.Transforms.ButtonsContainer.Initial, Nav.Transition.Transforms.ButtonsContainer.Current);
|
||
Nav.Transition.RequestedFrame = window.requestAnimationFrame(DoRotationStage);
|
||
}
|
||
|
||
function
|
||
AddAnimationClass()
|
||
{
|
||
Nav.Nexus.classList.add("anim");
|
||
}
|
||
|
||
function
|
||
Sort(Initialising)
|
||
{
|
||
ResetButtonsContainerClone();
|
||
|
||
if(Initialising && Nav.Transition.Enabled) { Nav.Nexus.classList.remove("anim"); }
|
||
if(Nav.SortChronological)
|
||
{
|
||
Nav.Controls.Sort.textContent = "Sort: New to Old ⏷";
|
||
Nav.Nexus.classList.add("reversed");
|
||
for(var i = 0; i < Search.Projects.length; ++i)
|
||
{
|
||
if(Search.Projects[i].entriesContainer)
|
||
{
|
||
Search.Projects[i].entriesContainer.style.flexFlow = "column-reverse";
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Nav.Controls.Sort.textContent = "Sort: Old to New ⏶";
|
||
Nav.Nexus.classList.remove("reversed");
|
||
for(var i = 0; i < Search.Projects.length; ++i)
|
||
{
|
||
if(Search.Projects[i].entriesContainer)
|
||
{
|
||
Search.Projects[i].entriesContainer.style.flexFlow = "column";
|
||
}
|
||
}
|
||
}
|
||
if(Initialising && Nav.Transition.Enabled) { setTimeout(AddAnimationClass, 320); }
|
||
|
||
UnbindGridKeys(Nav.GridSize);
|
||
Nav.SortChronological = !Nav.SortChronological;
|
||
if(Nav.Controls.GridTraversal.PrevAscends)
|
||
{
|
||
if(Nav.SortChronological)
|
||
{
|
||
Nav.Controls.GridTraversal.Prev.children[0].textContent = "↰";
|
||
}
|
||
else
|
||
{
|
||
Nav.Controls.GridTraversal.Prev.children[0].textContent = "↲";
|
||
}
|
||
}
|
||
|
||
if(MaintainingState())
|
||
{
|
||
if(Nav.SortChronological) { ClearStateBit(state_bit.SORT_REVERSED); }
|
||
else { SetStateBit(state_bit.SORT_REVERSED); }
|
||
}
|
||
RotateButtons(Initialising);
|
||
BindGridKeys();
|
||
runSearch(true);
|
||
}
|
||
|
||
function
|
||
DequeueInteraction()
|
||
{
|
||
if(Nav.InteractionQueue.length)
|
||
{
|
||
var I = Nav.InteractionQueue.shift();
|
||
|
||
switch(I.Type)
|
||
{
|
||
case interaction_type.PUSH_BUTTON: { PushButton(I.Data.Element, I.Data.Button); } break;
|
||
case interaction_type.SIBLING_SHIFT_PREV: { ShiftToPrevSibling(); } break;
|
||
case interaction_type.SIBLING_SHIFT_NEXT: { ShiftToNextSibling(); } break;
|
||
case interaction_type.ASCEND: { Ascend(); } break;
|
||
case interaction_type.SORT: { Sort(); } break;
|
||
}
|
||
}
|
||
}
|
||
|
||
function
|
||
EnqueueInteraction(InteractionType, Data)
|
||
{
|
||
// TODO(matt): Maybe see about interpolating out of interrupted transitions?
|
||
//
|
||
// Interruptability:
|
||
// Reversible Pairs:
|
||
// SIBLING_SHIFT_PREV by SIBLING_SHIFT_NEXT
|
||
// SIBLING_SHIFT_NEXT (when SHIFT) by SIBLING_SHIFT_PREV
|
||
// SIBLING_SHIFT_NEXT (when ASCEND) by PUSH_BUTTON (of the corresponding button)
|
||
// PUSH_BUTTON by ASCEND
|
||
// ASCEND by PUSH_BUTTON (of the corresponding button)
|
||
// SORT by SORT
|
||
//
|
||
// Mixtures:
|
||
// SIBLING_SHIFT_PREV – SORT
|
||
//
|
||
|
||
var I = {
|
||
Type: InteractionType,
|
||
Data: Data,
|
||
};
|
||
|
||
Nav.InteractionQueue.push(I);
|
||
|
||
if(!Nav.Transition.RequestedFrame)
|
||
{
|
||
DequeueInteraction();
|
||
}
|
||
}
|
||
|
||
function
|
||
UseOrientation(Orientation)
|
||
{
|
||
Nav.GridContainer.classList.remove("Portrait", "Landscape", "Left", "Right");
|
||
switch(Orientation)
|
||
{
|
||
case orientations.PORTRAIT:
|
||
{
|
||
Nav.Controls.GridTraversal.Header.insertBefore(Nav.Controls.GridTraversal.Ascend, Nav.Controls.GridTraversal.Next);
|
||
Nav.GridContainer.classList.add("Portrait");
|
||
} break;
|
||
|
||
case orientations.LANDSCAPE_LEFT:
|
||
{
|
||
Nav.Controls.GridTraversal.Header.parentElement.insertBefore(Nav.Controls.GridTraversal.Ascend, Nav.Controls.GridTraversal.Header);
|
||
Nav.GridContainer.classList.add("Landscape", "Left");
|
||
} break;
|
||
|
||
case orientations.LANDSCAPE_RIGHT:
|
||
{
|
||
Nav.Controls.GridTraversal.Header.parentElement.insertBefore(Nav.Controls.GridTraversal.Ascend, Nav.Controls.GridTraversal.Header);
|
||
Nav.GridContainer.classList.add("Landscape", "Right");
|
||
} break;
|
||
}
|
||
}
|
||
|
||
function
|
||
InitNexus()
|
||
{
|
||
Nav.List.classList.add("hidden");
|
||
var ButtonsContainerPrototype = document.createElement("div");
|
||
ButtonsContainerPrototype.setAttribute("id", "cineraIndexGrid");
|
||
|
||
var ButtonsPrototype = document.createElement("div");
|
||
ButtonsPrototype.classList.add("cineraButtons");
|
||
|
||
var ButtonsClonePrototype = document.createElement("div");
|
||
ButtonsClonePrototype.classList.add("cineraButtons");
|
||
|
||
ButtonsContainerPrototype.appendChild(ButtonsClonePrototype);
|
||
ButtonsContainerPrototype.appendChild(ButtonsPrototype);
|
||
Nav.Transition.ButtonsTransitionContainerElement = Nav.GridContainer.appendChild(ButtonsContainerPrototype);
|
||
Nav.Grid = Nav.Transition.ButtonsTransitionContainerElement;
|
||
|
||
Nav.Transition.ButtonsContainerCloneElement = Nav.Transition.ButtonsTransitionContainerElement.querySelectorAll(".cineraButtons")[0];
|
||
Nav.ButtonsContainer = Nav.Transition.ButtonsTransitionContainerElement.querySelectorAll(".cineraButtons")[1];
|
||
Nav.GridColumnGap = parseInt(window.getComputedStyle(Nav.ButtonsContainer).gridColumnGap);
|
||
Nav.GridRowGap = parseInt(window.getComputedStyle(Nav.ButtonsContainer).gridRowGap);
|
||
|
||
// NOTE(matt): We ResetButtonsContainerClone() anyway, but without cycling this classList Safari seems to do transitions
|
||
// based on the wrong size grid
|
||
|
||
Nav.GridContainer.classList.add("hidden");
|
||
ResetButtonsContainerClone();
|
||
|
||
switch(Nav.ViewType)
|
||
{
|
||
case view_type.LIST: { Nav.List.classList.remove("hidden"); ScrollCondition = false; } break;
|
||
case view_type.GRID: { Nav.Grid.classList.remove("hidden"); ScrollCondition = true; } break;
|
||
}
|
||
|
||
Nav.Nexus.classList.add("anim");
|
||
|
||
if(CineraProps.IsMobile)
|
||
{
|
||
CineraProps.Orientation = GetRealOrientation(orientations.LANDSCAPE_LEFT, CineraProps.IsMobile);
|
||
UseOrientation(CineraProps.Orientation);
|
||
}
|
||
}
|
||
|
||
function
|
||
ComputePossibleCellsInDimension(AvailableSpaceInPixels, GridGap)
|
||
{
|
||
var Result = 0;
|
||
var SpaceToFill = AvailableSpaceInPixels;
|
||
if(SpaceToFill >= Nav.MinButtonDim)
|
||
{
|
||
SpaceToFill -= Nav.MinButtonDim;
|
||
++Result;
|
||
}
|
||
|
||
for(; SpaceToFill >= (Nav.MinButtonDim + GridGap); )
|
||
{
|
||
SpaceToFill -= (Nav.MinButtonDim + GridGap);
|
||
++Result;
|
||
}
|
||
return Result;
|
||
}
|
||
|
||
function
|
||
GridSizeMeetsMinimumSupported(GridSize)
|
||
{
|
||
return GridSize.X * GridSize.Y >= 2;
|
||
}
|
||
|
||
function
|
||
GridSizeIsSupported(GridSize)
|
||
{
|
||
return (GridSize.X >= Nav.GridMinCellsPerDimension && GridSize.X <= Nav.GridMaxCellsPerDimension) &&
|
||
(GridSize.Y >= Nav.GridMinCellsPerDimension && GridSize.Y <= Nav.GridMaxCellsPerDimension) &&
|
||
GridSizeMeetsMinimumSupported(GridSize);
|
||
}
|
||
|
||
function
|
||
ComputeOptimalGridSize()
|
||
{
|
||
var Result = {
|
||
X: null,
|
||
Y: null,
|
||
};
|
||
|
||
var WindowDim = GetWindowDim(CineraProps.IsMobile);
|
||
var DimReduction = {
|
||
X: 0,
|
||
Y: Nav.Controls.Header.offsetHeight,
|
||
};
|
||
|
||
Nav.Transition.ButtonsTransitionContainerElement.style = null;
|
||
Nav.ButtonsContainer.style = null;
|
||
Nav.Controls.GridTraversal.Container.style = null;
|
||
Nav.Controls.GridTraversal.Ascend.style = null;
|
||
Nav.Controls.GridTraversal.Prev.style = null;
|
||
Nav.Controls.GridTraversal.Next.style = null;
|
||
|
||
// TODO(matt): Maybe structure it such that the grid is always not hidden at this point?
|
||
var GridWasHidden = Nav.GridContainer.classList.contains("hidden");
|
||
if(GridWasHidden)
|
||
{
|
||
Nav.List.classList.add("hidden");
|
||
Nav.GridContainer.classList.remove("hidden");
|
||
}
|
||
if(CineraProps.IsMobile && (CineraProps.Orientation == orientations.LANDSCAPE_LEFT || CineraProps.Orientation == orientations.LANDSCAPE_RIGHT))
|
||
{
|
||
DimReduction.X += Nav.Controls.GridTraversal.Container.offsetWidth;
|
||
}
|
||
else
|
||
{
|
||
DimReduction.Y += Nav.Controls.GridTraversal.Container.offsetHeight;
|
||
}
|
||
|
||
var MaxWidth = MaxWidthOfElement(Nav.GridContainer, WindowDim) - DimReduction.X;
|
||
var MaxHeight = MaxHeightOfElement(Nav.GridContainer, WindowDim) - DimReduction.Y;
|
||
|
||
if(GridWasHidden)
|
||
{
|
||
Nav.GridContainer.classList.add("hidden");
|
||
Nav.List.classList.remove("hidden");
|
||
}
|
||
|
||
var BodyStyle = window.getComputedStyle(document.body);
|
||
if(Nav.Nexus.parentNode == document.body)
|
||
{
|
||
// TODO(matt): Robustify this
|
||
MaxWidth -= parseInt(BodyStyle.marginRight);
|
||
MaxHeight -= parseInt(BodyStyle.marginBottom);
|
||
}
|
||
|
||
Result.X = ComputePossibleCellsInDimension(MaxWidth, Nav.GridColumnGap);
|
||
Result.Y = ComputePossibleCellsInDimension(MaxHeight, Nav.GridRowGap);
|
||
|
||
if(GridSizeMeetsMinimumSupported(Result))
|
||
{
|
||
Result.X = Clamp(Nav.GridMinCellsPerDimension, Result.X, Nav.GridMaxCellsPerDimension);
|
||
Result.Y = Clamp(Nav.GridMinCellsPerDimension, Result.Y, Nav.GridMaxCellsPerDimension);
|
||
|
||
var ButtonDimBasedOnX = Math.floor((MaxWidth - Nav.GridColumnGap * (Result.X - 1)) / Result.X);
|
||
var ButtonDimBasedOnY = Math.floor((MaxHeight - Nav.GridRowGap * (Result.Y - 1)) / Result.Y);
|
||
|
||
Nav.ButtonDim = Math.min(ButtonDimBasedOnX, ButtonDimBasedOnY);
|
||
Nav.ButtonDim -= Nav.ButtonDim % 2; // NOTE(matt): Even-length helps CSS keep the head & tail's correct size when rotated 180 degrees
|
||
|
||
var GridTemplateColumnsStyle = "repeat(" + Result.X + ", minmax(" + Nav.ButtonDim + "px, " + Nav.ButtonDim + "px))";
|
||
Nav.ButtonsContainer.style.gridTemplateColumns = GridTemplateColumnsStyle;
|
||
|
||
var GridTemplateRowsStyle = "repeat(" + Result.Y + ", " + Nav.ButtonDim + "px)";
|
||
Nav.ButtonsContainer.style.gridTemplateRows = GridTemplateRowsStyle;
|
||
|
||
Nav.GridDim.X = Nav.ButtonDim * Result.X + Nav.GridColumnGap * (Result.X - 1);
|
||
Nav.GridDim.Y = Nav.ButtonDim * Result.Y + Nav.GridRowGap * (Result.Y - 1);
|
||
|
||
SetDim(Nav.Transition.ButtonsTransitionContainerElement, Nav.GridDim.X + "px", Nav.GridDim.Y + "px");
|
||
|
||
Nav.Controls.GridTraversal.Container.style.maxWidth = Nav.GridDim.X + "px";
|
||
Nav.Controls.GridTraversal.Container.style.maxHeight = Nav.GridDim.Y + "px";
|
||
|
||
var TraversalButtonCount = 3;
|
||
if(Nav.Controls.GridTraversal.Container.scrollWidth > Nav.Controls.GridTraversal.Container.clientWidth)
|
||
{
|
||
var TraversalButtonDim = Nav.Controls.GridTraversal.Container.clientWidth / TraversalButtonCount;
|
||
SetDim(Nav.Controls.GridTraversal.Ascend, TraversalButtonDim + "px", TraversalButtonDim + "px");
|
||
SetDim(Nav.Controls.GridTraversal.Prev, TraversalButtonDim + "px", TraversalButtonDim + "px");
|
||
SetDim(Nav.Controls.GridTraversal.Next, TraversalButtonDim + "px", TraversalButtonDim + "px");
|
||
}
|
||
if(Nav.Controls.GridTraversal.Container.scrollHeight > Nav.Controls.GridTraversal.Container.clientHeight)
|
||
{
|
||
var TraversalButtonDim = Nav.Controls.GridTraversal.Container.clientHeight / TraversalButtonCount;
|
||
SetDim(Nav.Controls.GridTraversal.Ascend, TraversalButtonDim + "px", TraversalButtonDim + "px");
|
||
SetDim(Nav.Controls.GridTraversal.Prev, TraversalButtonDim + "px", TraversalButtonDim + "px");
|
||
SetDim(Nav.Controls.GridTraversal.Next, TraversalButtonDim + "px", TraversalButtonDim + "px");
|
||
}
|
||
|
||
}
|
||
|
||
ResetButtonsContainerClone(); // NOTE(matt): This reapplies the sorting Z-rotation
|
||
return Result;
|
||
}
|
||
|
||
function
|
||
GetScrollToElement(NodeList, UpperIndex)
|
||
{
|
||
var Result = undefined;
|
||
if(UpperIndex === null)
|
||
{
|
||
if(NodeList.length)
|
||
{
|
||
Result = NodeList[0].closest(".cineraIndexProject");
|
||
}
|
||
else
|
||
{
|
||
Result = NodeList.closest(".cineraIndexProject");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Result = NodeList[UpperIndex];
|
||
}
|
||
return Result;
|
||
}
|
||
|
||
function
|
||
EmptyTraversalStack()
|
||
{
|
||
while(Nav.TraversalStack.length > 1)
|
||
{
|
||
Nav.TraversalStack.pop();
|
||
}
|
||
}
|
||
|
||
function
|
||
GetContainingProjectOfLevel(Level)
|
||
{
|
||
var Result = null;
|
||
if(Level.Projects !== null)
|
||
{
|
||
if(Level.Projects.length)
|
||
{
|
||
Result = Level.Projects[0].parentNode;
|
||
}
|
||
else
|
||
{
|
||
Result = Level.Projects.parentNode;
|
||
}
|
||
}
|
||
else if(Level.Entries !== null)
|
||
{
|
||
if(Level.Entries.length)
|
||
{
|
||
Result = Level.Entries[0].closest(".cineraIndexProject");
|
||
}
|
||
else
|
||
{
|
||
Result = Level.Entries.closest(".cineraIndexProject");
|
||
}
|
||
}
|
||
return Result;
|
||
}
|
||
|
||
function
|
||
GetContainingProject(ProjectElement)
|
||
{
|
||
var Result = null;
|
||
if(ProjectElement.parentNode.classList.contains("cineraIndexProject"))
|
||
{
|
||
Result = ProjectElement.parentNode;
|
||
}
|
||
return Result;
|
||
}
|
||
|
||
function
|
||
PushProjectOntoStack(Stack, ContainingProject)
|
||
{
|
||
var Project = {
|
||
Element: ContainingProject,
|
||
Projects: ContainingProject.querySelectorAll(":scope > .cineraIndexProject"),
|
||
Entries: ContainingProject.querySelectorAll(":scope > .cineraIndexEntries > div"),
|
||
Index: null,
|
||
};
|
||
|
||
if(Project.Projects.length == 0) { Project.Projects = null; }
|
||
if(Project.Entries.length == 0) { Project.Entries = null; }
|
||
|
||
var Siblings = ContainingProject.parentNode.querySelectorAll(":scope > .cineraIndexProject");
|
||
Project.Index = GetIndexOfElement(Siblings, ContainingProject);
|
||
Stack.push(Project);
|
||
}
|
||
|
||
function
|
||
BuildProjectsStack(TargetLevel)
|
||
{
|
||
let ProjectsStack = [];
|
||
var ContainingProject = GetContainingProjectOfLevel(TargetLevel);
|
||
|
||
PushProjectOntoStack(ProjectsStack, ContainingProject);
|
||
|
||
ContainingProject = GetContainingProject(ProjectsStack[ProjectsStack.length - 1].Element);
|
||
while(ContainingProject)
|
||
{
|
||
PushProjectOntoStack(ProjectsStack, ContainingProject);
|
||
ContainingProject = GetContainingProject(ProjectsStack[ProjectsStack.length - 1].Element);
|
||
}
|
||
|
||
return ProjectsStack;
|
||
}
|
||
|
||
function
|
||
EmptyTraversalStackIntoProjectsStack()
|
||
{
|
||
let ProjectsStack = [];
|
||
while(Nav.TraversalStack.length > 1)
|
||
{
|
||
let ThisLevel = Nav.TraversalStack[Nav.TraversalStack.length - 1];
|
||
PushLevelProjectUniquely(ProjectsStack, ThisLevel);
|
||
Nav.TraversalStack.pop();
|
||
}
|
||
return ProjectsStack;
|
||
}
|
||
|
||
function
|
||
DeriveTraversalStack(ProjectsStack, TargetLevel)
|
||
{
|
||
if(ProjectsStack && TargetLevel)
|
||
{
|
||
while(ProjectsStack.length > 0)
|
||
{
|
||
var ThisProject = ProjectsStack[ProjectsStack.length - 1];
|
||
|
||
var ButtonInfo = { HeadIndex: null, TailIndex: null, ItemCount: null, };
|
||
let PopulationData = {
|
||
Distribution: null,
|
||
ThisLevel: null,
|
||
Prev: null,
|
||
DoingEntries: false,
|
||
};
|
||
PopulationData.ThisLevel = Nav.TraversalStack[Nav.TraversalStack.length - 1];
|
||
PopulationData.Distribution = ComputeItemDistribution(PopulationData.ThisLevel);
|
||
|
||
for(var ButtonIndex = 0; ButtonIndex < Nav.Buttons.length; ++ButtonIndex)
|
||
{
|
||
let Button = {
|
||
Projects: null,
|
||
Entries: null,
|
||
HeadIndex: null,
|
||
TailIndex: null,
|
||
};
|
||
|
||
if(PopulationData.Distribution.ProjectsToPlace > 0 || PopulationData.Distribution.EntriesToPlace > 0)
|
||
{
|
||
ButtonInfo = PopulateButton(PopulationData, Button, ButtonIndex);
|
||
if(ButtonInfo.HeadIndex <= ThisProject.Index && ButtonInfo.TailIndex >= ThisProject.Index)
|
||
{
|
||
var FoundProject = PseudoPushButton(Button);
|
||
if(FoundProject)
|
||
{
|
||
ProjectsStack.pop();
|
||
}
|
||
break;
|
||
}
|
||
PopulationData.Prev = ButtonInfo;
|
||
}
|
||
}
|
||
}
|
||
|
||
if(TargetLevel.HeadIndex !== null && TargetLevel.TailIndex !== null)
|
||
{
|
||
var Descended = false;
|
||
while(true)
|
||
{
|
||
let PopulationData = {
|
||
Distribution: null,
|
||
ThisLevel: null,
|
||
Prev: null,
|
||
DoingEntries: false,
|
||
};
|
||
PopulationData.ThisLevel = Nav.TraversalStack[Nav.TraversalStack.length - 1];
|
||
PopulationData.Distribution = ComputeItemDistribution(PopulationData.ThisLevel);
|
||
|
||
for(var ButtonIndex = 0; ButtonIndex < Nav.Buttons.length; ++ButtonIndex)
|
||
{
|
||
let Button = {
|
||
Projects: null,
|
||
Entries: null,
|
||
HeadIndex: null,
|
||
TailIndex: null,
|
||
};
|
||
|
||
if(PopulationData.Distribution.ProjectsToPlace > 0 || PopulationData.Distribution.EntriesToPlace > 0)
|
||
{
|
||
ButtonInfo = PopulateButton(PopulationData, Button, ButtonIndex);
|
||
if(ButtonInfo.HeadIndex <= TargetLevel.HeadIndex && ButtonInfo.TailIndex >= TargetLevel.TailIndex)
|
||
{
|
||
Descended = true;
|
||
PseudoPushButton(Button);
|
||
break;
|
||
}
|
||
PopulationData.Prev = ButtonInfo;
|
||
}
|
||
}
|
||
if(!Descended)
|
||
{
|
||
break;
|
||
}
|
||
else
|
||
{
|
||
Descended = false;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
function
|
||
GetGenerationOf(Element)
|
||
{
|
||
var Result = 0;
|
||
if(Element.classList.contains("cineraIndexProject"))
|
||
{
|
||
++Result;
|
||
var ContainingProject = GetContainingProject(Element);
|
||
while(ContainingProject)
|
||
{
|
||
++Result;
|
||
ContainingProject = GetContainingProject(ContainingProject);
|
||
}
|
||
}
|
||
return Result;
|
||
}
|
||
|
||
function
|
||
ComputeTargetLevelForViewport()
|
||
{
|
||
var Result = {
|
||
Projects: null,
|
||
Entries: null,
|
||
HeadIndex: null,
|
||
TailIndex: null,
|
||
};
|
||
|
||
var ViewportTop = window.scrollY;
|
||
var ViewportBottom = ViewportTop + window.innerHeight;
|
||
|
||
var ControlsHeight = Nav.Controls.Header.offsetHeight;
|
||
ViewportTop += ControlsHeight;
|
||
|
||
var Elements = [];
|
||
for(var ProjectIndex = 0; ProjectIndex < Search.Projects.length; ++ProjectIndex)
|
||
{
|
||
var ThisProject = Search.Projects[ProjectIndex];
|
||
Elements.push(ThisProject.projectTitleElement);
|
||
if(ThisProject.entriesContainer)
|
||
{
|
||
var Entries = ThisProject.entriesContainer.querySelectorAll(":scope > div");
|
||
if(Nav.SortChronological)
|
||
{
|
||
for(var EntryIndex = 0; EntryIndex < Entries.length; ++EntryIndex)
|
||
{
|
||
Elements.push(Entries[EntryIndex]);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
for(var EntryIndex = Entries.length - 1; EntryIndex >= 0; --EntryIndex)
|
||
{
|
||
Elements.push(Entries[EntryIndex]);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
var Upper = {
|
||
Element: null,
|
||
Parent: null,
|
||
Type: null,
|
||
};
|
||
var Lower = {
|
||
Element: null,
|
||
Parent: null,
|
||
Type: null,
|
||
};
|
||
|
||
for(var i = 0; i < Elements.length; ++i)
|
||
{
|
||
var ElementTop = getElementYOffsetFromPage(Elements[i]);
|
||
if(!Upper.Element)
|
||
{
|
||
if(ElementTop >= ViewportTop)
|
||
{
|
||
Upper.Element = Elements[i];
|
||
Lower.Element = Upper.Element;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
var ElementBottom = ElementTop + Elements[i].scrollHeight;
|
||
if(ElementBottom <= ViewportBottom)
|
||
{
|
||
Lower.Element = Elements[i];
|
||
}
|
||
else
|
||
{
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
Upper.Type = Upper.Element.classList.contains("cineraProjectTitle") ? item_type.PROJECT : item_type.ENTRY;
|
||
Lower.Type = Lower.Element.classList.contains("cineraProjectTitle") ? item_type.PROJECT : item_type.ENTRY;
|
||
|
||
Upper.Parent = Upper.Element.closest(".cineraIndexProject");
|
||
Lower.Parent = Lower.Element.closest(".cineraIndexProject");
|
||
|
||
if(Upper.Parent == Lower.Parent)
|
||
{
|
||
if(Upper.Type == Lower.Type)
|
||
{
|
||
switch(Upper.Type)
|
||
{
|
||
case item_type.PROJECT:
|
||
{
|
||
Result.Projects = Upper.Parent.querySelectorAll(":scope > .cineraIndexProject");
|
||
Result.HeadIndex = GetIndexOfElement(Result.Projects, Upper.Element);
|
||
Result.TailIndex = GetIndexOfElement(Result.Projects, Lower.Element);
|
||
} break;
|
||
case item_type.ENTRY:
|
||
{
|
||
Result.Entries = Upper.Parent.querySelectorAll(":scope > .cineraIndexEntries > div");
|
||
Result.HeadIndex = GetIndexOfElement(Result.Entries, Upper.Element);
|
||
Result.TailIndex = GetIndexOfElement(Result.Entries, Lower.Element);
|
||
} break;
|
||
}
|
||
if(!Nav.SortChronological)
|
||
{
|
||
var Temp = Result.HeadIndex;
|
||
Result.HeadIndex = Result.TailIndex;
|
||
Result.TailIndex = Temp;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Result.Projects = Upper.Parent.querySelectorAll(":scope > .cineraIndexProject");
|
||
if(Result.Projects.length == 0) { Result.Projects = null; }
|
||
Result.Entries = Upper.Parent.querySelectorAll(":scope > .cineraIndexEntries > div");
|
||
if(Result.Entries.length == 0) { Result.Entries = null; }
|
||
}
|
||
}
|
||
else
|
||
{
|
||
var UpperGeneration = GetGenerationOf(Upper.Parent);
|
||
var LowerGeneration = GetGenerationOf(Lower.Parent);
|
||
while(UpperGeneration > LowerGeneration)
|
||
{
|
||
Upper.Parent = GetContainingProject(Upper.Parent);
|
||
--UpperGeneration;
|
||
}
|
||
while(LowerGeneration > UpperGeneration)
|
||
{
|
||
Lower.Parent = GetContainingProject(Lower.Parent);
|
||
--LowerGeneration;
|
||
}
|
||
|
||
if(UpperGeneration == 0 && LowerGeneration == 0)
|
||
{
|
||
Upper.Parent = null;
|
||
Lower.Parent = null;
|
||
}
|
||
|
||
while(Upper.Parent && Lower.Parent && Upper.Parent != Lower.Parent)
|
||
{
|
||
Upper.Parent = GetContainingProject(Upper.Parent);
|
||
Lower.Parent = GetContainingProject(Lower.Parent);
|
||
}
|
||
|
||
if(Upper.Parent != null)
|
||
{
|
||
Result.Projects = Upper.Parent.querySelectorAll(":scope > .cineraIndexProject");
|
||
if(Result.Projects.length == 0) { Result.Projects = null; }
|
||
Result.Entries = Upper.Parent.querySelectorAll(":scope > .cineraIndexEntries > div");
|
||
if(Result.Entries.length == 0) { Result.Entries = null; }
|
||
}
|
||
}
|
||
|
||
while(Elements.length > 0)
|
||
{
|
||
Elements.pop();
|
||
}
|
||
|
||
return Result;
|
||
}
|
||
|
||
function
|
||
ScrollToWithOffset(Element, Offset)
|
||
{
|
||
var ScrollTop = getElementYOffsetFromPage(Element);
|
||
ScrollTop -= Offset;
|
||
window.scrollTo(0, ScrollTop);
|
||
}
|
||
|
||
function
|
||
PickGridView()
|
||
{
|
||
if(GridSizeIsSupported(Nav.GridSize))
|
||
{
|
||
Nav.Controls.View.textContent = "View: 𝑛-ary Grid";
|
||
Nav.ViewType = view_type.GRID;
|
||
if(MaintainingState())
|
||
{
|
||
ClearStateBit(state_bit.VIEW_LIST);
|
||
SetStateBit(state_bit.VIEW_GRID);
|
||
}
|
||
|
||
if(!IsQuery())
|
||
{
|
||
var TargetLevel = ComputeTargetLevelForViewport();
|
||
EmptyTraversalStack();
|
||
if(TargetLevel.Projects || TargetLevel.Entries)
|
||
{
|
||
var ProjectsStack = BuildProjectsStack(TargetLevel);
|
||
DeriveTraversalStack(ProjectsStack, TargetLevel);
|
||
}
|
||
Nav.List.classList.add("hidden");
|
||
Nav.GridContainer.classList.remove("hidden");
|
||
|
||
UpdateButtons();
|
||
|
||
ScrollToWithOffset(Nav.Controls.GridTraversal.Header, Nav.Controls.Header.offsetHeight);
|
||
}
|
||
ScrollCondition = true;
|
||
}
|
||
else
|
||
{
|
||
// TODO(matt): Inform user that grid view is unavailable
|
||
}
|
||
}
|
||
|
||
function
|
||
PickListView()
|
||
{
|
||
Nav.Controls.View.textContent = "View: List";
|
||
Nav.ViewType = view_type.LIST;
|
||
if(MaintainingState())
|
||
{
|
||
ClearStateBit(state_bit.VIEW_GRID);
|
||
SetStateBit(state_bit.VIEW_LIST);
|
||
}
|
||
|
||
if(!IsQuery())
|
||
{
|
||
Nav.List.classList.remove("hidden");
|
||
Nav.GridContainer.classList.add("hidden");
|
||
var LevelBundle = GetTraversalLevelBundle();
|
||
if(LevelBundle.Generation > 1)
|
||
{
|
||
var Element;
|
||
if(LevelBundle.This.Entries !== null)
|
||
{
|
||
Element = GetScrollToElement(LevelBundle.This.Entries, Nav.SortChronological ? LevelBundle.This.HeadIndex : LevelBundle.This.TailIndex);
|
||
}
|
||
else if(LevelBundle.This.Projects !== null)
|
||
{
|
||
Element = GetScrollToElement(LevelBundle.This.Projects, Nav.SortChronological ? LevelBundle.This.HeadIndex : LevelBundle.This.TailIndex);
|
||
}
|
||
ScrollToWithOffset(Element, Nav.Controls.Header.offsetHeight);
|
||
}
|
||
}
|
||
ScrollCondition = false;
|
||
}
|
||
|
||
function
|
||
ToggleView()
|
||
{
|
||
// NOTE(matt): While we only have two views, a toggle will suffice.
|
||
clearTimeout(ScrollerFunction);
|
||
ScrollTicking = false;
|
||
if(Nav.ViewType == view_type.GRID)
|
||
{
|
||
PickListView();
|
||
}
|
||
else
|
||
{
|
||
PickGridView();
|
||
}
|
||
}
|
||
|
||
function
|
||
ToggleAnimations()
|
||
{
|
||
Nav.Transition.Enabled = !Nav.Transition.Enabled;
|
||
if(Nav.Transition.Enabled)
|
||
{
|
||
Nav.Controls.Anim.textContent = "Animations: ✔";
|
||
Nav.Nexus.classList.add("anim");
|
||
ClearStateBit(state_bit.DISABLE_ANIMATIONS);
|
||
}
|
||
else
|
||
{
|
||
Nav.Controls.Anim.textContent = "Animations: ✘";
|
||
Nav.Nexus.classList.remove("anim");
|
||
SetStateBit(state_bit.DISABLE_ANIMATIONS);
|
||
}
|
||
}
|
||
|
||
function
|
||
ToggleSave()
|
||
{
|
||
if(MaintainingState())
|
||
{
|
||
Nav.Controls.Save.textContent = "Save Settings: ✘";
|
||
Nav.State = 0;
|
||
Nav.State |= state_bit.NO_SAVE;
|
||
SaveState();
|
||
}
|
||
else
|
||
{
|
||
Nav.Controls.Save.textContent = "Save Settings: ✔";
|
||
Nav.State ^= state_bit.NO_SAVE;
|
||
if(!Nav.Transition.Enabled) { SetStateBit(state_bit.DISABLE_ANIMATIONS); }
|
||
if(!Nav.SortChronological) { SetStateBit(state_bit.SORT_REVERSED); }
|
||
|
||
if(Nav.ViewType == view_type.LIST) { SetStateBit(state_bit.VIEW_LIST); }
|
||
else if(Nav.ViewType == view_type.GRID) { SetStateBit(state_bit.VIEW_GRID); }
|
||
}
|
||
}
|
||
|
||
function
|
||
BindMenuItem(Item)
|
||
{
|
||
// TODO(matt): Enable this to bind the "click" event, making it take a function and parameters
|
||
Item.addEventListener("mouseover", function(ev) { this.classList.add("focused"); });
|
||
Item.addEventListener("mouseout", function(ev) { this.classList.remove("focused"); });
|
||
}
|
||
|
||
function
|
||
BindControls()
|
||
{
|
||
var SettingsMenu = Nav.Controls.Header.querySelector(".cineraMenu.ViewSettings");
|
||
var SettingsMenuContainer = SettingsMenu.querySelector(".cineraMenuContainer");
|
||
|
||
SettingsMenu.addEventListener("mouseenter", function(ev) {
|
||
SettingsMenuContainer.classList.add("visible");
|
||
});
|
||
|
||
SettingsMenu.addEventListener("mouseleave", function(ev) {
|
||
SettingsMenuContainer.classList.remove("visible");
|
||
});
|
||
|
||
SettingsMenu.addEventListener("click", function(ev) {
|
||
SettingsMenuContainer.classList.toggle("visible");
|
||
});
|
||
|
||
BindMenuItem(Nav.Controls.Sort);
|
||
Nav.Controls.Sort.addEventListener("click", function(ev) { ev.stopPropagation(); EnqueueInteraction(interaction_type.SORT); });
|
||
BindMenuItem(Nav.Controls.View);
|
||
Nav.Controls.View.addEventListener("click", function(ev) { ev.stopPropagation(); ToggleView(); });
|
||
BindMenuItem(Nav.Controls.Anim);
|
||
Nav.Controls.Anim.addEventListener("click", function(ev) { ev.stopPropagation(); ToggleAnimations(); });
|
||
BindMenuItem(Nav.Controls.Save);
|
||
Nav.Controls.Save.addEventListener("click", function(ev) { ev.stopPropagation(); ToggleSave(); });
|
||
|
||
var Filter = Nav.Controls.Header.querySelector(".cineraMenu.IndexFilter");
|
||
if(Filter)
|
||
{
|
||
var FilterContainer = Filter.querySelector(".cineraMenuContainer");
|
||
// TODO(matt): Once we have multiple menus, use a menuState on this page
|
||
// menuState.push(Filter);
|
||
|
||
Filter.addEventListener("mouseenter", function(ev) {
|
||
FilterContainer.classList.add("visible");
|
||
});
|
||
|
||
Filter.addEventListener("mouseleave", function(ev) {
|
||
FilterContainer.classList.remove("visible");
|
||
});
|
||
|
||
Filter.addEventListener("click", function(ev) {
|
||
FilterContainer.classList.toggle("visible");
|
||
});
|
||
|
||
var IndexFilterProjects = Filter.querySelectorAll(".cineraFilterProject");
|
||
for(var i = 0; i < IndexFilterProjects.length; ++i)
|
||
{
|
||
IndexFilterProjects[i].addEventListener("mouseover", function(ev) {
|
||
ev.stopPropagation();
|
||
this.classList.add("focused");
|
||
focusSprite(this);
|
||
});
|
||
IndexFilterProjects[i].addEventListener("mouseout", function(ev) {
|
||
ev.stopPropagation();
|
||
this.classList.remove("focused");
|
||
unfocusSprite(this);
|
||
});
|
||
IndexFilterProjects[i].addEventListener("click", function(ev) {
|
||
ev.stopPropagation();
|
||
toggleEntriesOfProjectAndChildren(this);
|
||
});
|
||
}
|
||
}
|
||
|
||
Search.QueryElement.addEventListener("input", function(ev) {
|
||
history.replaceState(null, null, "#" + encodeURIComponent(Search.QueryElement.value));
|
||
runSearch();
|
||
});
|
||
|
||
Nav.Controls.GridTraversal.Prev.addEventListener("click", function() { EnqueueInteraction(interaction_type.SIBLING_SHIFT_PREV) });
|
||
Nav.Controls.GridTraversal.Ascend.addEventListener("click", function() { EnqueueInteraction(interaction_type.ASCEND) });
|
||
Nav.Controls.GridTraversal.Next.addEventListener("click", function() { EnqueueInteraction(interaction_type.SIBLING_SHIFT_NEXT) });
|
||
|
||
BindHelp(Nav.Controls.Help, Nav.Controls.HelpDocumentation);
|
||
}
|
||
|
||
function
|
||
InitButtons()
|
||
{
|
||
if(GridSizeIsSupported(Nav.GridSize))
|
||
{
|
||
var ButtonPrototype = document.createElement("div");
|
||
ButtonPrototype.classList.add("cineraButton", "subdivision");
|
||
for(var i = 0; i < Nav.GridSize.X * Nav.GridSize.Y; ++i)
|
||
{
|
||
Nav.ButtonsContainer.appendChild(ButtonPrototype.cloneNode());
|
||
}
|
||
|
||
var Buttons = Nav.ButtonsContainer.querySelectorAll(".cineraButton.subdivision");
|
||
for(let j = 0; j < Buttons.length; ++j)
|
||
{
|
||
let Button = {
|
||
Element: Buttons[j],
|
||
Projects: [],
|
||
Entries: [],
|
||
HeadIndex: null,
|
||
TailIndex: null,
|
||
};
|
||
|
||
Buttons[j].addEventListener("click", function() {
|
||
let InteractionData = {
|
||
Element: this,
|
||
Button: Button,
|
||
};
|
||
EnqueueInteraction(interaction_type.PUSH_BUTTON, InteractionData);
|
||
});
|
||
|
||
Nav.Buttons.push(Button);
|
||
}
|
||
}
|
||
}
|
||
|
||
function
|
||
ReinitButtons()
|
||
{
|
||
for(; Nav.Buttons.length > 0;)
|
||
{
|
||
Nav.Buttons[0].Element.remove();
|
||
Nav.Buttons.shift();
|
||
}
|
||
InitButtons();
|
||
}
|
||
|
||
function
|
||
PushLevelProjectUniquely(Stack, Level)
|
||
{
|
||
if(Level.HeadIndex == null && Level.TailIndex == null)
|
||
{
|
||
var Found = false;
|
||
var ContainingProject = GetContainingProjectOfLevel(Level);
|
||
|
||
for(var i = 0; i < Stack.length; ++i)
|
||
{
|
||
if(ContainingProject === Stack[i].Element)
|
||
{
|
||
Found = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if(!Found)
|
||
{
|
||
PushProjectOntoStack(Stack, ContainingProject);
|
||
}
|
||
}
|
||
}
|
||
|
||
function
|
||
ButtonContainsLevel(Button, Level)
|
||
{
|
||
var Result = false;
|
||
if(NodesMatch(Button.Projects, Level.Projects) &&
|
||
NodesMatch(Button.Entries, Level.Entries) &&
|
||
Button.HeadIndex <= Level.HeadIndex &&
|
||
Button.TailIndex >= Level.TailIndex)
|
||
{
|
||
Result = true;
|
||
}
|
||
return Result;
|
||
}
|
||
|
||
function
|
||
PopulateButton(PopulationData, Button, ButtonIndex)
|
||
{
|
||
var Result;
|
||
if(PopulationData.Distribution.ProjectsToPlace > 0)
|
||
{
|
||
Button.Projects = PopulationData.ThisLevel.Projects;
|
||
if(PopulationData.Distribution.ProjectsToPlace == 1 || PopulationData.Distribution.ProjectsToPlace == PopulationData.Distribution.ButtonsForProjects - ButtonIndex)
|
||
{
|
||
PopulationData.Distribution.FullButtonProjectCount = 1;
|
||
}
|
||
|
||
Result = SetButtonInfo(Button, PopulationData.Prev, PopulationData.ThisLevel, PopulationData.Distribution);
|
||
if(Result.ItemCount == 1)
|
||
{
|
||
Button.Projects = PopulationData.ThisLevel.Projects[Result.HeadIndex];
|
||
}
|
||
PopulationData.Distribution.ProjectsToPlace -= Result.ItemCount;
|
||
}
|
||
else
|
||
{
|
||
Button.Entries = PopulationData.ThisLevel.Entries;
|
||
if(!PopulationData.DoingEntries)
|
||
{
|
||
PopulationData.Prev = null;
|
||
PopulationData.DoingEntries = true;
|
||
}
|
||
|
||
if(PopulationData.Distribution.EntriesToPlace == 1 || PopulationData.Distribution.EntriesToPlace == PopulationData.Distribution.ButtonsForEntries - ButtonIndex)
|
||
{
|
||
PopulationData.Distribution.FullButtonEntryCount = 1;
|
||
}
|
||
|
||
Result = SetButtonInfo(Button, PopulationData.Prev, PopulationData.ThisLevel, PopulationData.Distribution);
|
||
if(Result.ItemCount == 1)
|
||
{
|
||
Button.Entries = Button.Entries[Result.HeadIndex];
|
||
}
|
||
PopulationData.Distribution.EntriesToPlace -= Result.ItemCount;
|
||
}
|
||
return Result;
|
||
}
|
||
|
||
function
|
||
PseudoPushButton(Button)
|
||
{
|
||
var ButtonIsProjectOrEntry = false;
|
||
|
||
var Level = {
|
||
Projects: Button.Projects,
|
||
Entries: Button.Entries,
|
||
HeadIndex: Button.HeadIndex,
|
||
TailIndex: Button.TailIndex,
|
||
};
|
||
|
||
if(Level.Projects !== null || Level.Entries !== null)
|
||
{
|
||
if(Level.Projects !== null)
|
||
{
|
||
if(Level.Projects.length === undefined)
|
||
{
|
||
var Entries = Level.Projects.querySelectorAll(":scope > .cineraIndexEntries > div");
|
||
var Projects = Level.Projects.querySelectorAll(":scope > .cineraIndexProject");
|
||
Level.Entries = Entries.length ? Entries : null;
|
||
Level.Projects = Projects.length ? Projects : null;
|
||
ButtonIsProjectOrEntry = true;
|
||
}
|
||
Nav.TraversalStack.push(Level);
|
||
}
|
||
else
|
||
{
|
||
if(Level.Entries.length === undefined)
|
||
{
|
||
ButtonIsProjectOrEntry = true;
|
||
}
|
||
else
|
||
{
|
||
Nav.TraversalStack.push(Level);
|
||
}
|
||
}
|
||
}
|
||
|
||
return ButtonIsProjectOrEntry;
|
||
}
|
||
|
||
function
|
||
ResizeFunction()
|
||
{
|
||
CineraProps.Orientation = GetRealOrientation(orientations.LANDSCAPE_LEFT, CineraProps.IsMobile);
|
||
if(CineraProps.IsMobile)
|
||
{
|
||
UseOrientation(CineraProps.Orientation);
|
||
}
|
||
var NewGridSize = ComputeOptimalGridSize();
|
||
if(Nav.GridSize !== NewGridSize)
|
||
{
|
||
UnbindGridKeys();
|
||
Nav.GridSize = NewGridSize;
|
||
ReinitButtons();
|
||
BindGridKeys();
|
||
SetHelpKeyAvailability(Nav.GridSize)
|
||
if(GridSizeIsSupported(Nav.GridSize))
|
||
{
|
||
var TargetLevel = Nav.TraversalStack[Nav.TraversalStack.length - 1];
|
||
var ProjectsStack = EmptyTraversalStackIntoProjectsStack();
|
||
DeriveTraversalStack(ProjectsStack, TargetLevel);
|
||
}
|
||
else if(Nav.ViewType == view_type.GRID)
|
||
{
|
||
PickListView();
|
||
// TODO(matt): Inform user that we've switched to the list view
|
||
}
|
||
ScrollToWithOffset(Nav.Nexus, 0);
|
||
}
|
||
UpdateButtons();
|
||
}
|
||
|
||
function
|
||
InitResizeEventListener()
|
||
{
|
||
window.addEventListener("resize", function() {
|
||
if(CineraProps.IsMobile)
|
||
{
|
||
window.setTimeout(ResizeFunction, 512);
|
||
}
|
||
else
|
||
{
|
||
ResizeFunction();
|
||
}
|
||
});
|
||
}
|
||
|
||
function
|
||
InitOrientationChangeListener()
|
||
{
|
||
window.onorientationchange = function()
|
||
{
|
||
if(CineraProps.IsMobile)
|
||
{
|
||
window.setTimeout(ResizeFunction, 512);
|
||
}
|
||
else
|
||
{
|
||
ResizeFunction();
|
||
}
|
||
};
|
||
}
|
||
//
|
||
// Presenting / Navigating (Laying out and traversing the grid, and sorting)
|