Verified Commit 4423136b authored by Camil Staps's avatar Camil Staps 🚀

A clean, modular, reusable, robuster library browser

parent 57c27f6b
......@@ -72,6 +72,3 @@ To add a library you have to add it in to the following places:
- `frontend/index.html`
Add your library to the checkboxes in the miscellaneous column.
- `frontend/src/index.php`
Add your library to `$all_libs`.
Element.prototype.documentOffsetTop = function() {
return this.offsetTop +
(this.offsetParent ? this.offsetParent.documentOffsetTop() : 0);
};
function extend(obj, def) {
for (var prop in def) {
if (!def.hasOwnProperty(prop))
continue;
if (obj.hasOwnProperty(prop))
continue;
obj[prop] = def[prop];
}
return obj;
}
Element.prototype.browser = function(opts) {
if (typeof opts === 'undefined')
opts = {};
opts = extend(opts, {
newPath: function (path) {},
newHash: function (hash) {},
newState: function () {},
getUrl: function () { return ''; },
onLoad: function (state) {},
state: {},
viewer: null
});
var root = this;
var state = opts.state;
var triggerChange = function(update) {
opts.newState();
if (typeof update != 'undefined' && !update)
return;
if (opts.viewer != null)
opts.viewer.innerHTML = '<p id="loading">Loading...</p>';
var url = opts.getUrl();
var xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
if (opts.viewer != null)
opts.viewer.innerHTML = xmlHttp.response;
opts.onLoad(state);
}
}
xmlHttp.open("GET", url, true);
xmlHttp.send(null);
}
togglers = this.getElementsByClassName('toggler');
for (var i = 0; i < togglers.length; i++) {
togglers[i].onclick = function() {
toggle(this);
};
}
items = this.getElementsByClassName('module');
for (var i = 0; i < items.length; i++) {
items[i].onclick = function(ev) {
ev.preventDefault();
ev.stopPropagation();
var old = root.getElementsByClassName('active');
for (var i = 0; i < old.length; i++)
old[i].classList.remove('active');
var e = this;
e.classList.add('active');
var path = [];
while (e != root) {
if ('name' in e.dataset)
path.unshift(e.dataset.name)
e = e.parentNode;
}
opts.newPath(path);
triggerChange();
return false;
}
}
return {
state: state,
triggerChange: triggerChange,
setPath: function(path) {
var old = root.getElementsByClassName('active');
for (var i = 0; i < old.length; i++)
old[i].classList.remove('active');
var e = root;
console.log(path);
for (var i = 0; i < path.length && 'childNodes' in e; i++) {
var children = e.childNodes;
for (var k = 0; k < children.length; k++) {
if (children[k].dataset.name == path[i]) {
if (i < path.length - 1) {
toggle(children[k]);
e = children[k].childNodes[1];
} else {
children[k].classList.add('active');
}
break;
}
}
}
},
open: function() {
opts.newHash(decodeURIComponent(window.location.hash.substring(1)));
}
};
}
......@@ -7,6 +7,7 @@
<meta name="description" content="Cloogle is the unofficial Clean language search engine"/>
<meta name="keywords" content="Clean,Clean language,Concurrent Clean,search,functions,search engine,programming language,clean platform,iTasks,cloogle,hoogle"/>
<script src="../common.js" type="text/javascript" defer="defer"></script>
<script src="../browser.js" type="text/javascript" defer="defer"></script>
<script src="view.js" type="text/javascript" defer="defer"></script>
<link rel="stylesheet" href="../common.css" type="text/css"/>
<link rel="stylesheet" href="view.css" type="text/css"/>
......@@ -15,56 +16,6 @@
<div id="sidebar">
<a href="/"><img id="logo" src="../logo.png" alt="Cloogle logo"/></a>
<h3>Library browser</h3>
<select id="select-lib">
<?php
$alllibs = [
'Clean 2.4' => [
'StdEnv',
'ArgEnv',
'Directory',
'Dynamics',
'Gast',
'Generics',
'MersenneTwister',
'StdLib',
'TCPIP',
],
'Official' => [
'GraphCopy',
'ObjectIO',
'Platform',
'Sapl',
'iTasks',
],
'Miscellaneous' => [
'CleanInotify',
'CleanPrettyPrint',
'CleanSerial',
'CleanSnappy',
'CleanTypeUnifier',
'Cloogle',
'SoccerFun',
'clean-compiler',
'clean-ide',
'libcloogle',
]
];
$selected_lib = isset($_GET['lib']) ? $_GET['lib'] : 'StdEnv';
foreach ($alllibs as $col => $libs) {
echo '<optgroup label="' . $col . '">';
foreach ($libs as $lib) {
$selected = '';
if ($lib == $selected_lib)
$selected = ' selected="selected"';
echo '<option value="' . $lib . '"' . $selected . '>' . $lib . '</option>';
}
echo '</optgroup>';
}
?>
</select>
<br/>
<label for="icl"><input id="icl" type="checkbox"/> Show implementation</label><br/>
<input id="share-button" type="button" value="Share" onclick="shareButtonClick()"/>
<hr/>
......
......@@ -46,8 +46,8 @@ function getDirsAndModules($dir) {
$ms[] = $f->getBasename('.dcl');
}
sort($ds);
sort($ms);
natcasesort($ds);
natcasesort($ms);
} catch (Exception $e) {
echo 'Failed to get directory ' . $dir;
}
......@@ -60,8 +60,8 @@ function makeBrowser($dir, $basemodule) {
$elems = getDirsAndModules($dir);
foreach ($elems['dirs'] as $d) {
echo '<div class="browser-item directory toggle-container">' .
'<span class="toggler" onclick="toggle(this)">' .
echo '<div class="browser-item directory toggle-container" data-name="' . $d . '">' .
'<span class="toggler">' .
'<span class="toggle-icon">&#x229e</span>' .
'<span class="title">' . $d . '</span></span>';
makeBrowser($dir . '/' . $d, $basemodule . $d . '.');
......@@ -70,14 +70,13 @@ function makeBrowser($dir, $basemodule) {
foreach ($elems['modules'] as $m) {
$fullm = $basemodule . $m;
echo '<div class="browser-item module" onclick="loadModule(this)" ' .
'data-module="' . $fullm . '">' . $m . '</span>' .
echo '<div class="browser-item module" data-name="' . $m . '">' . $m . '</span>' .
'</div>';
}
echo '</div>';
}
$dname = CLEANHOME . '/lib/' . $lib;
$dname = CLEANHOME . '/lib';
makeBrowser($dname, '');
<?php
define('CLEANHOME', '/opt/clean');
if (!isset($_REQUEST['lib'])) :
echo '<p>Add ?lib and ?mod.</p>';
elseif (!isset($_REQUEST['mod'])) :
if (empty($_REQUEST['mod'])) :
echo '<p>Select a module on the left.</p>';
else :
$lib = preg_replace('/[^\\w\\/\\-]/', '', $_REQUEST['lib']);
$mod = preg_replace('/[^\\w\\/\\. ]/', '', $_REQUEST['mod']);
$mod = preg_replace('/[^\\w\\/\\. -]/', '', $_REQUEST['mod']);
$mod = str_replace('.', '/', $mod);
$iclordcl = isset($_REQUEST['icl']) ? 'icl' : 'dcl';
$hl_lines = isset($_REQUEST['line']) ? escapeshellarg($_REQUEST['line']) : '';
$fname = CLEANHOME . '/lib/' . $lib . '/' . $mod . '.' . $iclordcl;
$fname = CLEANHOME . '/lib/' . $mod . '.' . $iclordcl;
$efname = escapeshellarg($fname);
system("python3 cloogle_pygments.py $efname $hl_lines");
......
......@@ -121,6 +121,10 @@ body {
font-weight: bold;
}
.browser-item.active {
font-style: italic;
}
.browser-item.directory .browser {
background-color: rgba(0, 0, 0, 0.05);
padding-left: 1em;
......
var share_button = document.getElementById('share-button');
var sidebar = null;
var viewer = null;
var libselect = null;
var icl = null;
var curmod = null;
var refresh_on_hash = true;
var line = null;
Element.prototype.documentOffsetTop = function() {
return this.offsetTop +
(this.offsetParent ? this.offsetParent.documentOffsetTop() : 0);
};
function loadModule(elem) {
if (typeof elem != 'undefined') {
line = null;
curmod = elem.dataset.module;
}
updateHash();
updateLibraryPanel();
viewer.innerHTML = '<p id="loading">Loading...</p>';
var url = 'src.php';
url += '?lib=' + libselect.value;
if (curmod != '')
url += '&mod=' + curmod;
if (icl.checked)
url += '&icl';
var hashelems = decodeURIComponent(window.location.hash.substring(1)).split(';');
for (var i in hashelems)
if (hashelems[i].substring(0,5) == 'line=')
url += '&line=' + hashelems[i].substring(5);
var xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
viewer.innerHTML = xmlHttp.response;
if (line != null) {
var l = document.getElementById('line-' + line).documentOffsetTop();
console.log(l);
document.getElementById('viewer').scrollTo(0, l - window.innerHeight/4);
}
var linenos = document.getElementsByClassName('special');
for (var i = 0; i < linenos.length; i++) {
linenos[i].onclick = function() {
selectLine(this);
}
}
document.title = curmod + " (" + libselect.value + ") - Cloogle Library Browser";
}
}
xmlHttp.open("GET", url, true);
xmlHttp.send(null);
}
function updateLibraryPanel() {
var mods = document.getElementsByClassName('module');
for (var i = 0; i < mods.length; i++)
mods[i].style.fontStyle = '';
var e = document.getElementById('sidebar');
var modpath = curmod.split('.');
for (var m = 0; m < modpath.length - 1; m++) {
var dirs = e.getElementsByClassName('directory');
for (var d = 0; d < dirs.length; d++) {
var t = dirs[d].getElementsByClassName('title')[0];
if (t.innerHTML == modpath[m]) {
e = dirs[d];
toggle(e.getElementsByClassName('toggler')[0], true);
}
}
}
var modules = e.getElementsByClassName('module');
for (var m = 0; m < modules.length; m++)
if (modules[m].innerHTML == modpath[modpath.length-1])
modules[m].style.fontStyle = 'italic';
}
function updateHash() {
var newhash = curmod;
if (icl.checked)
newhash += ';icl';
if (line != null)
newhash += ';line=' + line;
if (window.location.hash.substring(1) != encodeURIComponent(newhash)) {
refresh_on_hash = false;
window.location.hash = '#' + newhash;
restoreShareUI();
}
}
function selectLine(elem) {
if (typeof elem != 'number') {
elem = parseInt(elem.innerText);
}
if (isNaN(elem)) {
line = null;
return;
}
line = elem;
updateHash();
var hll = document.getElementsByClassName('hll');
for (var i = 0; i < hll.length; i++)
hll[i].parentNode.innerHTML = hll[i].innerHTML;
var linespan = document.getElementById('line-' + line);
linespan.innerHTML = '<span class="hll">' + linespan.innerHTML + '</span>';
}
var share_button;
function restoreShareUI() {
share_button.disabled = false;
......@@ -141,42 +22,105 @@ function shareButtonClick() {
});
}
window.onhashchange = function() {
if (!refresh_on_hash) {
refresh_on_hash = true;
} else {
var elems = decodeURIComponent(window.location.hash.substring(1)).split(';');
curmod = elems[0];
icl.checked = elems.indexOf('icl') != -1;
for (var i in elems)
if (elems[i].substring(0,5) == 'line=')
line = elems[i].substring(5);
loadModule();
}
}
window.onload = function() {
share_button = document.getElementById('share-button');
var viewer = document.getElementById('viewer');
var icl = document.getElementById('icl');
window.onload = function () {
sidebar = document.getElementById('sidebar');
viewer = document.getElementById('viewer');
libselect = document.getElementById('select-lib');
icl = document.getElementById('icl');
var selectLine = function() {
var elem = this;
if (window.innerWidth > 800) {
var height = window.innerHeight;
sidebar.style.height = (height - 20) + 'px';
viewer.style.height = height + 'px';
}
if (typeof elem != 'number')
elem = parseInt(elem.innerText);
libselect.onchange = function() {
window.location.href = '?lib=' + this.value;
if (isNaN(elem)) {
browser.state.line = null;
return;
}
browser.state.line = elem;
var hll = document.getElementsByClassName('hll');
for (var i = 0; i < hll.length; i++)
hll[i].parentNode.innerHTML = hll[i].innerHTML;
var linespan = document.getElementById('line-' + browser.state.line);
linespan.innerHTML = '<span class="hll">' + linespan.innerHTML + '</span>';
browser.triggerChange(false);
}
icl.onchange = function() {
line = null;
loadModule();
var bindLinenos = function() {
var linenos = viewer.getElementsByClassName('special');
for (var i = 0; i < linenos.length; i++)
linenos[i].onclick = selectLine;
}
restoreShareUI();
var browser = document.getElementsByClassName('browser')[0].browser({
newPath: function (path) {
this.state.mod = path.join('.');
this.state.line = null;
this.newState();
},
newHash: function (hash) {
var hashelems = hash.split(';');
this.state.mod = hashelems[0];
this.state.icl = false;
this.state.line = null;
for (var i = 1; i < hashelems.length; i++) {
if (hashelems[i] == 'icl')
icl.checked = this.state.icl = true;
else if (hashelems[i].substring(0,5) == 'line=')
this.state.line = hashelems[i].substring(5);
}
browser.setPath(this.state.mod.split('.'));
},
newState: function () {
var hash = this.state.mod;
if (this.state.icl)
hash += ';icl';
if (this.state.line != null)
hash += ';line=' + this.state.line;
document.location.hash = '#' + hash;
},
getUrl: function () {
var url = 'src.php?mod=' + this.state.mod;
if (this.state.icl)
url += '&icl';
if (this.state.line != null)
url += '&line=' + this.state.line;
return url;
},
viewer: viewer,
onLoad: function(state) {
if (state.line != null) {
var l = document.getElementById('line-' + state.line).documentOffsetTop();
viewer.scrollTo(0, l - window.innerHeight/4);
} else {
viewer.scrollTo(0, 0);
}
window.onhashchange();
bindLinenos();
restoreShareUI();
},
state: {
icl: false
}
});
browser.open();
icl.onchange = function() {
browser.state.icl = this.checked;
browser.triggerChange();
};
icl.onchange();
var sidebar = document.getElementById('sidebar');
var viewer = document.getElementById('viewer');
if (window.innerWidth > 800) {
var height = window.innerHeight;
sidebar.style.height = (height - 20) + 'px';
viewer.style.height = height + 'px';
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment