mirror of
https://github.com/skishore/makemeahanzi.git
synced 2025-11-02 21:41:28 +08:00
Port character page to Meteor
This commit is contained in:
@ -1,72 +0,0 @@
|
||||
<script type="text/ng-template" id="decomposition.template">
|
||||
<div class="decomposition">
|
||||
<div class="header"><div class="value">{{node.value}}</div> -</div>
|
||||
<div class="text">{{node.label}}</div>
|
||||
<ng-include src="'decomposition.template'"
|
||||
ng-repeat="node in node.children"></ng-include>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/ng-template" id="order.template">
|
||||
<svg viewBox="0 0 1024 1024">
|
||||
<g stroke="lightgray" stroke-dasharray="1,1" stroke-width="1"
|
||||
transform="scale(4, 4)">
|
||||
<line x1="0" y1="0" x2="256" y2="256"></line>
|
||||
<line x1="256" y1="0" x2="0" y2="256"></line>
|
||||
<line x1="128" y1="0" x2="128" y2="256"></line>
|
||||
<line x1="0" y1="128" x2="256" y2="128"></line>
|
||||
</g>
|
||||
<g transform="scale(1, -1) translate(0, -900)">
|
||||
<path ng-repeat="stroke in controller.strokes"
|
||||
x-ng-attr-class="{{stroke.class}}"
|
||||
x-ng-attr-d="{{stroke.d}}"></path>
|
||||
<g ng-repeat="animation in controller.animations">
|
||||
<clipPath id="{{animation.clip}}">
|
||||
<path x-ng-attr-d="{{animation.stroke}}"></path>
|
||||
</clipPath>
|
||||
<path x-ng-attr-class="animation {{animation.class}}"
|
||||
x-ng-attr-clip-path="url(#{{animation.clip}})"
|
||||
x-ng-attr-d="{{animation.median}}"
|
||||
x-ng-attr-stroke-dasharray="
|
||||
{{animation.length}} {{animation.spacing}}"
|
||||
x-ng-attr-stroke-dashoffset="{{animation.advance}}"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</script>
|
||||
|
||||
<div id="data" ng-controller="DataController as controller"
|
||||
class="{{controller.short}} {{controller.orientation}}">
|
||||
<div class="pane" ng-if="controller.orientation === 'vertical'">
|
||||
<ng-include src="'order.template'"></ng-include>
|
||||
</div>
|
||||
<div class="pane">
|
||||
<div class="panel panel-primary metadata">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
Metadata for {{controller.character}}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="field" ng-repeat="metadata in controller.metadata">
|
||||
<label class="control-label">{{metadata.label}}</label>
|
||||
<div class="value">{{metadata.value}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-primary metadata">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
Decomposition for {{controller.character}}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<ng-include src="'decomposition.template'"
|
||||
ng-repeat="node in controller.decomposition"></ng-include>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pane" ng-if="controller.orientation === 'horizontal'">
|
||||
<ng-include src="'order.template'"></ng-include>
|
||||
</div>
|
||||
</div>
|
||||
13
makemeahanzi/client/base.js
Normal file
13
makemeahanzi/client/base.js
Normal file
@ -0,0 +1,13 @@
|
||||
// Simple helpers for interacting with reactive variables.
|
||||
|
||||
ReactiveVar.prototype.pop = function() {
|
||||
const value = this.get();
|
||||
value.pop();
|
||||
this.set(value);
|
||||
}
|
||||
|
||||
ReactiveVar.prototype.push = function(element) {
|
||||
const value = this.get();
|
||||
value.push(element);
|
||||
this.set(value);
|
||||
}
|
||||
72
makemeahanzi/client/character.html
Normal file
72
makemeahanzi/client/character.html
Normal file
@ -0,0 +1,72 @@
|
||||
<template name="decomposition">
|
||||
<div class="decomposition">
|
||||
<div class="header"><div class="value">{{value}}</div> -</div>
|
||||
<div class="text">{{label}}</div>
|
||||
{{#each children}}
|
||||
{{> decomposition this}}
|
||||
{{/each}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="order">
|
||||
<svg viewBox="0 0 1024 1024">
|
||||
<g stroke="lightgray" stroke-dasharray="1,1" stroke-width="1"
|
||||
transform="scale(4, 4)">
|
||||
<line x1="0" y1="0" x2="256" y2="256"></line>
|
||||
<line x1="256" y1="0" x2="0" y2="256"></line>
|
||||
<line x1="128" y1="0" x2="128" y2="256"></line>
|
||||
<line x1="0" y1="128" x2="256" y2="128"></line>
|
||||
</g>
|
||||
<g transform="scale(1, -1) translate(0, -900)">
|
||||
{{#each strokes}}
|
||||
<path class="{{class}}" d="{{d}}"></path>
|
||||
{{/each}}
|
||||
{{#each animations}}
|
||||
<clipPath id="{{clip}}">
|
||||
<path d="{{stroke}}"></path>
|
||||
</clipPath>
|
||||
<path class="animation {{class}}" clip-path="url(#{{clip}})"
|
||||
d="{{median}}" stroke-dasharray="{{length}} {{spacing}}"
|
||||
stroke-dashoffset="{{advance}}"></path>
|
||||
{{/each}}
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<template name="character">
|
||||
<div id="character" class="{{short}} {{orientation}}">
|
||||
{{#if vertical}}
|
||||
<div class="pane">{{> order}}</div>
|
||||
{{/if}}
|
||||
<div class="pane">
|
||||
<div class="panel panel-primary metadata">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
Metadata for {{character}}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
{{#each metadata}}
|
||||
<div class="field">
|
||||
<label class="control-label">{{format label}}</label>
|
||||
<div class="value">{{value}}</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-primary metadata">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
Decomposition for {{character}}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
{{> decomposition tree}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{#if horizontal}}
|
||||
<div class="pane">{{> order}}</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</template>
|
||||
@ -1,12 +1,20 @@
|
||||
"use strict";
|
||||
const animations = new ReactiveVar();
|
||||
const character = new ReactiveVar();
|
||||
const metadata = new ReactiveVar();
|
||||
const strokes = new ReactiveVar();
|
||||
const tree = new ReactiveVar();
|
||||
|
||||
const animate = window.requestAnimationFrame ||
|
||||
((callback) => setTimeout(callback, 1000 / 60));
|
||||
let animation = null;
|
||||
|
||||
const orientation = new ReactiveVar('horizontal');
|
||||
const short = new ReactiveVar(false);
|
||||
|
||||
// Methods used to render all the various pieces of character metadata.
|
||||
|
||||
const augmentTreeWithLabels = (node, dependencies) => {
|
||||
const value = node.value;
|
||||
if (node.type === 'compound') {
|
||||
node.label = lower(decomposition_util.ids_data[value].label);
|
||||
node.label = lower(makemeahanzi.Decomposition.ids_data[value].label);
|
||||
node.children.map((child) => augmentTreeWithLabels(child, dependencies));
|
||||
} else {
|
||||
node.label = dependencies[node.value] || '(unknown)';
|
||||
@ -14,8 +22,8 @@ const augmentTreeWithLabels = (node, dependencies) => {
|
||||
}
|
||||
|
||||
const constructTree = (row) => {
|
||||
const decomposition = row.decomposition;
|
||||
const tree = decomposition_util.convertDecompositionToTree(decomposition);
|
||||
const util = makemeahanzi.Decomposition;
|
||||
const tree = util.convertDecompositionToTree(row.decomposition);
|
||||
augmentTreeWithLabels(tree, row.dependencies);
|
||||
return tree;
|
||||
}
|
||||
@ -45,74 +53,97 @@ const lower = (string) => {
|
||||
return string[0].toLowerCase() + string.substr(1);
|
||||
}
|
||||
|
||||
const DataController = function($scope, $routeParams, $http) {
|
||||
this.character = String.fromCharCode(parseInt($routeParams.codepoint, 10));
|
||||
this.animations = [];
|
||||
this.decomposition = [];
|
||||
this.metadata = [];
|
||||
this.strokes = [];
|
||||
this._animation = null;
|
||||
|
||||
this._advanceAnimation = () => {
|
||||
if (!this._animation) {
|
||||
return;
|
||||
}
|
||||
const step = this._animation.step();
|
||||
$scope.$apply(() => {
|
||||
const num_complete = step.animations.length - (step.complete ? 0 : 1);
|
||||
for (let i = 0; i < num_complete; i++) {
|
||||
this.strokes[i].class = 'complete';
|
||||
}
|
||||
this.animations = step.animations.slice(num_complete);
|
||||
});
|
||||
if (!step.complete) {
|
||||
animate(this._advanceAnimation);
|
||||
}
|
||||
}
|
||||
|
||||
this._getCharacterData = (character, callback) => {
|
||||
const part = Math.floor(character.charCodeAt(0) / 256);
|
||||
$http.get(`data/part-${part}.txt`).then((response) => {
|
||||
const data = response.data;
|
||||
for (let row of response.data) {
|
||||
if (row.character === character) {
|
||||
return callback(row);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this._refresh = (row) => {
|
||||
const short = window.innerWidth <= 480;
|
||||
this.decomposition.push(constructTree(row));
|
||||
this.metadata = [
|
||||
{label: (short ? 'Def.' : 'Definition:'), value: row.definition},
|
||||
{label: (short ? 'Pin.' : 'Pinyin:'), value: row.pinyin.join(', ')},
|
||||
{label: (short ? 'Rad.' : 'Radical:'), value: row.radical},
|
||||
];
|
||||
const refreshMetadata = (row) => {
|
||||
const options = {delay: 0.3, speed: 0.02};
|
||||
animation = new makemeahanzi.Animation(options, row.strokes, row.medians);
|
||||
animate(advanceAnimation);
|
||||
metadata.set([
|
||||
{label: 'Definition:', value: row.definition},
|
||||
{label: 'Pinyin:', value: row.pinyin.join(', ')},
|
||||
{label: 'Radical:', value: row.radical},
|
||||
]);
|
||||
if (row.etymology) {
|
||||
this.metadata.push({
|
||||
label: (short ? 'For.' : 'Formation:'),
|
||||
metadata.push({
|
||||
label: 'Formation:',
|
||||
value: formatEtymology(row.etymology),
|
||||
});
|
||||
}
|
||||
this.strokes = row.strokes.map((d) => ({d: d, class: 'incomplete'}));;
|
||||
|
||||
const options = {delay: 0.3, speed: 0.02};
|
||||
this._animation = new Animation(options, row.strokes, row.medians);
|
||||
animate(this._advanceAnimation);
|
||||
strokes.set(row.strokes.map((d) => ({d: d, class: 'incomplete'})));
|
||||
tree.set(constructTree(row));
|
||||
}
|
||||
|
||||
this._resize = () => {
|
||||
this.short = window.innerWidth <= 480 ? 'short ' : '';
|
||||
this.orientation = window.innerWidth < window.innerHeight ?
|
||||
'vertical' : 'horizontal';
|
||||
const updateCharacter = () => {
|
||||
animation = null;
|
||||
[animations, metadata, strokes, tree].map((x) => x.set(null));
|
||||
const value = character.get();
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
const part = Math.floor(value.charCodeAt(0) / 256);
|
||||
$.get(`characters/part-${part}.txt`, (response, code) => {
|
||||
if (code !== 'success') throw new Error(code);
|
||||
const data = JSON.parse(response);
|
||||
for (let row of data) {
|
||||
if (row.character === character.get()) {
|
||||
refreshMetadata(row);
|
||||
}
|
||||
}
|
||||
|
||||
this._getCharacterData(this.character, this._refresh);
|
||||
this._resize();
|
||||
|
||||
$scope.$on('$destroy', (event) => {
|
||||
this._animation = null;
|
||||
});
|
||||
}
|
||||
|
||||
// Methods for running the stroke-order animation.
|
||||
|
||||
const animate = window.requestAnimationFrame ||
|
||||
((callback) => setTimeout(callback, 1000 / 60));
|
||||
|
||||
const advanceAnimation = () => {
|
||||
if (animation == null) {
|
||||
return;
|
||||
}
|
||||
const step = animation.step();
|
||||
const complete = step.animations.length - (step.complete ? 0 : 1);
|
||||
|
||||
if (complete > 0 && strokes.get()[complete - 1].class !== 'complete') {
|
||||
const current = strokes.get();
|
||||
for (let i = 0; i < complete ; i++) {
|
||||
current[i].class = 'complete';
|
||||
}
|
||||
strokes.set(current);
|
||||
}
|
||||
animations.set(step.animations.slice(complete));
|
||||
if (!step.complete) {
|
||||
animate(advanceAnimation);
|
||||
}
|
||||
}
|
||||
|
||||
const resize = () => {
|
||||
short.set(window.innerWidth <= 480 ? 'short ' : '');
|
||||
orientation.set(window.innerWidth < window.innerHeight ?
|
||||
'vertical' : 'horizontal');
|
||||
}
|
||||
|
||||
// Meteor template bindings.
|
||||
|
||||
Template.character.helpers({
|
||||
character: () => character.get(),
|
||||
metadata: () => metadata.get(),
|
||||
tree: () => tree.get(),
|
||||
|
||||
orientation: () => orientation.get(),
|
||||
short: () => short.get(),
|
||||
|
||||
format: (label) => (short.get() ? label.slice(0, 3) + ':' : label),
|
||||
horizontal: () => orientation.get() === 'horizontal',
|
||||
vertical: () => orientation.get() === 'vertical',
|
||||
});
|
||||
|
||||
Template.order.helpers({
|
||||
animations: () => animations.get(),
|
||||
strokes: () => strokes.get(),
|
||||
});
|
||||
|
||||
Meteor.startup(() => {
|
||||
character.set('职');
|
||||
Deps.autorun(updateCharacter)
|
||||
resize();
|
||||
});
|
||||
|
||||
@ -14,5 +14,5 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{> search}}
|
||||
{{> character}}
|
||||
</body>
|
||||
|
||||
@ -10,20 +10,6 @@ makemeahanzi.mediansPromise.then((medians) => {
|
||||
Deps.autorun(refreshCandidates);
|
||||
}).catch(console.error.bind(console));
|
||||
|
||||
// Simple helpers for interacting with reactive variables.
|
||||
|
||||
const pop = (variable) => {
|
||||
const value = variable.get();
|
||||
value.pop();
|
||||
variable.set(value);
|
||||
}
|
||||
|
||||
const push = (variable, element) => {
|
||||
const value = variable.get();
|
||||
value.push(element);
|
||||
variable.set(value);
|
||||
}
|
||||
|
||||
// Methods needed to initialize the drawing canvas.
|
||||
|
||||
const createSketch = function() {
|
||||
@ -91,8 +77,8 @@ const d = (path) => {
|
||||
const endStroke = () => {
|
||||
const new_path = d(stroke.get());
|
||||
if (new_path.length > 0) {
|
||||
push(paths, new_path);
|
||||
push(strokes, stroke.get());
|
||||
paths.push(new_path);
|
||||
strokes.push(stroke.get());
|
||||
}
|
||||
stroke.set([]);
|
||||
}
|
||||
@ -110,13 +96,13 @@ const pushPoint = (point) => {
|
||||
}
|
||||
|
||||
const refreshCandidates = () => {
|
||||
const data = strokes.get();
|
||||
candidates.set(data.length > 0 ? matcher.match(data, 8) : []);
|
||||
const value = strokes.get();
|
||||
candidates.set(value.length > 0 ? matcher.match(value, 8) : []);
|
||||
}
|
||||
|
||||
const undo = () => {
|
||||
pop(paths);
|
||||
pop(strokes);
|
||||
paths.pop();
|
||||
strokes.pop();
|
||||
stroke.set([]);
|
||||
}
|
||||
|
||||
|
||||
@ -39,7 +39,7 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
#data {
|
||||
#character {
|
||||
margin-top: 60px;
|
||||
|
||||
.pane {
|
||||
|
||||
Reference in New Issue
Block a user