Overview

This page will discuss how panes are displayed on the screen visually. Then a high level discussion about the code that makes this happen.

Visually

If you can understand the concept of a Pane the layout will make sense.The image below is showing a single Pane named a.

single pane

The below image is displaying 2 Panes split vertically. This was accomplished by instructing the PaneService to split Pane a vertically.

two panes split vertically

The below image is displaying 3. Now we split Pane b horizontally to get Pane b and Pane c. Again we instructed the PaneService to split Pane b

3 panes split vertically first, then the b pane split horizontally

Lastly, the below image is displaying 4 Pane's. We split Pane a horizontally to get Pane a and Pane d. Again we instructed the PaneService to split Pane a

4 panes split vertically first, then the b pane split horizontally, then the a pane split horizontally

Pane Data Structure

The data structure used is the conventional Tree/Node

Note

Pane data structure is different than the Pane component.

/* pane.model.ts */
export interface Pane {
    id: string | any;
    left: Pane | any;
    right: Pane | any;
    split: string | any;
    buffer: any;
}

There are two types Panes. A branch Pane and leaf Pane.

A Simple Example

let root = {
            split: 'v',
            left: {
                id: 'a',
                buffer: {},
            },
            right: {
                id: 'b',
                buffer: {},
            }
        }

Branch Pane

A branch Pane only contains a left and right pane and a split.

In the example above, the left and right Panes are a and b with a vertical split (v is vertical).

Leaf Pane

A leaf Pane contains an id and a buffer.

In the example above the left Pane is a and the right Pane is b.

Note

A buffer a data structure holds the information about what Module to display in the Pane i.e. Chapter, Search, Modules etc...

DOM rendering

How do we render the panes in the DOM and what considerations needed to be considered?

Grid Template Area

checkout the MDN docs for 👉 grid-template-area docs. The grid-template-areas CSS property specifies named grid areas, establishing the cells in the grid and assigning them names.

grid-template-area examples

example1 {
    grid-template-areas: 
                "a a a"
                "b c c"
                "b c c";
}

example2 {
    grid-template-areas: 
                "b b a"
                "b b c"
                "b b c";
}

What we do to render the Panes in the proper position is recursively render a grid-template-area similar to the above example from the Pane tree/node data structure discussed in the section above. The constraint placed on rendering the grid-template-area is the ratio of each area must be respected.

Important

The algorithm used to render the grid-template-area is in the dynamicGrid.service.ts file. It's a recursive algorithm that starts at left leaf node and joins the left and right leafs at the branch node all the way up the tree respecting the area ratios.

A Visual Example

A single Pane grid-template-area would look like this.👇

example {
    grid-template-areas:
                "a";
}
let root = {
    id: 'a',
    buffer: {},
}

Splitting Pane a vertically grid-template-area would look like this.👇

example {
    grid-template-areas:
                "a b";
}
let root = {
            split: 'v',
            left: {
                id: 'a',
                buffer: {},
            },
            right: {
                id: 'b',
                buffer: {},
            }
        }

Splitting Pane b horizontally grid-template-area would look like this.👇

Note

See how Pane as area spans row 0 and row 1. This keeps the ratio of panes correct. The height of a is twice the size of b or c.

example {
    grid-template-areas:
                "a b"
                "a c"
}
let root = {
            split: 'v',
            left: {
                id: 'a',
                buffer: {},
            },
            right: {
                split: 'h',
                left: {
                    id: 'b',
                    buffer: {},
                }
                right: {
                    id: 'c',
                    buffer: {},
                }

            }
        }

Splitting Pane a horizontally grid-template-area would look like this.👇

Note

See how Pane as area reduces after adding Pane d

example {
    grid-template-areas:
                "a b"
                "d c"
}
let root = {
            split: 'v',
            left: {
                split: 'h'
                left: {
                    id: 'a',
                    buffer: {},
                },
                right: {
                    id: 'd',
                    buffer: {}
                }
            },
            right: {
                split: 'h',
                left: {
                    id: 'b',
                    buffer: {},
                }
                right: {
                    id: 'c',
                    buffer: {},
                }
            }
        }

HTML for grid-template-area

The html for grid-template-areas will look similar to this.


<div class="grid">
  <div style="grid-area: a;">a</div>
  <div style="grid-area: b;">b</div>
  <div style="grid-area: c;">d</div>
  <div style="grid-area: d;">d</div>
</div>
.grid {
    display: grid;
    grid-template-areas:
                "a b"
                "d c"
}

Dynamically

Dynamically we track the pane ids in a string[] and render the pane to the DOM this way. 👇

{#each panes as paneId}
            <div style="grid-area: {paneId};">
            <PaneContainer {paneId}></PaneContainer>
        </div>
{/each}

Danger

This is great but there is a constraint we must adhere to about rerendering panes in the DOM that do not need to be rerendered. For example if we delete Pane b then Pane c should fill the area of b. If the panes array changes, Pane b is deleted in this example, then Pane d is now in index 2 of the array then Pane d will be rerendered causing issues. Notably, if Pane d was displaying a bible chapter and the user had scrolled to the middle of the chapter, rerendering Pane d would then have the scroll set to 0 resulting in the first verse being displayed instead of the middle of the chapter.

Prevent DOM From Rerendering Panes

To remedy the issue of rerendering the Panes that should not be rerendered we never remove a Pand id from the panes array. Instead we track the deleted panes and conditionally choose not to display them. See below 👇

{#each panes as paneId}
    {#if !deletedPanes[paneId]}
        <div style="grid-area: {paneId};">
            <PaneContainer {paneId}></PaneContainer>
        </div>
    {/if}
{/each}

Defining Pane Height

In HTML/CSS you have to specify the height of an element otherwise the height will be sum of the height of inner html. For us since we generated the grid-template-area we know exactly the height of each Pane by adding up the number of rows the Pane id is present and dividing by the total number of rows to get the percentage of the view height.

In the code (in +page.svelte, onGridUpdate function), we determine the height of each Pane and publish the height of the Panes. Each Pane subscribes to this publish event and will update the Pane height accordingly.