cinera.web: Fix issues (on mobile) with new layout

Both pages:
    Derive reliable window dimensions ourselves
    Set the orientation from the window dimensions
    Use "box-sizing: content-box" on the help keys, to fix sizing

Search page:
    Support irregular (i.e. non-square) grids
    General sizing fixes
    Fix text fitting when toggling back from List to Grid view
    Fix text fitting when textNode would overflow container
    Correctly compute optimal grid size after orientation change
    Rename .text to .cineraText to avoid CSS selector clash
    Only add click events for the main set of buttons (not its clone)

Player page:
    Size the video and timestamps bar more sensibly
This commit is contained in:
Matt Mascarenhas 2021-02-04 00:13:55 +00:00
parent 2cf8739a60
commit 545938d766
7 changed files with 1161 additions and 828 deletions

View File

@ -23,7 +23,7 @@ typedef struct
version CINERA_APP_VERSION = {
.Major = 0,
.Minor = 8,
.Patch = 1
.Patch = 2
};
#include <stdarg.h> // NOTE(matt): varargs
@ -13507,6 +13507,7 @@ SearchToBuffer(buffers *CollationBuffers, db_header_project *StoredP, project *P
OpenNodeNewLine(B, &IndentationLevel, NODE_DIV, 0);
AppendStringToBuffer(B, Wrap0(" class=\"key_block\">"));
OpenNodeCNewLine(B, &IndentationLevel, NODE_DIV, 0);
AppendHelpKeyToBufferNewLine(B, &IndentationLevel, Wrap0("1"), TRUE);
AppendHelpKeyToBufferNewLine(B, &IndentationLevel, Wrap0("q"), TRUE);
@ -13535,6 +13536,7 @@ SearchToBuffer(buffers *CollationBuffers, db_header_project *StoredP, project *P
AppendHelpKeyToBuffer(B, &IndentationLevel, Wrap0("v"), TRUE);
CloseNodeNewLine(B, &IndentationLevel, NODE_DIV);
CloseNodeNewLine(B, &IndentationLevel, NODE_DIV); // key_block
CloseNodeNewLine(B, &IndentationLevel, NODE_DIV); // help_paragraph

View File

@ -142,20 +142,6 @@ ul.cineraNavPlain li.current > a {
order: 2;
}
#cineraIndex .cineraTraversal .cineraButton
{
/* TODO(matt): Do the cineraTraversal as a flex box, to auto-size these buttons? */
height: 42px;
width: 42px;
padding: 2px;
margin: 2px;
display: inline-flex;
align-items: center;
justify-content: center;
}
#cineraIndex.anim .cineraTraversal,
#cineraIndex.anim .cineraTraversal * p
{
@ -179,6 +165,20 @@ ul.cineraNavPlain li.current > a {
transform: rotate(180deg);
}
#cineraIndex .cineraTraversal .cineraButton
{
box-sizing: border-box;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 2px;
margin: 2px;
height: 42px;
width: 42px;
}
#cineraIndex .cineraTraversal * p
{
transform: rotate(0deg);
@ -274,6 +274,7 @@ ul.cineraNavPlain li.current > a {
}
.cineraQueryContainer #query {
width: 0px;
flex-grow: 1;
}
@ -281,6 +282,7 @@ ul.cineraNavPlain li.current > a {
position: absolute;
top: 2px;
right: 5px;
font-size: small;
color: #000000;
height: 100%;
display: none;
@ -345,14 +347,23 @@ ul.cineraNavPlain li.current > a {
display: flex;
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
overflow: hidden;
}
#cineraIndex #cineraIndexGrid .cineraButton.subdivision .head-item,
#cineraIndex #cineraIndexGrid .cineraButton.subdivision .tail-item
{
display: flex;
width: 100%;
height: 50%;
display: flex;
}
#cineraIndex #cineraIndexGrid .cineraButton.subdivision .head-item p,
#cineraIndex #cineraIndexGrid .cineraButton.subdivision .tail-item p
{
border: 0;
margin: 0;
padding: 0;
}
#cineraIndex #cineraIndexGrid .cineraButton.subdivision.leaf
@ -518,7 +529,7 @@ ul.cineraNavPlain li.current > a {
left: 50%;
transform: translate(-50%, -50%);
max-height: 97%;
overflow-y: scroll;
overflow-y: auto;
}
.cineraHelp .help_container.visible {
@ -526,14 +537,15 @@ ul.cineraNavPlain li.current > a {
}
.cineraHelp .help_container .help_key {
box-sizing: content-box;
font-family: Inconsolata;
font-size: 16px;
border: 1px solid;
display: inline-block;
background-color: #111111; /* Per project */
border-radius: 4px;
height: 16px;
width: 16px;
min-height: 1em;
min-width: 1em;
padding: 4px;
line-height: 16px;
margin: 2px;

View File

@ -21,6 +21,9 @@ var sourceMenus = null;
var helpButton = null;
var helpDocumentation = null;
// NOTE(matt): One set of markers per page. There is code to support multiple, which we may want to extend everywhere
var MarkersContainer = cinera.querySelector(".markers_container");
var views = {
REGULAR: 0,
THEATRE: 1,
@ -32,7 +35,7 @@ var devices = {
MOBILE: 1,
};
var cineraProps = {
var CineraProps = {
C: null,
V: views.REGULAR,
Z: null,
@ -46,11 +49,12 @@ var cineraProps = {
Display: null,
FlexDirection: null,
JustifyContent: null,
O: window.orientation,
O: null,
D: IsMobile() ? devices.MOBILE : devices.DESKTOP,
ScrollX: null,
ScrollY: null,
};
CineraProps.O = GetRealOrientation(CineraProps.IsMobile);
if(titleBar)
{
@ -169,7 +173,7 @@ if(titleBar)
}
viewsMenu = titleBar.querySelector(".views");
if(viewsMenu && cineraProps.D !== devices.MOBILE)
if(viewsMenu && CineraProps.D !== devices.MOBILE)
{
menuState.push(viewsMenu);
var viewsContainer = viewsMenu.querySelector(".views_container");
@ -276,7 +280,7 @@ var MobileCineraContentRule = GetOrSetRule(MobileCineraContentRuleSelector);
var MenuContainerRuleSelector = ".cineraMenus > .menu .quotes_container, .cineraMenus > .menu .references_container, .cineraMenus > .menu .filter_container, .cineraMenus > .menu .views_container, .cineraMenus > .menu .link_container, .cineraMenus > .menu .credits_container";
var MenuContainerRule = GetOrSetRule(MenuContainerRuleSelector);
if(cineraProps.D == devices.MOBILE)
if(CineraProps.D == devices.MOBILE)
{
InitMobileStyle();
}
@ -288,7 +292,7 @@ else
var player = new Player(playerContainer, onRefChanged);
if(cineraProps.D == devices.MOBILE)
if(CineraProps.D == devices.MOBILE)
{
ConnectMobileControls(player);
}
@ -302,7 +306,28 @@ if(viewsMenu && localStorage.getItem(cineraViewStorageItem))
InitScrollEventListener(cinera);
window.addEventListener("resize", function() { player.updateSize(); });
window.addEventListener("resize", function() {
if(CineraProps.IsMobile)
{
setTimeout(player.updateSize, 512);
}
else
{
player.updateSize();
}
});
window.onorientationchange = function() {
if(CineraProps.IsMobile)
{
setTimeout(player.updateSize, 512);
}
else
{
player.updateSize();
}
};
document.addEventListener("keydown", function(ev) {
var key = ev.key;
if(ev.getModifierState("Shift") && key == " ")
@ -350,9 +375,3 @@ else if(lastAnnotation = localStorage.getItem(lastAnnotationStorageItem))
{
player.setTime(lastAnnotation);
}
window.onorientationchange = function()
{
cineraProps.O = window.orientation;
player.updateSize();
};

View File

@ -207,7 +207,7 @@ function sizeAndPositionMenuContainer(TitleBar, SizerElement, Menu)
var ContainerDimX = SizerElementDimX;
var ContainerDimY = SizerElementDimY;
switch(cineraProps.O)
switch(CineraProps.O)
{
case orientations.PORTRAIT:
{
@ -286,14 +286,18 @@ ComputeTallest(Elements, UnhidingClass)
function
ComputeAndSetTallest(Selector, Elements, UnhidingClass)
{
var Result;
Selector.style.height = "unset";
Selector.style.height = ComputeTallest(Elements, UnhidingClass) + "px";
Result = ComputeTallest(Elements, UnhidingClass);
Selector.style.height = Result + "px";
return Result;
}
function ApplyMobileStyle(player)
{
var MaxWidth = MaxWidthOfElement(cinera);
var MaxHeight = MaxHeightOfElement(cinera);
var WindowDim = DeriveReliableWindowDimensions();
var MaxWidth = cinera.offsetWidth;
var MaxHeight = MaxHeightOfElement(cinera, WindowDim);
var IndicesBar = playerContainer.querySelector(".markers_container");
var Markers = IndicesBar.querySelector(".markers");
@ -305,7 +309,7 @@ function ApplyMobileStyle(player)
CineraContentWidth -= EpisodeMarkers[i].offsetWidth;
}
switch(cineraProps.O)
switch(CineraProps.O)
{
case orientations.PORTRAIT:
{
@ -326,28 +330,29 @@ function ApplyMobileStyle(player)
} break;
}
var HeightOfTallestIndex;
if(MobileCineraContentRule !== undefined)
{
MobileCineraContentRule.style.width = CineraContentWidth + "px";
var MarkerList = Markers.querySelectorAll(".marker");
ComputeAndSetTallest(MobileCineraContentRule, MarkerList, "current");
HeightOfTallestIndex = ComputeAndSetTallest(MobileCineraContentRule, MarkerList, "current");
IndicesBar.style.height = HeightOfTallestIndex + "px";
Markers.style.width = CineraContentWidth + "px";
}
var VideoMaxDimX = null;
var VideoMaxDimY = null;
var VideoMaxDimX = CineraContentWidth;
var VideoMaxDimY = MaxHeight - HeightOfTallestIndex;
switch(cineraProps.O)
switch(CineraProps.O)
{
case orientations.PORTRAIT:
{
VideoMaxDimX = MaxWidth;
VideoMaxDimY = MaxHeight - IndicesBar.offsetHeight - titleBar.offsetHeight;
VideoMaxDimY -= titleBar.offsetHeight;
} break;
case orientations.LANDSCAPE_LEFT:
case orientations.LANDSCAPE_RIGHT:
{
VideoMaxDimX = MaxWidth - titleBar.offsetWidth;
VideoMaxDimY = MaxHeight - IndicesBar.offsetHeight;
VideoMaxDimX -= titleBar.offsetWidth;
} break;
}
@ -477,7 +482,8 @@ ConnectMobileControls(player)
Player.prototype.updateSize = function() {
var width = 0;
var height = 0;
if(cineraProps.D === devices.DESKTOP)
CineraProps.O = GetRealOrientation(orientations.LANDSCAPE_LEFT, CineraProps.IsMobile);
if(CineraProps.D === devices.DESKTOP)
{
width = this.videoContainer.offsetWidth;
height = width / 16 * 9; // TODO(matt): Get the aspect ratio from the video itself?
@ -862,7 +868,7 @@ function toggleMenuVisibility(element) {
function handleMouseOverViewsMenu()
{
switch(cineraProps.V)
switch(CineraProps.V)
{
case views.REGULAR:
case views.THEATRE:
@ -904,24 +910,24 @@ function leaveFullScreen_()
}
function toggleTheatreMode() {
switch(cineraProps.V)
switch(CineraProps.V)
{
case views.REGULAR:
{
cineraProps.C = cinera.style.backgroundColor;
cineraProps.Z = cinera.style.zIndex;
cineraProps.X = cinera.style.left;
cineraProps.Y = cinera.style.top;
cineraProps.W = cinera.style.width;
cineraProps.mW = cinera.style.maxWidth;
cineraProps.H = cinera.style.height;
cineraProps.mH = cinera.style.maxHeight;
cineraProps.P = cinera.style.position;
cineraProps.Display = cinera.style.display;
cineraProps.FlexDirection = cinera.style.flexDirection;
cineraProps.JustifyContent = cinera.style.justifyContent;
cineraProps.ScrollX = window.scrollX;
cineraProps.ScrollY = window.scrollY;
CineraProps.C = cinera.style.backgroundColor;
CineraProps.Z = cinera.style.zIndex;
CineraProps.X = cinera.style.left;
CineraProps.Y = cinera.style.top;
CineraProps.W = cinera.style.width;
CineraProps.mW = cinera.style.maxWidth;
CineraProps.H = cinera.style.height;
CineraProps.mH = cinera.style.maxHeight;
CineraProps.P = cinera.style.position;
CineraProps.Display = cinera.style.display;
CineraProps.FlexDirection = cinera.style.flexDirection;
CineraProps.JustifyContent = cinera.style.justifyContent;
CineraProps.ScrollX = window.scrollX;
CineraProps.ScrollY = window.scrollY;
cinera.style.backgroundColor = "#000";
cinera.style.zIndex = 64;
@ -939,43 +945,43 @@ function toggleTheatreMode() {
viewItems[0].setAttribute("data-id", "regular");
viewItems[0].setAttribute("title", "Regular mode");
viewItems[0].firstChild.nodeValue = "📺";
} cineraProps.V = views.THEATRE; localStorage.setItem(cineraViewStorageItem, views.THEATRE); break;
} CineraProps.V = views.THEATRE; localStorage.setItem(cineraViewStorageItem, views.THEATRE); break;
case views.SUPERTHEATRE:
{
leaveFullScreen_();
}
case views.THEATRE:
{
cinera.style.backgroundColor = cineraProps.C;
cinera.style.zIndex = cineraProps.Z;
cinera.style.left = cineraProps.X;
cinera.style.top = cineraProps.Y;
cinera.style.width = cineraProps.W;
cinera.style.maxWidth = cineraProps.mW;
cinera.style.height = cineraProps.H;
cinera.style.maxHeight = cineraProps.mH;
cinera.style.position = cineraProps.P;
cinera.style.display = cineraProps.Display;
cinera.style.flexDirection = cineraProps.FlexDirection;
cinera.style.justifyContent = cineraProps.JustifyContent;
cinera.style.backgroundColor = CineraProps.C;
cinera.style.zIndex = CineraProps.Z;
cinera.style.left = CineraProps.X;
cinera.style.top = CineraProps.Y;
cinera.style.width = CineraProps.W;
cinera.style.maxWidth = CineraProps.mW;
cinera.style.height = CineraProps.H;
cinera.style.maxHeight = CineraProps.mH;
cinera.style.position = CineraProps.P;
cinera.style.display = CineraProps.Display;
cinera.style.flexDirection = CineraProps.FlexDirection;
cinera.style.justifyContent = CineraProps.JustifyContent;
window.scroll(
{
top: cineraProps.ScrollY,
left: cineraProps.ScrollX
top: CineraProps.ScrollY,
left: CineraProps.ScrollX
}
);
viewItems[0].setAttribute("data-id", "theatre");
viewItems[0].setAttribute("title", "Theatre mode");
viewItems[0].firstChild.nodeValue = "🎭";
} cineraProps.V = views.REGULAR; localStorage.removeItem(cineraViewStorageItem); break;
} CineraProps.V = views.REGULAR; localStorage.removeItem(cineraViewStorageItem); break;
}
player.updateSize();
}
function toggleSuperTheatreMode()
{
switch(cineraProps.V)
switch(CineraProps.V)
{
case views.REGULAR:
{
@ -984,12 +990,12 @@ function toggleSuperTheatreMode()
case views.THEATRE:
{
enterFullScreen_();
} cineraProps.V = views.SUPERTHEATRE; localStorage.setItem(cineraViewStorageItem, views.SUPERTHEATRE); break;
} CineraProps.V = views.SUPERTHEATRE; localStorage.setItem(cineraViewStorageItem, views.SUPERTHEATRE); break;
case views.SUPERTHEATRE:
{
leaveFullScreen_();
toggleTheatreMode();
} cineraProps.V = views.REGULAR; localStorage.removeItem(cineraViewStorageItem); break;
} CineraProps.V = views.REGULAR; localStorage.removeItem(cineraViewStorageItem); break;
}
player.updateSize();
}

View File

@ -4,6 +4,73 @@ var orientations = {
LANDSCAPE_RIGHT: -90,
};
function
DeriveReliableWindowDimensions()
{
// https://www.howtocreate.co.uk/tutorials/javascript/browserwindow
var Result = {
X: null,
Y: null,
};
var DisplaySettings = [];
for(var i = 0; i < document.body.children.length; ++i)
{
var Child = document.body.children[i];
DisplaySettings.push(Child.style.display);
Child.style.display = "none";
}
var Element = document.createElement("div");
Element.style.position = "fixed";
Element.style.top = 0;
Element.style.right = 0;
Element.style.bottom = 0;
Element.style.left = 0;
Element.style.zOffset = -1;
Element.style.opacity = 0;
var ElementInPlace = document.body.appendChild(Element);
Result.X = ElementInPlace.offsetWidth;
Result.Y = ElementInPlace.offsetHeight;
ElementInPlace.remove();
for(var i = 0; i < document.body.children.length; ++i)
{
var Child = document.body.children[i];
Child.style.display = DisplaySettings.shift();
}
return Result;
}
function
GetRealOrientation(PreferredLandscape, IsMobile)
{
var Result = window.orientation;
var WindowDim = {
X: null,
Y: null,
};
if(IsMobile)
{
WindowDim = DeriveReliableWindowDimensions();
}
else
{
WindowDim.X = document.body.clientWidth;
WindowDim.Y = document.body.clientHeight;
}
if(WindowDim.Y > WindowDim.X)
{
Result = orientations.PORTRAIT;
}
else if(Result == undefined || Result == orientations.PORTRAIT)
{
Result = PreferredLandscape;
}
return Result;
}
var DebugConsoleMessageCount = 0;
function Say(Message)
{
@ -275,7 +342,7 @@ function getElementYOffsetFromPage(el) {
}
function
MaxWidthOfElement(Element)
MaxWidthOfElement(Element, WindowDim)
{
var Result = 0;
var OriginalWidth = Element.style.width;
@ -283,21 +350,31 @@ MaxWidthOfElement(Element)
var Max = parseInt(window.getComputedStyle(Element).width);
Element.style.width = "unset";
var Default = parseInt(window.getComputedStyle(Element).width);
Element.style.width = OriginalWidth;
if(Max > window.innerWidth || Max == Default)
var InnerWidth = WindowDim ? WindowDim.X : document.body.clientWidth;
if(Max > InnerWidth || Max == Default)
{
Result = window.innerWidth;
Result = InnerWidth;
}
else
{
Result = Max;
}
Element.style.width = Result + "px";
if(Element.scrollWidth > Element.clientWidth)
{
Result = Element.clientWidth;
}
Element.style.width = OriginalWidth;
return Result;
}
function
MaxHeightOfElement(Element)
MaxHeightOfElement(Element, WindowDim)
{
var Result = 0;
var OriginalHeight = Element.style.height;
@ -305,16 +382,25 @@ MaxHeightOfElement(Element)
var Max = parseInt(window.getComputedStyle(Element).height);
Element.style.height = "unset";
var Default = parseInt(window.getComputedStyle(Element).height);
Element.style.height = OriginalHeight;
if(Max > window.innerHeight || Max == Default)
var InnerHeight = WindowDim ? WindowDim.Y : document.body.clientHeight;
if(Max > InnerHeight || Max == Default)
{
Result = window.innerHeight;
Result = InnerHeight;
}
else
{
Result = Max;
}
Element.style.height = Result + "px";
if(Element.scrollHeight > Element.clientHeight)
{
Result = Element.clientHeight;
}
Element.style.height = OriginalHeight;
return Result;
}

View File

@ -1,4 +1,5 @@
document.body.style.overflowY = "scroll";
CineraProps.Orientation = GetRealOrientation(orientations.LANDSCAPE_LEFT, CineraProps.IsMobile);
// Element Selection
//
@ -48,10 +49,12 @@ SyncNavState();
// NOTE(matt): Listeners
//
BindControlKeys();
BindGridKeys(Nav.GridSize);
BindControls();
InitResizeEventListener();
InitOrientationChangeListener();
InitScrollEventListener(Nav.Nexus);
InitScrollEventListener(Nav.Grid);
//
////

View File

@ -2,8 +2,7 @@
//
var CineraProps = {
IsMobile: IsMobile(),
Orientation: window.orientation,
Orientation: null,
};
var Search = {
@ -680,8 +679,17 @@ var Nav = {
Nexus: null,
GridContainer: null,
ButtonsContainer: null,
GridSize: null,
GridWidth: null,
GridSize: {
X: null,
Y: null,
},
GridMinCellsPerDimension: 1,
GridMaxCellsPerDimension: 4,
GridDim: {
X: null,
Y: null,
},
MinButtonDim: 100,
ButtonDim: null,
GridColumnGap: null,
GridRowGap: null,
@ -855,23 +863,74 @@ SetHelpKeyAvailability(GridSize)
Nav.Controls.HelpKeys[i].classList.remove("unavailable");
}
if(GridSize < 4)
/* 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[3].classList.add("unavailable");
Nav.Controls.HelpKeys[7].classList.add("unavailable");
Nav.Controls.HelpKeys[11].classList.add("unavailable");
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 < 3)
if(GridSize.X < 3)
{
Nav.Controls.HelpKeys[2].classList.add("unavailable");
Nav.Controls.HelpKeys[6].classList.add("unavailable");
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");
}
}
}
}
}
@ -895,17 +954,29 @@ SyncNavState()
if(StateBitIsSet(state_bit.SORT_REVERSED)) { Sort(true); }
// Nav.ViewType is initialised to view_type.GRID, so we needn't do anything if the Nav.State is also on Grid
if(StateBitIsSet(state_bit.VIEW_LIST)) { ToggleView(); }
if(StateBitIsSet(state_bit.VIEW_LIST) || !GridSizeIsSupported(Nav.GridSize))
{
ToggleView();
}
}
else
{
Nav.Controls.Save.textContent = "Save Settings: ✘";
// Nav.ViewType was initialised to view_type.GRID
if(!GridSizeIsSupported(Nav.GridSize))
{
ToggleView();
}
}
}
else
{
Nav.State = 0;
SetStateBit(state_bit.VIEW_GRID); // NOTE(matt): Nav.ViewType was initialised to view_type.GRID
if(!GridSizeIsSupported(Nav.GridSize))
{
ToggleView();
}
}
}
@ -936,7 +1007,6 @@ InitTraversalStack()
}
Nav.TraversalStack.push(Level);
console.log(Level);
}
function
@ -951,6 +1021,13 @@ 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)
{
@ -958,17 +1035,7 @@ EmptyAndResetButton(Button)
Button.Element.style.fontSize = null;
Button.Element.style.fontWeight = null;
Button.Element.style.width = Nav.ButtonDim + "px";
Button.Element.style.height = Nav.ButtonDim + "px";
for(var i = 0; i < Button.Element.children.length; ++i)
{
Button.Element.children[i].style.fontSize = null;
Button.Element.children[i].style.fontWeight = null;
Button.Element.children[i].style.width = null;
Button.Element.children[i].style.height = null;
}
SetDim(Button.Element, null, null);
for(var i = 0; i < Button.Element.classList.length;)
{
@ -1074,8 +1141,11 @@ ComputeItemDistribution(Level)
FullButtonEntryCount: 0,
};
// NOTE(matt): Assuming a square grid, reserving the top row for projects
Result.ButtonsForProjects = Math.min(Math.sqrt(Nav.Buttons.length), Result.ProjectsToPlace);
// 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)
{
@ -1126,8 +1196,6 @@ ResetButtonsContainerClone()
ResetTransform(Nav.Transition.Transforms.ButtonsContainerClone.Initial);
ApplyTransform(Nav.Transition.ButtonsContainerCloneElement, Nav.Transition.Transforms.ButtonsContainerClone.Current);
Nav.Transition.ButtonsContainerCloneElement.style.width = Nav.ButtonsContainer.style.width;
Nav.Transition.ButtonsContainerCloneElement.style.height = Nav.ButtonsContainer.style.height;
Nav.Transition.ButtonsContainerCloneElement.style.gridTemplateColumns = Nav.ButtonsContainer.style.gridTemplateColumns;
Nav.Transition.ButtonsContainerCloneElement.style.gridTemplateRows = Nav.ButtonsContainer.style.gridTemplateRows;
@ -1146,6 +1214,7 @@ ResetButtonsContainerClone()
Nav.ButtonsContainer.style.zIndex = 1;
Nav.ButtonsContainer.style.order = 1;
Nav.Transition.ButtonsContainerCloneElement.style.order = 0;
Nav.Transition.ButtonsContainerCloneElement.style.display = "none";
}
function
@ -1161,6 +1230,7 @@ CloneButtonsContainer()
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
@ -1556,8 +1626,8 @@ ComputeButtonGeometryRelativeToGrid(Button)
var ButtonStyle = window.getComputedStyle(Button);
var GridDimX = Nav.GridWidth;
var GridDimY = Nav.GridWidth;
var GridDimX = Nav.GridDim.X;
var GridDimY = Nav.GridDim.Y;
var ButtonDimX = parseInt(ButtonStyle.width);
var ButtonDimY = parseInt(ButtonStyle.height);
@ -1620,31 +1690,23 @@ GetTraversalLevelBundle()
function
FitText(Element)
{
if(Element.textContent.includes("Day 068") || Element.textContent.includes("Day 136"))
{
if(Element.textContent.includes("Day 068"))
{
Say("FitText(068)");
}
else
{
Say("FitText(136)");
}
var Style = window.getComputedStyle(Element);
Say("Height: " + Style.height);
var ParentStyle = window.getComputedStyle(Element.parentElement);
Say("Parent Height: " + ParentStyle.height);
}
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";
var FontSize = parseInt(window.getComputedStyle(Element).fontSize);
while(FontSize > 10 && IsOverflowed(Element))
while(FontSize > 10 && (IsOverflowed(Element) || parseInt(ParagraphStyle.width) > Width || parseInt(ParagraphStyle.height) > Height))
{
FontSize -= 0.2;
Element.style.fontSize = FontSize + "px";
ParagraphStyle = window.getComputedStyle(Paragraph);
}
if(IsOverflowed(Element))
ParagraphStyle = window.getComputedStyle(Paragraph);
if(IsOverflowed(Element) || parseInt(ParagraphStyle.width) > Width || parseInt(ParagraphStyle.height) > Height)
{
Element.style.fontWeight = "normal";
}
@ -1654,16 +1716,21 @@ FitText(Element)
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("text");
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;
}
@ -1673,16 +1740,25 @@ AppendItemToButton(Button, Text, ItemEnd)
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);
Item.textContent = Text;
Item.style.width = Nav.ButtonDim + "px";
Item.style.height = (Nav.ButtonDim / 2) + "px";
// 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);
}
function
UpdateButtons(TransitionType, RelevantButton, PoppedLevel)
{
if(GridSizeIsSupported(Nav.GridSize))
{
var LevelBundle = GetTraversalLevelBundle();
Nav.Controls.GridTraversal.Prev.children[0].textContent = "←";
@ -1708,7 +1784,7 @@ UpdateButtons(TransitionType, RelevantButton, PoppedLevel)
else if(SiblingIsLeaf(siblings.PREV))
{
Nav.Controls.GridTraversal.PrevAscends = true;
Nav.Controls.GridTraversal.Prev.children[0].textContent = "↰";
Nav.Controls.GridTraversal.Prev.children[0].textContent = Nav.SortChronological ? "↰" : "↲";
}
if(!HasNextSibling(LevelBundle.This))
@ -1777,7 +1853,7 @@ UpdateButtons(TransitionType, RelevantButton, PoppedLevel)
{
This.Element.classList.add("leaf");
var TextElement = document.createElement("p");
TextElement.classList.add("text");
TextElement.classList.add("cineraText");
if(!Nav.SortChronological)
{
TextElement.style.transform = "rotate3d(0, 0, 1, 180deg)";
@ -1822,7 +1898,7 @@ UpdateButtons(TransitionType, RelevantButton, PoppedLevel)
{
This.Element.classList.add("leaf");
var ButtonLink = document.createElement("a");
ButtonLink.classList.add("text");
ButtonLink.classList.add("cineraText");
if(!Nav.SortChronological)
{
ButtonLink.style.transform = "rotate3d(0, 0, 1, 180deg)";
@ -1875,7 +1951,7 @@ UpdateButtons(TransitionType, RelevantButton, PoppedLevel)
}
else
{
TargetA0.ScrollX = Nav.GridWidth + Padding;
TargetA0.ScrollX = Nav.GridDim.X + Padding;
}
Nav.Transition.StageDurations.push(320);
@ -1889,7 +1965,7 @@ UpdateButtons(TransitionType, RelevantButton, PoppedLevel)
{
Nav.ButtonsContainer.style.order = 0;
Nav.Transition.ButtonsContainerCloneElement.style.order = 1;
ScrollX = Nav.GridWidth + Padding;
ScrollX = Nav.GridDim.X + Padding;
}
else
{
@ -1911,7 +1987,7 @@ UpdateButtons(TransitionType, RelevantButton, PoppedLevel)
var Padding = Nav.GridColumnGap;
if(Nav.SortChronological)
{
TargetA0.ScrollX = Nav.GridWidth + Padding;
TargetA0.ScrollX = Nav.GridDim.X + Padding;
}
else
{
@ -1936,7 +2012,7 @@ UpdateButtons(TransitionType, RelevantButton, PoppedLevel)
{
Nav.ButtonsContainer.style.order = 0;
Nav.Transition.ButtonsContainerCloneElement.style.order = 1;
ScrollX = Nav.GridWidth + Padding;
ScrollX = Nav.GridDim.X + Padding;
}
Nav.Transition.ButtonsContainerCloneElement.style.paddingRight = Padding + "px";
@ -2195,6 +2271,7 @@ UpdateButtons(TransitionType, RelevantButton, PoppedLevel)
}
DoTransition();
}
}
}
function
@ -2438,7 +2515,7 @@ ShouldFireGridEvents()
}
function
ModifyButtonKeybinding(Event)
ModifyControlKeybinding(Event)
{
// TODO(matt): Settle on the final sets of bindings
var Chron = Nav.SortChronological;
@ -2456,79 +2533,102 @@ ModifyButtonKeybinding(Event)
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;
}
}
if(Nav.GridSize == 2)
{
switch(Key)
{
case "1": if(ShouldFireGridEvents()) { ID.Element = Nav.Buttons[Chron ? 0 : 3].Element; ID.Button = Nav.Buttons[Chron ? 0 : 3]; EnqueueInteraction(IT, ID); } break;
case "2": if(ShouldFireGridEvents()) { ID.Element = Nav.Buttons[Chron ? 1 : 2].Element; ID.Button = Nav.Buttons[Chron ? 1 : 2]; EnqueueInteraction(IT, ID); } break;
function
BindControlKeys()
{
document.addEventListener("keydown", ModifyControlKeybinding);
}
case "q": if(ShouldFireGridEvents()) { ID.Element = Nav.Buttons[Chron ? 2 : 1].Element; ID.Button = Nav.Buttons[Chron ? 2 : 1]; EnqueueInteraction(IT, ID); } break;
case "w": if(ShouldFireGridEvents()) { ID.Element = Nav.Buttons[Chron ? 3 : 0].Element; ID.Button = Nav.Buttons[Chron ? 3 : 0]; EnqueueInteraction(IT, ID); } break;
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);
}
}
else if(Nav.GridSize == 3)
{
switch(Key)
{
case "1": if(ShouldFireGridEvents()) { ID.Element = Nav.Buttons[Chron ? 0 : 8].Element; ID.Button = Nav.Buttons[Chron ? 0 : 8]; EnqueueInteraction(IT, ID); } break;
case "2": if(ShouldFireGridEvents()) { ID.Element = Nav.Buttons[Chron ? 1 : 7].Element; ID.Button = Nav.Buttons[Chron ? 1 : 7]; EnqueueInteraction(IT, ID); } break;
case "3": if(ShouldFireGridEvents()) { ID.Element = Nav.Buttons[Chron ? 2 : 6].Element; ID.Button = Nav.Buttons[Chron ? 2 : 6]; EnqueueInteraction(IT, ID); } break;
case "q": if(ShouldFireGridEvents()) { ID.Element = Nav.Buttons[Chron ? 3 : 5].Element; ID.Button = Nav.Buttons[Chron ? 3 : 5]; EnqueueInteraction(IT, ID); } break;
case "w": if(ShouldFireGridEvents()) { ID.Element = Nav.Buttons[Chron ? 4 : 4].Element; ID.Button = Nav.Buttons[Chron ? 4 : 4]; EnqueueInteraction(IT, ID); } break;
case "e": if(ShouldFireGridEvents()) { ID.Element = Nav.Buttons[Chron ? 5 : 3].Element; ID.Button = Nav.Buttons[Chron ? 5 : 3]; EnqueueInteraction(IT, ID); } break;
case "a": if(ShouldFireGridEvents()) { ID.Element = Nav.Buttons[Chron ? 6 : 2].Element; ID.Button = Nav.Buttons[Chron ? 6 : 2]; EnqueueInteraction(IT, ID); } break;
case "s": if(ShouldFireGridEvents()) { ID.Element = Nav.Buttons[Chron ? 7 : 1].Element; ID.Button = Nav.Buttons[Chron ? 7 : 1]; EnqueueInteraction(IT, ID); } break;
case "d": if(ShouldFireGridEvents()) { ID.Element = Nav.Buttons[Chron ? 8 : 0].Element; ID.Button = Nav.Buttons[Chron ? 8 : 0]; EnqueueInteraction(IT, ID); } break;
}
}
else if(Nav.GridSize == 4)
{
switch(Key)
{
case "1": if(ShouldFireGridEvents()) { ID.Element = Nav.Buttons[Chron ? 0 : 15].Element; ID.Button = Nav.Buttons[Chron ? 0 : 15]; EnqueueInteraction(IT, ID); } break;
case "2": if(ShouldFireGridEvents()) { ID.Element = Nav.Buttons[Chron ? 1 : 14].Element; ID.Button = Nav.Buttons[Chron ? 1 : 14]; EnqueueInteraction(IT, ID); } break;
case "3": if(ShouldFireGridEvents()) { ID.Element = Nav.Buttons[Chron ? 2 : 13].Element; ID.Button = Nav.Buttons[Chron ? 2 : 13]; EnqueueInteraction(IT, ID); } break;
case "4": if(ShouldFireGridEvents()) { ID.Element = Nav.Buttons[Chron ? 3 : 12].Element; ID.Button = Nav.Buttons[Chron ? 3 : 12]; EnqueueInteraction(IT, ID); } break;
case "q": if(ShouldFireGridEvents()) { ID.Element = Nav.Buttons[Chron ? 4 : 11].Element; ID.Button = Nav.Buttons[Chron ? 4 : 11]; EnqueueInteraction(IT, ID); } break;
case "w": if(ShouldFireGridEvents()) { ID.Element = Nav.Buttons[Chron ? 5 : 10].Element; ID.Button = Nav.Buttons[Chron ? 5 : 10]; EnqueueInteraction(IT, ID); } break;
case "e": if(ShouldFireGridEvents()) { ID.Element = Nav.Buttons[Chron ? 6 : 9].Element; ID.Button = Nav.Buttons[Chron ? 6 : 9]; EnqueueInteraction(IT, ID); } break;
case "r": if(ShouldFireGridEvents()) { ID.Element = Nav.Buttons[Chron ? 7 : 8].Element; ID.Button = Nav.Buttons[Chron ? 7 : 8]; EnqueueInteraction(IT, ID); } break;
case "a": if(ShouldFireGridEvents()) { ID.Element = Nav.Buttons[Chron ? 8 : 7].Element; ID.Button = Nav.Buttons[Chron ? 8 : 7]; EnqueueInteraction(IT, ID); } break;
case "s": if(ShouldFireGridEvents()) { ID.Element = Nav.Buttons[Chron ? 9 : 6].Element; ID.Button = Nav.Buttons[Chron ? 9 : 6]; EnqueueInteraction(IT, ID); } break;
case "d": if(ShouldFireGridEvents()) { ID.Element = Nav.Buttons[Chron ? 10 : 5].Element; ID.Button = Nav.Buttons[Chron ? 10 : 5]; EnqueueInteraction(IT, ID); } break;
case "f": if(ShouldFireGridEvents()) { ID.Element = Nav.Buttons[Chron ? 11 : 4].Element; ID.Button = Nav.Buttons[Chron ? 11 : 4]; EnqueueInteraction(IT, ID); } break;
case "z": if(ShouldFireGridEvents()) { ID.Element = Nav.Buttons[Chron ? 12 : 3].Element; ID.Button = Nav.Buttons[Chron ? 12 : 3]; EnqueueInteraction(IT, ID); } break;
case "x": if(ShouldFireGridEvents()) { ID.Element = Nav.Buttons[Chron ? 13 : 2].Element; ID.Button = Nav.Buttons[Chron ? 13 : 2]; EnqueueInteraction(IT, ID); } break;
case "c": if(ShouldFireGridEvents()) { ID.Element = Nav.Buttons[Chron ? 14 : 1].Element; ID.Button = Nav.Buttons[Chron ? 14 : 1]; EnqueueInteraction(IT, ID); } break;
case "v": if(ShouldFireGridEvents()) { ID.Element = Nav.Buttons[Chron ? 15 : 0].Element; ID.Button = Nav.Buttons[Chron ? 15 : 0]; EnqueueInteraction(IT, ID); } break;
}
}
}
function
BindKeys()
BindGridKeys()
{
document.addEventListener("keydown", ModifyButtonKeybinding);
document.addEventListener("keydown", ModifyGridKeybinding);
}
function
UnbindKeys()
UnbindGridKeys()
{
document.removeEventListener("keydown", ModifyButtonKeybinding);
}
function
RebindKeys()
{
UnbindKeys();
BindKeys();
document.removeEventListener("keydown", ModifyGridKeybinding);
}
function
@ -2564,7 +2664,7 @@ DoRotationStage(Now)
for(var i = 0; i < Nav.ButtonsContainer.children.length; ++i)
{
var This = Nav.ButtonsContainer.children[i];
var ThisTexts = This.querySelectorAll(".text");
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)";
@ -2589,7 +2689,7 @@ DoRotationStage(Now)
function
RotateButtons(Initialising)
{
// TODO(matt): Consider pushing this through DoTransition(), aware that it'd need to transform ".text" children
// 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)
@ -2654,15 +2754,27 @@ Sort(Initialising)
}
if(Initialising && Nav.Transition.Enabled) { setTimeout(AddAnimationClass, 320); }
UnbindKeys();
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);
RebindKeys();
BindGridKeys();
runSearch(true);
}
@ -2715,6 +2827,29 @@ EnqueueInteraction(InteractionType, Data)
}
}
function
UseOrientation(Orientation)
{
Nav.GridContainer.classList.remove("Portrait", "Landscape", "Left", "Right");
switch(Orientation)
{
case orientations.PORTRAIT:
{
Nav.GridContainer.classList.add("Portrait");
} break;
case orientations.LANDSCAPE_LEFT:
{
Nav.GridContainer.classList.add("Landscape", "Left");
} break;
case orientations.LANDSCAPE_RIGHT:
{
Nav.GridContainer.classList.add("Landscape", "Right");
} break;
}
}
function
InitNexus()
{
@ -2749,91 +2884,151 @@ InitNexus()
ScrollCondition = true; // NOTE(matt): Variable in cinera_pre.js, we init with the Grid which is the view we want to auto-scroll
if(CineraProps.IsMobile)
{
switch(CineraProps.Orientation)
{
case orientations.LANDSCAPE_LEFT:
{
Nav.GridContainer.classList.add("Landscape", "Left");
} break;
case orientations.LANDSCAPE_RIGHT:
{
Nav.GridContainer.classList.add("Landscape", "Right");
} break;
case orientations.PORTRAIT:
{
Nav.GridContainer.classList.add("Portrait");
} break;
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 = null;
var DimOffset = {
var Result = {
X: null,
Y: null,
};
var WindowDim;
if(CineraProps.IsMobile)
{
WindowDim = DeriveReliableWindowDimensions();
}
else
{
WindowDim.X = document.body.clientWidth;
WindowDim.Y = document.body.clientHeight;
}
var DimReduction = {
X: 0,
Y: Nav.Controls.Header.offsetHeight,
};
if(CineraProps.Orientation == orientations.LANDSCAPE_LEFT || CineraProps.Orientation == orientations.LANDSCAPE_RIGHT)
Nav.Transition.ButtonsTransitionContainerElement.style = null;
Nav.ButtonsContainer.style = null;
Nav.Controls.GridTraversal.Header.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)
{
DimOffset.X += Nav.Controls.GridTraversal.Header.offsetWidth;
Nav.GridContainer.classList.remove("hidden");
}
if(CineraProps.IsMobile && (CineraProps.Orientation == orientations.LANDSCAPE_LEFT || CineraProps.Orientation == orientations.LANDSCAPE_RIGHT))
{
DimReduction.X += Nav.Controls.GridTraversal.Header.offsetWidth;
}
else
{
DimOffset.Y += Nav.Controls.GridTraversal.Header.offsetHeight;
DimReduction.Y += Nav.Controls.GridTraversal.Header.offsetHeight;
}
if(GridWasHidden)
{
Nav.GridContainer.classList.add("hidden");
}
var MaxWidth = MaxWidthOfElement(Nav.Nexus) - DimOffset.X;
var MaxHeight = MaxHeightOfElement(Nav.Nexus) - DimOffset.Y;
var MaxWidth = MaxWidthOfElement(Nav.Nexus, WindowDim) - DimReduction.X;
var MaxHeight = MaxHeightOfElement(Nav.Nexus, WindowDim) - DimReduction.Y;
var BodyStyle = window.getComputedStyle(document.body);
if(Nav.Nexus.parentNode == document.body)
{
// TODO(matt): Robustify this
var BodyStyle = window.getComputedStyle(document.body);
MaxWidth -= parseInt(BodyStyle.marginRight);
MaxHeight -= parseInt(BodyStyle.marginBottom);
}
if(MaxWidth > MaxHeight)
Result.X = ComputePossibleCellsInDimension(MaxWidth, Nav.GridColumnGap);
Result.Y = ComputePossibleCellsInDimension(MaxHeight, Nav.GridRowGap);
if(GridSizeMeetsMinimumSupported(Result))
{
MaxWidth = MaxHeight;
}
else if(MaxHeight > MaxWidth)
{
MaxHeight = MaxWidth;
}
Result.X = Clamp(Nav.GridMinCellsPerDimension, Result.X, Nav.GridMaxCellsPerDimension);
Result.Y = Clamp(Nav.GridMinCellsPerDimension, Result.Y, Nav.GridMaxCellsPerDimension);
var MinButtonSize = 100;
var ButtonDimBasedOnX = Math.floor((MaxWidth - Nav.GridColumnGap * (Result.X - 1)) / Result.X);
var ButtonDimBasedOnY = Math.floor((MaxHeight - Nav.GridRowGap * (Result.Y - 1)) / Result.Y);
var MinGridSize = 2;
var MaxGridSize = 4;
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
Result = Clamp(MinGridSize, Math.floor(MaxWidth / MinButtonSize), MaxGridSize);
Nav.ButtonDim = (MaxWidth - Nav.GridRowGap * (Result - 1)) / Result;
// TODO(matt): If Safari's head-item sizing bug proves to be a problem, use this:
/*
var ButtonStyleRuleSelector = "#cineraIndex #cineraIndexGrid .cineraButton.subdivision";
var ButtonStyleRule = GetOrSetRule(ButtonStyleRuleSelector);
ButtonStyleRule.style.width = Nav.ButtonDim + "px";
ButtonStyleRule.style.height = Nav.ButtonDim + "px";
*/
var GridTemplateColumnsStyle = "repeat(" + Result + ", 1fr)";
var GridTemplateColumnsStyle = "repeat(" + Result.X + ", minmax(" + Nav.ButtonDim + "px, " + Nav.ButtonDim + "px))";
Nav.ButtonsContainer.style.gridTemplateColumns = GridTemplateColumnsStyle;
var GridTemplateRowsStyle = "repeat(" + Result + ", " + Nav.ButtonDim + "px)";
var GridTemplateRowsStyle = "repeat(" + Result.Y + ", " + Nav.ButtonDim + "px)";
Nav.ButtonsContainer.style.gridTemplateRows = GridTemplateRowsStyle;
Nav.ButtonsContainer.style.width = MaxWidth + "px";
Nav.ButtonsContainer.style.height = MaxHeight + "px";
Nav.GridDim.X = Nav.ButtonDim * Result.X + Nav.GridColumnGap * (Result.X - 1);
Nav.GridDim.Y = Nav.ButtonDim * Result.Y + Nav.GridRowGap * (Result.Y - 1);
Nav.Transition.ButtonsTransitionContainerElement.style.width = MaxWidth + "px";
Nav.Transition.ButtonsTransitionContainerElement.style.height = MaxHeight + "px";
SetDim(Nav.Transition.ButtonsTransitionContainerElement, Nav.GridDim.X + "px", Nav.GridDim.Y + "px");
Nav.GridWidth = MaxWidth;
Nav.Controls.GridTraversal.Header.style.maxWidth = Nav.GridDim.X + "px";
Nav.Controls.GridTraversal.Header.style.maxHeight = Nav.GridDim.Y + "px";
var TraversalButtonCount = 3;
if(Nav.Controls.GridTraversal.Header.scrollWidth > Nav.Controls.GridTraversal.Header.clientWidth)
{
var TraversalButtonDim = Nav.Controls.GridTraversal.Header.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.Header.scrollHeight > Nav.Controls.GridTraversal.Header.clientHeight)
{
var TraversalButtonDim = Nav.Controls.GridTraversal.Header.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;
}
@ -3273,6 +3468,8 @@ ToggleView()
}
}
else
{
if(GridSizeIsSupported(Nav.GridSize))
{
Nav.Controls.View.textContent = "View: Grid";
Nav.ViewType = view_type.GRID;
@ -3291,14 +3488,19 @@ ToggleView()
var ProjectsStack = BuildProjectsStack(TargetLevel);
DeriveTraversalStack(ProjectsStack, TargetLevel);
}
UpdateButtons();
Nav.List.classList.add("hidden");
Nav.GridContainer.classList.remove("hidden");
UpdateButtons();
ScrollToWithOffset(Nav.Controls.GridTraversal.Header, Nav.Controls.Header.offsetHeight);
}
}
else
{
// TODO(matt): Inform user that grid view is unavailable
}
}
ScrollCondition = Nav.ViewType == view_type.GRID;
}
@ -3431,14 +3633,16 @@ BindControls()
function
InitButtons()
{
if(GridSizeIsSupported(Nav.GridSize))
{
var ButtonPrototype = document.createElement("div");
ButtonPrototype.classList.add("cineraButton", "subdivision");
for(var i = 0; i < Math.pow(Nav.GridSize, 2); ++i)
for(var i = 0; i < Nav.GridSize.X * Nav.GridSize.Y; ++i)
{
Nav.ButtonsContainer.appendChild(ButtonPrototype.cloneNode());
}
var Buttons = Nav.Nexus.querySelectorAll(".cineraButton.subdivision");
var Buttons = Nav.ButtonsContainer.querySelectorAll(".cineraButton.subdivision");
for(let j = 0; j < Buttons.length; ++j)
{
let Button = {
@ -3459,8 +3663,7 @@ InitButtons()
Nav.Buttons.push(Button);
}
BindKeys();
}
}
function
@ -3471,9 +3674,7 @@ ReinitButtons()
Nav.Buttons[0].Element.remove();
Nav.Buttons.shift();
}
RebindKeys();
InitButtons();
UpdateButtons();
}
function
@ -3600,20 +3801,48 @@ PseudoPushButton(Button)
}
function
InitResizeEventListener()
ResizeFunction()
{
window.addEventListener("resize", function() {
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);
SetHelpKeyAvailability(Nav.GridSize)
}
else if(Nav.ViewType == view_type.GRID)
{
ToggleView();
// 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();
}
});
}
@ -3624,35 +3853,11 @@ InitOrientationChangeListener()
{
if(CineraProps.IsMobile)
{
CineraProps.Orientation = window.orientation;
Nav.GridContainer.classList.remove("Portrait", "Landscape", "Left", "Right");
switch(CineraProps.Orientation)
{
case orientations.LANDSCAPE_LEFT:
{
Nav.GridContainer.classList.add("Landscape", "Left");
} break;
case orientations.LANDSCAPE_RIGHT:
{
Nav.GridContainer.classList.add("Landscape", "Right");
} break;
case orientations.PORTRAIT:
{
Nav.GridContainer.classList.add("Portrait");
} break;
window.setTimeout(ResizeFunction, 512);
}
var NewGridSize = ComputeOptimalGridSize();
if(Nav.GridSize !== NewGridSize)
else
{
Nav.GridSize = NewGridSize;
ReinitButtons();
var TargetLevel = Nav.TraversalStack[Nav.TraversalStack.length - 1];
var ProjectsStack = EmptyTraversalStackIntoProjectsStack();
DeriveTraversalStack(ProjectsStack, TargetLevel);
SetHelpKeyAvailability(Nav.GridSize)
}
UpdateButtons();
ResizeFunction();
}
};
}