offline-search/index.html
2021-03-27 09:47:00 -04:00

601 lines
17 KiB
HTML

<div id="root"></div>
<script type="module">
import _ from "https://dev.jspm.io/lodash";
import React from "https://dev.jspm.io/react/index.js";
import ReactDOM from "https://dev.jspm.io/react-dom/index.js";
import ReactDOMServer from "https://dev.jspm.io/react-dom/server.js";
let Util = {
ParsePassage:inRef=>
{
let indexNumeric = inRef.lastIndexOf(" ");
let partBook = inRef.substring(0, indexNumeric);
let chapterVerse = inRef.substring(indexNumeric+1).split(":");
return [partBook, chapterVerse[0], chapterVerse[1]];
}
};
let h = React.createElement;
let ElemApp = props =>
{
return h("div", null,
[
h(ElemTree, {key:"tree1", tree:App.State.Topics}),
h(ElemTree, {key:"tree2", tree:App.State.Bible}),
h(ElemItems, {key:3})
]
);
};
let ElemItems = props =>
{
if(App.State.Items.Active.length == 0)
{
return h("div", null,
h("h3", null, "no results found")
)
}
let list = App.State.Pages.All[App.State.Pages.Active].map( i=>h(ElemItem, {...i, key:i.ID} ) );
let pages = h(ElemPager, {count:App.State.Pages.All.length, active:App.State.Pages.Active}, null);
return h("div", null, [
h("h4", {key:0}, App.State.Items.Active.length+" results"),
h("div", {key:1}, pages),
h("div", {key:2}, list),
h("div", {key:3}, pages)
]);
}
let ElemItem = props =>
{
return h("div", {key:0}, [
h("div", {key:1},
[
h("span", {key:2}, props.title),
h("em", {key:3}, props.id)
]
),
h("small", {key:4}, props.bible)
])
};
let ElemPager = ({count, active}) =>
{
var children;
var blockIntro, blockAt, blockOutro;
var hitLeft, hitRight;
let renderRange = (inStart, inStop) =>
{
let output = [];
for(let i=inStart; i<=inStop; i++)
{
output.push(h("button", {key:i, onClick:e=>App.Update.Page(i)}, i+1));
}
return output;
}
let renderSpacer = () => h("span", {key:Math.random()}, "…");
blockIntro = [0, 1, 2];
blockAt = [active-1, active, active+1];
blockOutro = [count-3, count-2, count-1];
hitLeft = _.last(blockIntro) >= _.head(blockAt);
hitRight = _.last(blockAt) >= _.head(blockOutro);
if(count < 2)
{
children = [];
}
else
{
if( (hitLeft && hitRight) || count < 7)
{
children = renderRange(_.head(blockIntro), _.last(blockOutro)) /* merge all */
}
else if(!hitLeft && hitRight)
{
children = [
...renderRange(_.head(blockIntro), _.last(blockIntro)),
renderSpacer(),
...renderRange(Math.min(_.head(blockAt), _.head(blockOutro)), _.last(blockOutro)) /* merge end */
];
}
else if(hitLeft && !hitRight)
{
children = [
...renderRange(_.head(blockIntro), Math.max(_.last(blockAt), _.last(blockIntro)) ), /* merge beginning */
renderSpacer(),
...renderRange(_.head(blockOutro), _.last(blockOutro))
];
}
else if(!hitLeft && !hitRight)
{
children = [
...renderRange(_.head(blockIntro), _.last(blockIntro)),
renderSpacer(),
...renderRange(_.head(blockAt), _.last(blockAt)),
renderSpacer(),
...renderRange(_.head(blockOutro), _.last(blockOutro))
];
}
}
return h("div", {}, children);
};
let ElemTopics = props =>
{
let children = App.State.Topics.All.map( t=>
{
return h(ElemTopic, {
onClick:e=>App.Update.Topic(t),
key:t.id,
label:t.display,
active:t.active
});
});
return h("div", null, children);
};
let ElemTopic = props =>
{
let label = props.active? props.label+" !" : props.label;
return h("button", {onClick:props.onClick}, label);
};
let ElemTree = ({tree}) =>
{
let activeItems = tree.Active.map( a=>{
return h("button", {disabled:a.Parent?false:true, key:a.ID, onClick:e=>App.Update.Select(a, tree)}, a.Display )
})
return h("div", {},
[
h("h3", {key:"title"}, tree.Display),
h("div", {key:"filters-active"}, [
h("strong", {key:"label"}, "Showing:"),
h("span", {key:"list"}, activeItems)
]),
h("div", {key:"filters-browse"}, [
h("strong", {key:"label"}, "Available:"),
h(ElemTreeNode, {key:"tree", node:tree.Root, tree:tree})
])
]);
}
let ElemTreeNode = ({node, tree}) =>
{
let children = [];
if(node.Children && node.Open)
{
children = node.Children.map( c=>h(ElemTreeNode, {node:c, tree:tree, key:c.ID}));
}
let elemTitle = (inExpandable, inButton) =>
{
let attributes = null;
let parts = [h("span", null, node.Display+" ("+node.Leaves.length+") ")];
if(inExpandable)
{
attributes = {onClick:e=>{ e.stopPropagation(); App.Update.Interact(node); }};
parts.unshift(h("span", null, node.Open?"-":"+"));
}
if(inButton)
{
parts.push(
h("button",
{
onClick:e=>{ e.stopPropagation(); App.Update.Select(node, tree); }
},
node.Active?"remove":"add")
);
}
return h("div", attributes, parts);
}
var partTitle;
if(!node.Parent)
{
partTitle = elemTitle(true, false);
}
else
{
if(node.Children)
{
partTitle = elemTitle(true, true);
}
else
{
partTitle = elemTitle(false, true);
}
}
return h("div", {style:{padding:"10px"}}, [
partTitle,
h("div", {}, children)
]);
};
let Node = {
Create:(inID, inDisplay, inChildren)=>
{
if(!inDisplay)
{
inDisplay = inID;
}
let node = {
ID:inID,
Display:inDisplay,
Open:false,
Active:false,
Parent:false,
Children:inChildren,
Leaves:[]
};
if(inChildren)
{
inChildren.forEach(c=>c.Parent = node);
}
return node;
},
IterateDown:(inNode, inIterator)=>
{
if( inIterator(inNode) )
{
return true;
}
if(inNode.Children)
{
for(let i=0; i<inNode.Children.length; i++)
{
if(Node.IterateDown(inNode.Children[i], inIterator))
{
return true;
}
}
}
},
IterateUp:(inNode, inIterator)=>
{
if( inIterator(inNode) )
{
return true;
}
if(inNode.Parent)
{
Node.IterateUp(inNode.Parent, inIterator);
}
},
Path:(inNode, inList) =>
{
let node = inNode;
while(node)
{
inList.push(node);
node = node.Parent;
}
},
Leaves:(inNode, inList) =>
{
var i;
var child;
if(inNode.Children)
{
for(i=0; i<inNode.Children.length; i++)
{
Node.Decedents(inNode.Children[i], inList);
}
}
else
{
inList.push(inNode);
}
}
};
var App = {
State:
{
Items:
{
All:[],
Active:[]
},
Pages:
{
All:[],
Active:0,
},
Topics:
{
Display:"Topics",
Results:[],
Active:[],
Root:
Node.Create("root", "All", [
Node.Create("", "Unclassified"),
Node.Create("ideologies", "Ideologies", [
Node.Create("gospel-the", "The Gospel"),
Node.Create("free-will", "Free Will"),
Node.Create("secular-culture", "Secular Culture")
]),
Node.Create("people", "People", [
Node.Create("biblical-figures", "Biblical Figures"),
Node.Create("jesus-christ", "Jesus Christ"),
])
])
},
Bible:
{
Display:"Bible",
Results:[],
Active:[],
Root:
Node.Create("all", "All", [
Node.Create("", "Unclassified"),
Node.Create("old ", "Old Testament",
[
Node.Create("pent", "Pentatuch",
[
Node.Create("Genesis", "Genesis"),
Node.Create("Exodus", "Exodus"),
Node.Create("Leviticus", "Leviticus"),
Node.Create("Duteronomy", "Duteronomy"),
Node.Create("Numbers", "Numbers")
]),
Node.Create("hist", "History",
[
Node.Create("Josua", "Josua"),
Node.Create("Judges", "Judges"),
Node.Create("Ruth", "Ruth"),
Node.Create("1 Samuel", "1 Samuel"),
Node.Create("2 Samuel", "2 Samuel"),
Node.Create("1 Kings", "1 Kings"),
Node.Create("2 Kings", "2 Kings"),
Node.Create("1 Chronicles", "1 Chronicles"),
Node.Create("2 Chronicles", "2 Chronicles"),
Node.Create("Ezra", "Ezra"),
Node.Create("Nehemiah", "Nehemiah"),
Node.Create("Esther", "Esther")
]),
Node.Create("poet", "Poetry",
[
Node.Create("Job", "Job"),
Node.Create("Psalms", "Psalms"),
Node.Create("Proverbs", "Proverbs"),
Node.Create("Ecclesiastes", "Ecclesiastes"),
Node.Create("Song of Solomon", "Song of Songs"),
]),
Node.Create("majo", "Major Prophets",
[
Node.Create("Isaiah", "Isaiah"),
Node.Create("Jeremiah", "Jeremiah"),
Node.Create("Lamentations", "Lamentations"),
Node.Create("Ezekiel", "Ezekiel"),
Node.Create("Daniel", "Daniel"),
]),
Node.Create("mino", "Minor Prophets",
[
Node.Create("Hosea"),
Node.Create("Joel"),
Node.Create("Amos"),
Node.Create("Obadiah"),
Node.Create("Jonah"),
Node.Create("Micah"),
Node.Create("Nahum"),
Node.Create("Habakkuk"),
Node.Create("Zephaniah"),
Node.Create("Haggai"),
Node.Create("Zechariah"),
Node.Create("Malachi")
])
]),
Node.Create("nt", "New Testament", [
Node.Create("gospels", "The Gospels", [
Node.Create("Matthew"),
Node.Create("Mark"),
Node.Create("Luke"),
Node.Create("John"),
]),
Node.Create("Acts"),
Node.Create("Romans"),
Node.Create("1 Corinthians"),
Node.Create("2 Corinthians"),
Node.Create("Galatians"),
Node.Create("Ephesians"),
Node.Create("1 Thessalonians")
])
])
}
},
RootDOM:document.querySelector("#root"),
RootComponent:ElemApp,
Render:()=>ReactDOM.render( h(App.RootComponent), App.RootDOM ),
ApplyFilters:()=>
{
App.State.Items.Active = _.union(App.State.Topics.Results, App.State.Bible.Results);
if(App.State.Items.Active.length == 0)
{
App.State.Items.Active = App.State.Items.All;
}
App.State.Pages.All = _.chunk(App.State.Items.Active, 10);
App.State.Pages.Active = 0;
},
LeafStructure:(inTree)=>
{
let output = {};
Node.IterateDown(inTree.Root, n=>
{
if(!n.Children)
{
output[n.ID] = n;
}
});
return output;
},
Update:
{
Interact:(inNode)=>
{
if(inNode.Open)
{
Node.IterateDown(inNode, n=>{n.Open=false;});
}
else
{
Node.IterateUp(inNode, n=>{n.Open=true;});
}
App.Render();
},
Select:(inNode, inTree)=>
{
let clear = c =>
{
let index = _.indexOf(inTree.Active, c);
inTree.Active.splice( index, 1 );
c.Active = false;
};
let add = a =>
{
a.Active = true;
inTree.Active.push(a);
};
let itr = n=>
{
if(n.Active)
{
clear(n);
}
};
if(inNode.Active)
{
clear(inNode);
}
else
{
Node.IterateUp(inNode, itr);
Node.IterateDown(inNode, itr);
add(inNode);
}
inTree.Results = [];
inTree.Active.forEach(inNode =>
{
inTree.Results = inTree.Results.concat( inNode.Leaves );
});
App.ApplyFilters();
App.Render();
},
Load:(file)=>
{
fetch(file)
.then(inAccept=>inAccept.text())
.then(inAccept=>
{
let structTopics = App.LeafStructure(App.State.Topics);
let structBible = App.LeafStructure(App.State.Bible);
let itrUp = inLeaf=>
{
Node.IterateUp(inLeaf, inBranch=>
{
inBranch.Leaves = _.union(inBranch.Leaves, inLeaf.Leaves)
});
};
let columns = inAccept.split("|");
for(let i=0; i<columns.length; i+=5)
{
let title = columns[i+0];
let id = columns[i+1];
let year = columns[i+2];
let bible = columns[i+3].split("*");
let topics = columns[i+4].split("*");
let output = { title, id, year, topics, bible };
let matched = false;
topics.forEach(t=>
{
let match = structTopics[t];
if(match)
{
match.Leaves.push(output);
matched = true;
}
});
if(!matched)
{
structTopics[""].Leaves.push(output);
}
matched = false;
bible.forEach(t=>
{
let passage = Util.ParsePassage(t);
let match = structBible[ passage[0] ];
if(match)
{
match.Leaves.push(output);
matched = true;
}
});
if(!matched)
{
structBible[""].Leaves.push(output);
}
App.State.Items.All.push(output);
}
Object.values(structTopics).forEach(itrUp);
Object.values(structBible).forEach(itrUp);
console.log(App.State.Items.All.length);
})
.then(inAccept=>
{
App.ApplyFilters();
App.Render();
});
},
Topic:(topic)=>
{
if(topic.active)
{
topic.active = false;
App.State.Topics.Active = App.State.Topics.Active.filter( t=>t.id != topic.id );
}
else
{
topic.active = true;
App.State.Topics.Active.push(topic);
}
App.ApplyFilters();
App.Render();
},
Page:(page)=>
{
if(page !== false)
{
App.State.Pages.Active = page;
App.Render();
}
}
}
};
App.Update.Load("data-1.csv");
App.Update.Load("data-2.csv");
App.Update.Load("data-3.csv");
</script>