Annotation-System/cinera/cinera_search_pre.js

3884 lines
133 KiB
JavaScript
Raw Normal View History

// 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)