Start work on client-side pipeline support

This commit is contained in:
Shaunak Kishore
2015-09-28 15:24:19 -04:00
parent 7f96d905c1
commit 3373b41676
9 changed files with 214 additions and 466 deletions

View File

@ -21,3 +21,4 @@ check
ecmascript
meteorhacks:npm
npm-container
less

View File

@ -29,6 +29,7 @@ http@1.1.1
id-map@1.0.4
jquery@1.11.4
launch-screen@1.0.4
less@2.5.0_2
livedata@1.0.15
logging@1.0.8
meteor@1.1.7

35
client/controls.js vendored
View File

@ -1,35 +0,0 @@
"use strict";
Session.setDefault('controls.show_editor', true);
Template.content.helpers({
show_editor: function() {
return Session.get('controls.show_editor');
}
});
Template.controls.events({
'click #backup-button': function() {
Meteor.call('backup');
},
'click #restore-button': function() {
Meteor.call('restore');
},
});
Template.navbar.helpers({
mode: function() {
if (Session.get('controls.show_editor')) {
return 'editor';
}
var radical = Session.get('gallery.radical');
if (radical === undefined) {
return 'all radicals';
}
return 'radical ' + radical;
},
percent: function() {
var value = Session.get('glyph.fraction_verified');
return Math.round(100*(value === undefined ? 0 : value));
},
});

View File

