mirror of
https://github.com/skishore/makemeahanzi.git
synced 2025-11-03 05:48:23 +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 ||
|
let animation = null;
|
||||||
((callback) => setTimeout(callback, 1000 / 60));
|
|
||||||
|
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 augmentTreeWithLabels = (node, dependencies) => {
|
||||||
const value = node.value;
|
const value = node.value;
|
||||||
if (node.type === 'compound') {
|
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));
|
node.children.map((child) => augmentTreeWithLabels(child, dependencies));
|
||||||
} else {
|
} else {
|
||||||
node.label = dependencies[node.value] || '(unknown)';
|
node.label = dependencies[node.value] || '(unknown)';
|
||||||
@ -14,8 +22,8 @@ const augmentTreeWithLabels = (node, dependencies) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const constructTree = (row) => {
|
const constructTree = (row) => {
|
||||||
const decomposition = row.decomposition;
|
const util = makemeahanzi.Decomposition;
|
||||||
const tree = decomposition_util.convertDecompositionToTree(decomposition);
|
const tree = util.convertDecompositionToTree(row.decomposition);
|
||||||
augmentTreeWithLabels(tree, row.dependencies);
|
augmentTreeWithLabels(tree, row.dependencies);
|
||||||
return tree;
|
return tree;
|
||||||
}
|
}
|
||||||
@ -45,74 +53,97 @@ const lower = (string) => {
|
|||||||
return string[0].toLowerCase() + string.substr(1);
|
return string[0].toLowerCase() + string.substr(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const DataController = function($scope, $routeParams, $http) {
|
const refreshMetadata = (row) => {
|
||||||
this.character = String.fromCharCode(parseInt($routeParams.codepoint, 10));
|
const options = {delay: 0.3, speed: 0.02};
|
||||||
this.animations = [];
|
animation = new makemeahanzi.Animation(options, row.strokes, row.medians);
|
||||||
this.decomposition = [];
|
animate(advanceAnimation);
|
||||||
this.metadata = [];
|
metadata.set([
|
||||||
this.strokes = [];
|
{label: 'Definition:', value: row.definition},
|
||||||
this._animation = null;
|
{label: 'Pinyin:', value: row.pinyin.join(', ')},
|
||||||
|
{label: 'Radical:', value: row.radical},
|
||||||
this._advanceAnimation = () => {
|
]);
|
||||||
if (!this._animation) {
|
if (row.etymology) {
|
||||||
return;
|
metadata.push({
|
||||||
}
|
label: 'Formation:',
|
||||||
const step = this._animation.step();
|
value: formatEtymology(row.etymology),
|
||||||
$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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
strokes.set(row.strokes.map((d) => ({d: d, class: 'incomplete'})));
|
||||||
|
tree.set(constructTree(row));
|
||||||
|
}
|
||||||
|
|
||||||
this._refresh = (row) => {
|
const updateCharacter = () => {
|
||||||
const short = window.innerWidth <= 480;
|
animation = null;
|
||||||
this.decomposition.push(constructTree(row));
|
[animations, metadata, strokes, tree].map((x) => x.set(null));
|
||||||
this.metadata = [
|
const value = character.get();
|
||||||
{label: (short ? 'Def.' : 'Definition:'), value: row.definition},
|
if (value == null) {
|
||||||
{label: (short ? 'Pin.' : 'Pinyin:'), value: row.pinyin.join(', ')},
|
return;
|
||||||
{label: (short ? 'Rad.' : 'Radical:'), value: row.radical},
|
}
|
||||||
];
|
const part = Math.floor(value.charCodeAt(0) / 256);
|
||||||
if (row.etymology) {
|
$.get(`characters/part-${part}.txt`, (response, code) => {
|
||||||
this.metadata.push({
|
if (code !== 'success') throw new Error(code);
|
||||||
label: (short ? 'For.' : 'Formation:'),
|
const data = JSON.parse(response);
|
||||||
value: formatEtymology(row.etymology),
|
for (let row of data) {
|
||||||
});
|
if (row.character === character.get()) {
|
||||||
|
refreshMetadata(row);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._resize = () => {
|
|
||||||
this.short = window.innerWidth <= 480 ? 'short ' : '';
|
|
||||||
this.orientation = window.innerWidth < window.innerHeight ?
|
|
||||||
'vertical' : 'horizontal';
|
|
||||||
}
|
|
||||||
|
|
||||||
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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{> search}}
|
{{> character}}
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@ -10,20 +10,6 @@ makemeahanzi.mediansPromise.then((medians) => {
|
|||||||
Deps.autorun(refreshCandidates);
|
Deps.autorun(refreshCandidates);
|
||||||
}).catch(console.error.bind(console));
|
}).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.
|
// Methods needed to initialize the drawing canvas.
|
||||||
|
|
||||||
const createSketch = function() {
|
const createSketch = function() {
|
||||||
@ -91,8 +77,8 @@ const d = (path) => {
|
|||||||
const endStroke = () => {
|
const endStroke = () => {
|
||||||
const new_path = d(stroke.get());
|
const new_path = d(stroke.get());
|
||||||
if (new_path.length > 0) {
|
if (new_path.length > 0) {
|
||||||
push(paths, new_path);
|
paths.push(new_path);
|
||||||
push(strokes, stroke.get());
|
strokes.push(stroke.get());
|
||||||
}
|
}
|
||||||
stroke.set([]);
|
stroke.set([]);
|
||||||
}
|
}
|
||||||
@ -110,13 +96,13 @@ const pushPoint = (point) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const refreshCandidates = () => {
|
const refreshCandidates = () => {
|
||||||
const data = strokes.get();
|
const value = strokes.get();
|
||||||
candidates.set(data.length > 0 ? matcher.match(data, 8) : []);
|
candidates.set(value.length > 0 ? matcher.match(value, 8) : []);
|
||||||
}
|
}
|
||||||
|
|
||||||
const undo = () => {
|
const undo = () => {
|
||||||
pop(paths);
|
paths.pop();
|
||||||
pop(strokes);
|
strokes.pop();
|
||||||
stroke.set([]);
|
stroke.set([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -39,7 +39,7 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#data {
|
#character {
|
||||||
margin-top: 60px;
|
margin-top: 60px;
|
||||||
|
|
||||||
.pane {
|
.pane {
|
||||||
|
|||||||
Reference in New Issue
Block a user