@ -1 +1,2 @@ | |||
main.js | |||
style.css |
@ -1,12 +1,19 @@ | |||
SRCDIR=src | |||
SRC=$(shell find $(SRCDIR) -type f -name '*.js') | |||
JS_SRCDIR=js | |||
CSS_SRCDIR=css | |||
JS_SRC=$(shell find $(JS_SRCDIR) -type f -name '*.js') | |||
CSS_SRC=$(shell find $(CSS_SRCDIR) -type f -name '*.css') | |||
LIB=unitJS | |||
TARGET=main.js | |||
JS=main.js | |||
CSS=style.css | |||
TARGET=$(JS) $(CSS) | |||
all: $(TARGET) | |||
$(TARGET): $(SRC) | |||
sjw $(LIB:%=-I %) -o $@ $(SRCDIR) | |||
$(JS): $(JS_SRC) | |||
sjw $(LIB:%=-I %) -o $@ $(JS_SRCDIR) | |||
$(CSS): $(CSS_SRC) | |||
cat $^ > $@ | |||
mrproper: | |||
rm $(TARGET) |
@ -0,0 +1,27 @@ | |||
html, body { | |||
margin: 0; | |||
} | |||
#workzone { | |||
position: fixed; | |||
overflow: scroll; | |||
z-index: -1; | |||
left: 0; | |||
top: 3em; | |||
right: 0; | |||
bottom: 0; | |||
} | |||
#windowCatchAll { | |||
display: none; | |||
position: absolute; | |||
z-index: 1; | |||
left: 0; | |||
top: 0; | |||
right: 0; | |||
bottom: 0; | |||
} | |||
#windowCatchAll.on { | |||
display: block; | |||
} |
@ -0,0 +1,18 @@ | |||
#selector { | |||
display: none; | |||
position: fixed; | |||
border: 1px dashed #aaa; | |||
} | |||
#selector.on { | |||
display: block; | |||
} | |||
#selector, #page.freeze { | |||
cursor: crosshair; | |||
user-select: none; | |||
-moz-user-select: none; | |||
-ms-user-select: none; | |||
-webkit-touch-callout: none; | |||
-webkit-user-select: none; | |||
} |
@ -0,0 +1,19 @@ | |||
#toolbar { | |||
background: #ddd; | |||
border-top: 1px solid #eee; | |||
border-bottom: 1px solid #ccc; | |||
user-select: none; | |||
-moz-user-select: none; | |||
-ms-user-select: none; | |||
-webkit-touch-callout: none; | |||
-webkit-user-select: none; | |||
height: 3em; | |||
} | |||
#fileNumber { | |||
width: 4em; | |||
} | |||
#wcThreshold { | |||
width: 3.5em; | |||
} |
@ -0,0 +1,39 @@ | |||
.menu { | |||
cursor: default; | |||
} | |||
.menu > * { | |||
z-index: 2; | |||
} | |||
.menu span { | |||
position: relative; /* needed for z-index to work */ | |||
} | |||
.menu ul { | |||
display: none; | |||
list-style: none; | |||
padding: 0; | |||
margin: 0; | |||
position: absolute; | |||
background: #ddd; | |||
border: 1px solid #ccc; | |||
} | |||
.menu.open ul { | |||
display: block; | |||
} | |||
.menu ul li { | |||
padding: 0.2em 0.5em; | |||
} | |||
.menu.open span, .menu ul li:hover { | |||
background: #777; | |||
color: #fff; | |||
} | |||
.menu ul li.disabled { | |||
color: #888; | |||
background: #ddd; | |||
} |
@ -0,0 +1,33 @@ | |||
import * as Dom from UnitJS.Dom; | |||
return { | |||
load: load, | |||
pick: pick, | |||
save: save | |||
}; | |||
function pick(attributes) { | |||
return function(f) { | |||
attributes.type = 'file'; | |||
var input = Dom.make('input', attributes); | |||
input.addEventListener('change', function() { | |||
f(input); | |||
}); | |||
input.click(); | |||
} | |||
} | |||
function load(file) { | |||
return function(f) { | |||
var fileReader = new FileReader(); | |||
fileReader.addEventListener('load', function() { | |||
f(fileReader.result); | |||
}) | |||
fileReader.readAsText(file); | |||
}; | |||
} | |||
function save(data, name) { | |||
var a = Dom.make('a', {download: name, href: data}); | |||
a.click(); | |||
} |
@ -1,5 +1,7 @@ | |||
import * as EditMode from Toolbar.EditMode; | |||
import * as Menu from Toolbar.Menu; | |||
import * as Page from Toolbar.Page; | |||
EditMode.sync(); | |||
Page.syncNumber(); | |||
Menu.init(); |
@ -0,0 +1,38 @@ | |||
import File; | |||
var scoriae = {}; | |||
return { | |||
getScoriae: getScoriae, | |||
isScoria: isScoria, | |||
setScoria: setScoria, | |||
toggleWord: toggleWord | |||
}; | |||
function getScoriae() { | |||
var result = []; | |||
for(var id in scoriae) { | |||
result.push(id); | |||
} | |||
return result; | |||
} | |||
function isScoria(wordId) { | |||
return scoriae[wordId] || false; | |||
} | |||
function setScoria(wordId, state) { | |||
if(state == undefined || state) { | |||
scoriae[wordId] = true; | |||
} else { | |||
delete scoriae[wordId]; | |||
} | |||
} | |||
function toggleWord(domElem, forceState) { | |||
if(editMode.checked) { | |||
var state = forceState != undefined ? forceState : !scoriae[domElem.id]; | |||
setScoria(domElem.id, state); | |||
domElem.classList.toggle('deleted', state); | |||
} | |||
} |
@ -0,0 +1,42 @@ | |||
import {menus, windowCatchAll} from GUI; | |||
var selectedMenu = null; | |||
window.addEventListener('keydown', function(e) { | |||
switch(e.keyCode) { | |||
case 0x1b: closeMenu(); break; | |||
} | |||
}); | |||
windowCatchAll.addEventListener('click', closeMenu); | |||
return { | |||
init: init | |||
} | |||
function init() { | |||
for(var i = 0; i < menus.length; i++) { | |||
menus[i].querySelector('span').addEventListener('click', toggleMenu(menus[i])); | |||
menus[i].querySelector('ul').addEventListener('click', closeMenu); | |||
} | |||
} | |||
function toggleMenu(menu) { | |||
return function() { | |||
if(selectedMenu == undefined) { | |||
menu.classList.add('open'); | |||
selectedMenu = menu; | |||
windowCatchAll.className = 'on'; | |||
} else { | |||
closeMenu(); | |||
} | |||
}; | |||
} | |||
function closeMenu(e) { | |||
if(selectedMenu != undefined | |||
&& !(e != undefined && e.target.classList.contains('disabled'))) { | |||
selectedMenu.classList.remove('open'); | |||
selectedMenu = null; | |||
windowCatchAll.className = ''; | |||
} | |||
} |
@ -0,0 +1,80 @@ | |||
import File; | |||
import * as GUI from GUI; | |||
import * as Async from UnitJS.Async; | |||
import XML; | |||
import * as ALTO from XML.ALTO; | |||
import View; | |||
var files = null; | |||
GUI.loadALTO.addEventListener('click', function() { | |||
Async.run( | |||
Async.bind( | |||
File.pick({accept:"text/xml,.xml", multiple: true}), | |||
function(input) { | |||
files = input.files; | |||
GUI.fileNumber.value = 0; | |||
syncNumber(); | |||
updateNumber(); | |||
GUI.saveALTO.classList.remove('disabled'); | |||
return Async.wrap(); | |||
} | |||
) | |||
); | |||
}); | |||
GUI.saveALTO.addEventListener('click', function() { | |||
if(!GUI.saveALTO.classList.contains('disabled')) { | |||
var file = files[GUI.fileNumber.value]; | |||
Async.run( | |||
Async.bind( | |||
File.load(file), | |||
Async.map(XML.parse), | |||
Async.map(function(ALTOFile) { | |||
var payload = encodeURIComponent(XML.serialize(ALTO.edit(ALTOFile))); | |||
File.save('data:text/xml,' + payload, file.name); | |||
}) | |||
) | |||
); | |||
} | |||
}); | |||
GUI.fileNumber.addEventListener('change', updateNumber); | |||
window.addEventListener('keydown', function(e) { | |||
switch(e.keyCode) { | |||
case 0x25: moveFile(-1); break; | |||
case 0x27: moveFile(+1); break; | |||
} | |||
}); | |||
return { | |||
syncNumber: syncNumber | |||
} | |||
function syncNumber() { | |||
if(files != null) { | |||
GUI.fileNumber.max = files.length-1; | |||
GUI.fileName.textContent = files[GUI.fileNumber.value].name; | |||
} else { | |||
GUI.fileNumber.max = 0; | |||
} | |||
} | |||
function updateNumber() { | |||
if(GUI.fileNumber.checkValidity() && files != undefined) { | |||
gotoFile(GUI.fileNumber.value); | |||
} | |||
} | |||
function moveFile(delta) { | |||
var n = 1*GUI.fileNumber.value + delta; | |||
if(n >= GUI.fileNumber.min && n <= GUI.fileNumber.max) { | |||
GUI.fileNumber.value = n; | |||
gotoFile(n); | |||
} | |||
} | |||
function gotoFile(n) { | |||
GUI.fileName.textContent = files[n].name; | |||
View.open(files[n]); | |||
} |
@ -0,0 +1,65 @@ | |||
import * as ALTO from XML.ALTO; | |||
import File; | |||
import GUI; | |||
import Scoria; | |||
import * as Async from UnitJS.Async; | |||
import XML; | |||
var currentALTOFile = null; | |||
GUI.wcThreshold.addEventListener('change', refresh); | |||
GUI.loadScoria.addEventListener('click', importScoria); | |||
GUI.saveScoria.addEventListener('click', exportScoria); | |||
return { | |||
open: open | |||
}; | |||
function open(file) { | |||
Async.run( | |||
Async.bind( | |||
File.load(file), | |||
Async.map(XML.parse), | |||
Async.map(function(ALTOFile) { | |||
currentALTOFile = ALTOFile; | |||
refresh(); | |||
}) | |||
) | |||
); | |||
} | |||
function refresh() { | |||
if(currentALTOFile != undefined) { | |||
ALTO.display(currentALTOFile); | |||
} | |||
} | |||
function importScoria() { | |||
Async.run( | |||
Async.bind( | |||
File.pick({accept:"text/csv,.csv", multiple: true}), | |||
function(input) { | |||
var loaders = []; | |||
for(var i = 0; i < input.files.length; i++) { | |||
loaders.push(File.load(input.files[i])); | |||
} | |||
return Async.parallel.apply(null, loaders); | |||
}, | |||
Async.map(setFromFiles), | |||
Async.map(refresh) | |||
) | |||
); | |||
} | |||
function setFromFiles(files) { | |||
files.forEach(function(file) { | |||
file.split('\n').slice(1).forEach(function(line) { | |||
Scoria.setScoria(line); | |||
}); | |||
}); | |||
} | |||
function exportScoria() { | |||
var column = ['ID'].concat(Scoria.getScoriae()); | |||
var data = 'data:text/csv,' + encodeURIComponent(column.join('\n')); | |||
File.save(data, 'scoria.csv'); | |||
} |
@ -1,13 +0,0 @@ | |||
return { | |||
load: load | |||
}; | |||
function load(file) { | |||
return function(f) { | |||
var fileReader = new FileReader(); | |||
fileReader.addEventListener('load', function() { | |||
f(fileReader.result); | |||
}) | |||
fileReader.readAsText(file); | |||
}; | |||
} |
@ -1,36 +0,0 @@ | |||
import saveScoria from GUI; | |||
saveScoria.addEventListener('click', save); | |||
var scoriae = {}; | |||
return { | |||
isScoria: isScoria, | |||
save: save, | |||
toggleWord: toggleWord | |||
}; | |||
function isScoria(wordId) { | |||
return scoriae[wordId] || false; | |||
} | |||
function toggleWord(domElem, forceState) { | |||
if(editMode.checked) { | |||
var state = forceState != undefined ? forceState : !scoriae[domElem.id]; | |||
if(state) { | |||
scoriae[domElem.id] = true; | |||
domElem.classList.add('deleted'); | |||
} else { | |||
delete scoriae[domElem.id]; | |||
domElem.classList.remove('deleted'); | |||
} | |||
} | |||
} | |||
function save() { | |||
var column = ['ID']; | |||
for(var id in scoriae) { | |||
column.push(id); | |||
} | |||
var data = encodeURIComponent(column.join('\n')); | |||
saveScoria.href = 'data:text/csv,' + data; | |||
} |
@ -1,48 +0,0 @@ | |||
import * as GUI from GUI; | |||
import View; | |||
GUI.inputFiles.addEventListener('change', function() { | |||
GUI.fileNumber.value = 0; | |||
syncNumber(); | |||
updateNumber(); | |||
View.loadCurrentFile(); | |||
}); | |||
GUI.fileNumber.addEventListener('change', updateNumber); | |||
window.addEventListener('keydown', function(e) { | |||
switch(e.keyCode) { | |||
case 0x25: moveFile(-1); break; | |||
case 0x27: moveFile(+1); break; | |||
} | |||
}); | |||
return { | |||
syncNumber: syncNumber | |||
} | |||
function syncNumber() { | |||
if(GUI.inputFiles.files.length > 0) { | |||
GUI.fileNumber.max = GUI.inputFiles.files.length-1; | |||
GUI.fileName.textContent = GUI.inputFiles.files[GUI.fileNumber.value].name; | |||
} else { | |||
GUI.fileNumber.max = 0; | |||
} | |||
} | |||
function updateNumber() { | |||
if(GUI.fileNumber.checkValidity()) { | |||
gotoFile(GUI.fileNumber.value); | |||
} | |||
} | |||
function moveFile(delta) { | |||
var n = 1*GUI.fileNumber.value + delta; | |||
if(n >= GUI.fileNumber.min && n <= GUI.fileNumber.max) { | |||
GUI.fileNumber.value = n; | |||
gotoFile(n); | |||
} | |||
} | |||
function gotoFile(n) { | |||
GUI.fileName.textContent = GUI.inputFiles.files[n].name; | |||
View.loadCurrentFile(); | |||
} |
@ -1,31 +0,0 @@ | |||
import * as ALTO from XML.ALTO; | |||
import File; | |||
import GUI; | |||
import * as Async from UnitJS.Async; | |||
import XML; | |||
var currentALTOFile = null; | |||
wcThreshold.addEventListener('change', refresh); | |||
return { | |||
loadCurrentFile: loadCurrentFile | |||
}; | |||
function loadCurrentFile() { | |||
Async.run( | |||
Async.bind( | |||
File.load(inputFiles.files[GUI.fileNumber.value]), | |||
Async.map(XML.parse), | |||
Async.map(function(ALTOFile) { | |||
currentALTOFile = ALTOFile; | |||
refresh(); | |||
}) | |||
) | |||
); | |||
} | |||
function refresh() { | |||
if(currentALTOFile != undefined) { | |||
ALTO.display(currentALTOFile); | |||
} | |||
} |