@ -1,44 +1,16 @@
"use strict";
Session.setDefault('glyph.data', undefined);
Session.setDefault('editor.glyph', undefined);
Session.setDefault('glyph.selected_point', undefined);
Session.setDefault('glyph.show_strokes', true);
var COLORS = ['#0074D9', '#2ECC40', '#FFDC00', '#FF4136', '#7FDBFF',
'#001F3F', '#39CCCC', '#3D9970', '#01FF70', '#FF851B'];
var DICTIONARY = 'http://www.archchinese.com/chinese_english_dictionary.html';
var EDIT_STROKES = true;
function change_glyph(method, glyph) {
glyph = glyph || Session.get('glyph.data');
Meteor.call(method, glyph, function(err, data) {
Session.set('glyph.data', data);
Session.set('glyph.show_strokes', true);
});
}
function fill_glyph_fields(glyph) {
glyph.manual = glyph.manual || {};
glyph.manual.verified = glyph.manual.verified || false;
if (EDIT_STROKES) {
glyph.render = get_glyph_render_data(glyph, glyph.manual.bridges);
} else {
glyph.render = {d: '', log: [], strokes: glyph.derived.strokes};
}
glyph.manual.bridges = glyph.manual.bridges || glyph.render.bridges;
return glyph;
}
function has_errors(glyph) {
var error = function(pair) { return pair[0] != 'success'; };
return glyph && glyph.render.log.filter(error).length > 0;
}
function refresh_fraction_verified() {
Meteor.call('get_fraction_verified', function(err, data) {
if (!err) {
Session.set('glyph.fraction_verified', data);
}
function change_glyph(method, argument) {
argument = argument || Session.get('editor.glyph');
Meteor.call(method, argument, function(err, data) {
Session.set('editor.glyph', data);
});
}
@ -76,7 +48,7 @@ function to_point(pair) {
var bindings = {
'w': function() {
if (!EDIT_STROKES) {
var glyph = Session.get('glyph.data');
var glyph = Session.get('editor.glyph');
var character = String.fromCodePoint(parseInt(glyph.name.substr(3), 16));
window.open(DICTIONARY + '?find=' + character, '_blank').focus();
return;
@ -84,13 +56,13 @@ var bindings = {
if (Session.get('glyph.show_strokes')) {
Session.set('glyph.show_strokes', false);
Session.set('glyph.selected_point', undefined);
var glyph = Session.get('glyph.data');
var glyph = Session.get('editor.glyph');
glyph.manual.verified = false;
change_glyph('save_glyph', glyph);
} else {
var glyph = Session.get('glyph.data');
var glyph = Session.get('editor.glyph');
delete glyph.manual;
Session.set('glyph.data', fill_glyph_fields(glyph));
Session.set('editor.glyph', fill_glyph_fields(glyph));
}
},
'a': function() {
@ -103,7 +75,7 @@ var bindings = {
if (!EDIT_STROKES) {
return;
}
var glyph = Session.get('glyph.data');
var glyph = Session.get('editor.glyph');
if (!Session.get('glyph.show_strokes')) {
Session.set('glyph.show_strokes', true);
return;
@ -121,13 +93,7 @@ var bindings = {
},
};
Template.controls.events({
'click #w-button': bindings.w,
'click #a-button': bindings.a,
'click #s-button': bindings.s,
'click #d-button': bindings.d,
});
/*
Template.controls.helpers({
w_button_name: function() {
if (!EDIT_STROKES) {
@ -179,50 +145,30 @@ Template.glyph.events({
Session.set('glyph.selected_point', undefined);
},
});
*/
Template.glyph.helpers({
glyph() {
return !!Session.get('glyph.data');
},
verified: function() {
return false;
if (!EDIT_STROKES) {
Template.editor.helpers({
class() {
return undefined;
}
var glyph = Session.get('glyph.data');
if (has_errors(glyph)) {
return 'error';
}
return glyph && glyph.manual.verified ? 'verified' : undefined;
},
log: function() {
return;
var glyph = Session.get('glyph.data');
return glyph ? glyph.render.log.map(function(pair) {
return {log_class: pair[0], log_message: pair[1]};
}) : [];
},
base_color() {
return Session.get('glyph.show_strokes') ? 'black' : 'gray';
},
d() {
return Session.get('glyph.data').stages.path;
},
show_strokes() {
return !!Session.get('glyph.show_strokes');
},
strokes() {
const glyph = Session.get('glyph.data');
paths() {
const glyph = Session.get('editor.glyph');
if (!glyph) return;
const result = [];
for (let i = 0; i < glyph.stages.strokes.length; i++) {
const stroke = glyph.stages.strokes[i];
result.push({color: COLORS[i % COLORS.length], stroke: stroke});
result.push({
cls: 'selectable',
d: glyph.stages.strokes[i],
fill: COLORS[i % COLORS.length],
stroke: 'black',
});
}
return result;
},
bridges: function() {
lines() {
return;
var glyph = Session.get('glyph.data');
const glyph = Session.get('editor.glyph');
if (!glyph) return;
var original = {};
for (var i = 0; i < glyph.render.bridges.length; i++) {
var bridge = glyph.render.bridges[i];
@ -237,9 +183,10 @@ Template.glyph.helpers({
}
return result;
},
points: function() {
points() {
return;
var glyph = Session.get('glyph.data');
const glyph = Session.get('editor.glyph');
if (!glyph) return;
var result = [];
for (var i = 0; i < glyph.render.endpoints.length; i++) {
var endpoint = glyph.render.endpoints[i];
@ -256,6 +203,37 @@ Template.glyph.helpers({
},
});
Template.metadata.helpers({
character() {
const glyph = Session.get('editor.glyph');
if (!glyph) return;
return glyph.character;
},
items() {
const glyph = Session.get('editor.glyph');
if (!glyph) return;
const defaults = cjklib.getCharacterData(glyph.character);
const fields = ['definition', 'pinyin', 'strokes']
return fields.map((x) => ({
field: `${x[0].toUpperCase()}${x.substr(1)}:`,
value: glyph.metadata[x] || defaults[x] || '(unknown)',
}));
},
});
Template.status.helpers({
stage() {
return 'strokes';
},
lines() {
return [
{cls: 'success', message: 'asdf'},
{cls: 'error', message: 'asdf asdf'},
{message: 'asdf asdf asdf asdf asdf asdf asdf asdf asdf asd fasd fasd fasd fas dfa sdfa sdf'},
];
},
});
Meteor.startup(function() {
$('body').on('keypress', function(e) {
var key = String.fromCharCode(e.which);
@ -263,8 +241,6 @@ Meteor.startup(function() {
bindings[key]();
}
});
if (!Session.get('glyph.data')) {
change_glyph('get_next_glyph');
}
refresh_fraction_verified();
cjklib.promise.then(() => change_glyph('get_next_glyph'))
.catch(console.error.bind(console));
});

View File

@ -1,97 +0,0 @@
"use strict";
var COLORS = ['#0074D9', '#2ECC40', '#FFDC00', '#FF4136', '#7FDBFF',
'#001F3F', '#39CCCC', '#3D9970', '#01FF70', '#FF851B'];
var DICTIONARY = 'http://www.archchinese.com/chinese_english_dictionary.html';
var subscription = undefined;
function comparison(glyph1, glyph2) {
if (glyph1.derived.strokes.length !== glyph2.derived.strokes.length) {
return glyph1.derived.strokes.length - glyph2.derived.strokes.length;
} else if (glyph1.index.radical !== glyph2.index.radical) {
return glyph1.index.radical - glyph2.index.radical;
}
return glyph1.name < glyph2.name ? -1 : 1;
}
Template.gallery.events({
'click svg.radical': function(e) {
window.location.hash = this.radical;
},
'click svg.character': function(e) {
var character = String.fromCodePoint(parseInt(this.name.substr(3), 16));
window.open(DICTIONARY + '?find=' + character, '_blank').focus();
},
});
Template.gallery.helpers({
blocks: function() {
if (!Session.get('gallery.ready')) {
return [];
}
var glyphs = Glyphs.find().fetch().sort(comparison);
var on_radicals_page = Session.get('gallery.radical') === undefined;
var last_num_strokes = -1;
var result = [];
for (var i = 0; i < glyphs.length; i++) {
var glyph = glyphs[i];
if (on_radicals_page && i > 0 &&
glyph.index.radical === glyphs[i - 1].index.radical) {
continue;
}
var strokes = [];
if (!on_radicals_page) {
for (var j = 0; j < glyph.derived.strokes.length; j++) {
var stroke = glyph.derived.strokes[j];
strokes.push({color: COLORS[j % COLORS.length], stroke: stroke});
}
}
var data = {
class: (on_radicals_page ? 'radical' : 'character'),
d: Glyphs.get_svg_path(glyph),
name: glyph.name,
radical: glyph.index.radical,
strokes: strokes
};
var num_strokes =
on_radicals_page ? glyph.derived.strokes.length : glyph.index.strokes;
if (num_strokes != last_num_strokes) {
result.push({
glyphs: [],
count: num_strokes,
show_divider: result.length > 0,
});
last_num_strokes = num_strokes;
}
result[result.length - 1].glyphs.push(data);
}
return result;
},
loading: function() {
return !Session.get('gallery.ready');
},
});
window.onhashchange = function() {
var hash = parseInt(window.location.hash.substr(1), 10);
if (isNaN(hash)) {
Session.set('gallery.radical', undefined);
} else {
Session.set('gallery.radical', hash);
}
Session.set('gallery.ready', false);
if (subscription) {
subscription.stop();
}
}
Meteor.startup(function() {
window.onhashchange();
Tracker.autorun(function() {
var radical = Session.get('gallery.radical')
subscription = Meteor.subscribe('index', radical, function() {
Session.set('gallery.ready', true);
});
});
});

View File

@ -3,17 +3,17 @@
</head>
<body>
{{> navbar}}
{{> content}}
{{> progress}}
{{> modal}}
{{> editor}}
</body>
<template name="navbar">
<div id="navbar" class="navbar navbar-default navbar-static-top">
<div class="container-fluid">
<div class="navbar-header">
<div class="navbar-brand">Hanzi decomposition - {{mode}}</div>
<div class="navbar-brand">Hanzi decomposition</div>
</div>
<div class="navbar-progress progress" style="visibility: hidden;">
<div class="progress">
<div class="progress-bar progress-bar-warning active"
role="progressbar" style="width: {{percent}}%"
aria-valuenow="{{percent}}"
@ -23,12 +23,12 @@
</div>
</template>
<template name="progress">
<div id="progress" class="modal fade" tabIndex="2"
<template name="modal">
<div id="modal" class="modal fade" tabIndex="2"
data-backdrop="static" data-keyboard="false">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">Reloading glyph data...</div>
<div class="modal-body">{{content}}</div>
<div class="modal-footer">
<div class="progress">
<div class="progress-bar progress-bar-striped active"
@ -42,91 +42,56 @@
</div>
</template>
<template name="content">
{{#if show_editor}}
{{> controls}}
{{> glyph}}
{{else}}
{{> gallery}}
{{/if}}
</template>
<template name="controls">
<div id="controls" class="btn-group" role="group">
<button id="w-button" class="btn btn-default">W: {{w_button_name}}</button>
<button id="a-button" class="btn btn-warning">A: Previous</button>
<button id="s-button" class="btn btn-success">S: {{s_button_name}}</button>
<button id="d-button" class="btn btn-info">D: Next</button>
</div>
<div id="right-controls" class="btn-group" role="group">
<button id="backup-button" class="btn btn-success">Backup</button>
<button id="restore-button" class="btn btn-info">Restore</button>
<button id="reload-button" class="btn btn-danger">Reload</button>
</div>
</template>
<template name="glyph">
<div id="glyph" class="{{verified}}">
<div class="log">
{{#each log}}
<div class="log-line {{log_class}}">{{log_message}}</div>
{{/each}}
<template name="editor">
<div id="editor" class="{{stage}}">
<div class="left-pane">
{{> metadata}}
{{> status}}
</div>
<svg viewbox="0 0 1024 1024">
<g transform="scale(1, -1) translate(0, -900)">
{{#if glyph}}
<path fill="{{base_color}}" stroke="{{base_color}}" d="{{d}}"></path>
{{#if show_strokes}}
{{#each strokes}}
<path class="stroke" fill="{{color}}"
stroke="black" d="{{stroke}}"></path>
{{#each paths}}
<path class="{{cls}}" fill="{{fill}}"
stroke="{{stroke}}" d="{{d}}"></path>
{{/each}}
{{else}}
{{#each bridges}}
<line x1={{x1}} y1={{y1}} x2={{x2}} y2={{y2}}
stroke="{{color}}" stroke-width="8"
{{#each lines}}
<line class="{{cls}}" stroke="{{stroke}}" stroke-width="8"
x1={{x1}} y1={{y1}} x2={{x2}} y2={{y2}}
data-coordinates="{{coordinates}}"></line>
{{/each}}
{{#each points}}
<circle fill="{{color}}" stroke="{{color}}" cx={{x}} cy={{y}}
r="8" style="z-index:{{z_index}}"
<circle class="{{cls}}" fill="{{fill}}" stroke="{{stroke}}"
cx={{cx}} cy={{cy}} r="8"
data-coordinates="{{coordinates}}"></circle>
{{/each}}
{{/if}}
{{/if}}
</g>
</svg>
</div>
</template>
<template name="gallery">
<div id="gallery">
{{#each blocks}}
{{#if show_divider}}
<div class="gallery-divider"></div>
{{/if}}
<div class="gallery-block">
<div class="block-header">{{count}}</div>
<div class="block-values">
{{#each glyphs}}
<svg viewbox="0 0 1024 1024"
class="{{class}}" data-name="{{name}}">
<g transform="scale(1, -1) translate(0, -900)">
<path d="{{d}}"></path>
{{#each strokes}}
<path class="stroke" fill="{{color}}"
stroke="black" d="{{stroke}}"></path>
{{/each}}
</g>
</svg>
{{/each}}
<template name="metadata">
<div class="panel panel-primary metadata">
<div class="panel-heading">
<h3 class="panel-title">Metadata for {{character}}</h3>
</div>
{{#each items}}
<div class="field">
<label class="control-label">{{field}}</label>
<span contenteditable="true">{{value}}</span>
</div>
{{/each}}
</div>
{{#if loading}}
<div id="gallery-loader">
<div class="whirly-loader"></div>
</div>
{{/if}}
</template>
<template name="status">
<div class="panel panel-primary status">
<div class="panel-heading">
<h3 class="panel-title">Edit {{stage}}</h3>
</div>
<ul class="log">
{{#each lines}}
<li class="line {{cls}}">{{message}}</li>
{{/each}}
</ul>
</div>
</template>

View File

@ -4,6 +4,7 @@ var BATCH_SIZE = 64;
var CODEPOINTS = [0x2e80, 0x2fdf];
var FONT_LOADED_PROGRESS = 0.1;
/*
Template.controls.events({
'click #reload-button': function() {
const characters_to_save =
@ -11,13 +12,13 @@ Template.controls.events({
.filter((radical) => !cjklib.gb2312[radical]));
const characters_found = new Set;
Session.set('progress.value', 0);
Session.set('modal.value', 0);
opentype.load('arphic/UKaiCN.ttf', function(err, font) {
if (err) {
console.log('Error loading font: ' + err);
return;
}
Session.set('progress.value', FONT_LOADED_PROGRESS);
Session.set('modal.value', FONT_LOADED_PROGRESS);
var glyphs_to_save = [];
for (var i = 0; i < font.glyphs.length; i++) {
@ -40,11 +41,11 @@ Template.controls.events({
function save_glyphs(glyphs, index) {
index = index || 0;
if (index >= glyphs.length) {
Session.set('progress.value', undefined);
Session.set('modal.value', undefined);
return;
}
var remainder = (1 - FONT_LOADED_PROGRESS)*index/glyphs.length;
Session.set('progress.value', remainder + FONT_LOADED_PROGRESS);
Session.set('modal.value', remainder + FONT_LOADED_PROGRESS);
var max = Math.min(index + BATCH_SIZE, glyphs.length);
var batch = [];
for (var i = index; i < max; i++) {
@ -54,28 +55,29 @@ function save_glyphs(glyphs, index) {
Meteor.setTimeout(function() { save_glyphs(glyphs, max); }, 0);
});
}
*/
Template.progress.helpers({
Template.modal.helpers({
percent: function() {
var value = Session.get('progress.value');
var value = Session.get('modal.value');
return Math.round(100*(value === undefined ? 1 : value));
},
});
Tracker.autorun(function() {
if (Session.get('progress.show')) {
$('#progress').modal({background: 'static', keyboard: false});
if (Session.get('modal.show')) {
$('#modal').modal({background: 'static', keyboard: false});
} else {
$('#progress').modal('hide');
$('#modal').modal('hide');
}
});
Tracker.autorun(function() {
var progress = Session.get('progress.value');
Session.set('progress.show', progress !== undefined);
const value = Session.get('modal.value');
Session.set('modal.show', value !== undefined);
});
Meteor.startup(function() {
Session.set('progress.show', false);
Session.set('progress.value', undefined);
Session.set('modal.show', false);
Session.set('modal.value', undefined);
});

View File

@ -1,151 +0,0 @@
.navbar {
cursor: default !important;
-webkit-user-select: none !important;
}
.navbar .navbar-brand:hover {
color: #ffffff !important;
}
.navbar .progress {
float: right;
margin-top: 21px;
margin-right: -3px;
width: 298px;
}
.btn {
outline: 0 !important;
padding: 6px 12px !important;
}
.navbar .progress, #progress .progress {
height: 18px;
margin-bottom: 0;
}
#controls, #right-controls {
position: absolute;
top: 72px;
z-index: 1;
}
#controls .btn, #right-controls .btn {
width: 100px;
}
#controls {
left: 12px;
}
#right-controls {
right: 12px;
}
#gallery, #gallery-loader, #glyph {
position: absolute;
top: 60px;
left: 0;
right: 0;
text-align: center;
}
#gallery-loader, #glyph {
bottom: 0;
}
#gallery-loader {
position: fixed !important;
top: 0;
transform: scale(1.5, 1.5);
}
#gallery-loader .whirly-loader {
position: absolute;
top: 50%;
transform: translateY(-50%);
}
#glyph .log {
position: absolute;
text-align: left;
left: 40px;
top: 76px;
}
#glyph .log .log-line {
font-size: 20px;
margin-bottom: 16px;
}
#glyph .log .log-line.error {
color: red;
}
#glyph .log .log-line.success {
color: green;
}
#glyph.error {
background-color: #fdc;
}
#glyph.verified {
background-color: #dfc;
}
#glyph svg {
height: 100%;
margin: 0 auto;
}
#gallery svg {
height: 200px;
width: 200px;
}
#gallery .gallery-block {
display: inline-block;
margin: 40px auto;
text-align: left;
}
#gallery .gallery-divider {
border-top: 1px dotted black;
margin: 0 auto;
max-width: 1200px;
}
#gallery .gallery-block .block-header {
float: left;
font-size: 60px;
line-height: 200px;
margin-right: 80px;
text-align: right;
width: 80px;
}
#gallery .gallery-block .block-values {
float: left;
width: 1020px;
}
#gallery svg {
cursor: pointer;
}
#gallery svg:hover {
background-color: #adf;
}
#gallery svg g path.stroke:hover, #glyph svg g path.stroke:hover {
cursor: pointer;
stroke: red;
stroke-width: 8;
}
#glyph svg g circle:hover, #glyph svg g line:hover {
cursor: pointer;
fill: blue;
stroke: blue;
}

86
client/style.less Normal file
View File

@ -0,0 +1,86 @@
// Bootstrap style overrides.
.panel-title {
font-size: 16px;
}
.progress {
height: 18px;
margin-bottom: 0;
}
// Styling for the 'navbar' template.
#navbar {
cursor: default !important;
-webkit-user-select: none !important;
.navbar-brand:hover {
color: white !important;
}
.progress {
float: right;
margin-top: 21px;
margin-right: -3px;
width: 298px;
}
}
// Styling for the 'editor' template.
#editor {
position: absolute;
left: 0;
right: 0;
top: 60px;
bottom: 0;
.left-pane {
float: left;
width: 680px;
.metadata, .status {
margin: 16px 16px 0 16px;
}
.metadata {
.field {
margin: 12px 0 4px;
.control-label {
margin-right: 4px;
text-align: right;
width: 96px;
}
}
}
.status {
.log {
margin-bottom: 0;
.line {
margin: 6px 0;
&.error { color: red; }
&.success { color: green; }
}
}
}
}
svg {
float: left;
height: 100%;
margin: 0 auto;
path.selectable:hover {
cursor: pointer;
stroke: blue;
stroke-width: 8;
}
circle.selectable:hover {
cursor: pointer;
fill: blue;
stroke: blue;
}
}
}