mirror of
https://github.com/skishore/makemeahanzi.git
synced 2025-10-31 02:45:49 +08:00
Clean up stage persistence UI further
This commit is contained in:
@ -17,15 +17,15 @@ const changeGlyph = (method, argument) => {
|
|||||||
const constructStage = (type) => {
|
const constructStage = (type) => {
|
||||||
const glyph = Session.get('editor.glyph');
|
const glyph = Session.get('editor.glyph');
|
||||||
stage = new stages[type](glyph);
|
stage = new stages[type](glyph);
|
||||||
|
stage.refreshUI(glyph.character, glyph.metadata);
|
||||||
|
glyph.stages[stage.type] = stage.getStageOutput();
|
||||||
Session.set('editor.glyph', glyph);
|
Session.set('editor.glyph', glyph);
|
||||||
stage._type = type;
|
|
||||||
stage.refresh(glyph);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.getGlyph = (selector) => changeGlyph('getGlyph', selector);
|
this.getGlyph = (selector) => changeGlyph('getGlyph', selector);
|
||||||
|
|
||||||
const incrementStage = (amount) => {
|
const incrementStage = (amount) => {
|
||||||
const index = types.indexOf(stage._type);
|
const index = types.indexOf(stage.type);
|
||||||
if (index < 0) return;
|
if (index < 0) return;
|
||||||
const new_index = index + amount;
|
const new_index = index + amount;
|
||||||
if (new_index < 0 || new_index >= types.length) return;
|
if (new_index < 0 || new_index >= types.length) return;
|
||||||
@ -52,8 +52,9 @@ const bindings = {
|
|||||||
Template.editor.events({
|
Template.editor.events({
|
||||||
'click svg .selectable': function(event) {
|
'click svg .selectable': function(event) {
|
||||||
// We avoid the arrow function here so that this is bound to the template.
|
// We avoid the arrow function here so that this is bound to the template.
|
||||||
|
stage.handleEvent(event, this);
|
||||||
const glyph = Session.get('editor.glyph');
|
const glyph = Session.get('editor.glyph');
|
||||||
stage.handleEvent(glyph, event, this);
|
glyph.stages[stage.type] = stage.getStageOutput();
|
||||||
Session.set('editor.glyph', glyph);
|
Session.set('editor.glyph', glyph);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -78,7 +79,7 @@ Tracker.autorun(() => {
|
|||||||
types.map((x) => { if (glyph.stages[x]) last_completed_stage = x; });
|
types.map((x) => { if (glyph.stages[x]) last_completed_stage = x; });
|
||||||
constructStage(last_completed_stage);
|
constructStage(last_completed_stage);
|
||||||
}
|
}
|
||||||
stage.refresh(glyph);
|
stage.refreshUI(glyph.character, glyph.metadata);
|
||||||
last_glyph = glyph;
|
last_glyph = glyph;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,39 +1,56 @@
|
|||||||
if (this.stages !== undefined) throw new Error('Redifining stages global!');
|
if (this.stages !== undefined) throw new Error('Redifining stages global!');
|
||||||
this.stages = {};
|
this.stages = {};
|
||||||
|
|
||||||
|
// Each stage is supposed to compute a particular field for the glyph.
|
||||||
|
// It computes an initial value for this field based only on previous stages,
|
||||||
|
// then exposes a UI for manual correction of its output.
|
||||||
|
//
|
||||||
|
// NOTE: No stage methods should update the glyph. The framework will do so by
|
||||||
|
// calling getStageOutput when appropriate.
|
||||||
stages.AbstractStage = class AbstractStage {
|
stages.AbstractStage = class AbstractStage {
|
||||||
// This method should fill in this stage's field in glyph.stages. The glyph
|
// Initialize this stage's values based only off previous stages. Then, if the
|
||||||
// may already have a value for this stage set. If so, this stage's internal
|
// glyph already has a value for this stage's field and it is possible to set
|
||||||
// state should be initialized in such a way to achieve that output, if that
|
// up the internal state of this stage to achieve that value, set that state.
|
||||||
// is possible; doing so allows users to make some edits, switch to another
|
// This piece allows the user to resume editing a glyph.
|
||||||
// glyph, and then switch back and continue where they left off.
|
//
|
||||||
|
// Typically, a stage will maintain a 'this.original' variable containing the
|
||||||
|
// value without any manual edits and a 'this.adjusted' variable containing
|
||||||
|
// the value with manual edits.
|
||||||
constructor(glyph) {
|
constructor(glyph) {
|
||||||
|
// The super constructor should be passed a type, but subclass constructors
|
||||||
|
// will be passed a glyph instead, hence the variable name discrepancy.
|
||||||
|
this.type = glyph;
|
||||||
|
this.colors = ['#0074D9', '#2ECC40', '#FFDC00', '#FF4136', '#7FDBFF',
|
||||||
|
'#001F3F', '#39CCCC', '#3D9970', '#01FF70', '#FF851B'];
|
||||||
// Session variables the interface by which the stage interacts with UI:
|
// Session variables the interface by which the stage interacts with UI:
|
||||||
// - type - String type of this stage.
|
// - type - String type of this stage.
|
||||||
// - paths - list of dicts with keys in [cls, d, fill, stroke].
|
// - paths - list of dicts with keys in [cls, d, fill, stroke].
|
||||||
// - lines - list of dicts with keys in [cls, stroke, x1, y1, x2, y2].
|
// - lines - list of dicts with keys in [cls, stroke, x1, y1, x2, y2].
|
||||||
// - points - list of dicts with keys in [cls, cx, cy, fill, stroke].
|
// - points - list of dicts with keys in [cls, cx, cy, fill, stroke].
|
||||||
// - instructions - String instructions for the user
|
|
||||||
// - status - list of dicts with keys in [cls, message] to log.
|
// - status - list of dicts with keys in [cls, message] to log.
|
||||||
//
|
//
|
||||||
// The class name 'selectable' is special for paths, lines, and points.
|
// The class name 'selectable' is special for paths, lines, and points.
|
||||||
// Including this class in cls for those objects will make them interactive
|
// Including this class in cls for those objects will make them interactive
|
||||||
// and will trigger the onClick callback when they are clicked.
|
// and will trigger the onClick callback when they are clicked.
|
||||||
Session.set('stage.type', glyph);
|
Session.set('stage.type', this.type);
|
||||||
Session.set('stage.paths', undefined);
|
Session.set('stage.paths', undefined);
|
||||||
Session.set('stage.lines', undefined);
|
Session.set('stage.lines', undefined);
|
||||||
Session.set('stage.points', undefined);
|
Session.set('stage.points', undefined);
|
||||||
Session.set('stage.status', undefined);
|
Session.set('stage.status', undefined);
|
||||||
this.colors = ['#0074D9', '#2ECC40', '#FFDC00', '#FF4136', '#7FDBFF',
|
|
||||||
'#001F3F', '#39CCCC', '#3D9970', '#01FF70', '#FF851B'];
|
|
||||||
}
|
}
|
||||||
// Update the stage's internal state and possibly update this stage's field
|
// Return this stage's value based on current internal state. The default
|
||||||
// in glyph.stages based on the event.
|
// implementation works for stages that follow the 'original/adjusted'
|
||||||
handleEvent(glyph, event, template) {
|
// convention described in the constructor.
|
||||||
|
getStageOutput() {
|
||||||
|
return this.adjusted;
|
||||||
|
}
|
||||||
|
// Update the stage's internal state based on the event.
|
||||||
|
handleEvent(event, template) {
|
||||||
assert(false, 'handleEvent was not implemented!');
|
assert(false, 'handleEvent was not implemented!');
|
||||||
}
|
}
|
||||||
// Refresh the stage UI based on the current state of this stage.
|
// Refresh the stage UI based on the current state of this stage and the
|
||||||
refresh(glyph) {
|
// glyph's character and current metadata.
|
||||||
|
refreshUI(character, metadata) {
|
||||||
assert(false, 'refresh was not implemented!');
|
assert(false, 'refresh was not implemented!');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,52 +13,50 @@ stages.bridges = class BridgesStage extends stages.AbstractStage {
|
|||||||
constructor(glyph) {
|
constructor(glyph) {
|
||||||
super('bridges');
|
super('bridges');
|
||||||
const bridges = stroke_extractor.getBridges(glyph.stages.path);
|
const bridges = stroke_extractor.getBridges(glyph.stages.path);
|
||||||
this.bridges = bridges.bridges;
|
this.original = bridges.bridges;
|
||||||
this.endpoints = [];
|
this.adjusted = glyph.stages.bridges || this.original;
|
||||||
bridges.endpoints.map(
|
this.endpoints = bridges.endpoints.reduce((x, y) => x.concat(y), []);
|
||||||
(path) => this.endpoints = this.endpoints.concat(path));
|
this.path = glyph.stages.path;
|
||||||
this.selected_point = undefined;
|
this.selected_point = undefined;
|
||||||
glyph.stages.bridges = glyph.stages.bridges || this.bridges;
|
|
||||||
}
|
}
|
||||||
handleClickOnBridge(glyph, bridge) {
|
handleClickOnBridge(bridge) {
|
||||||
glyph.stages.bridges = removeBridge(glyph.stages.bridges, bridge);
|
this.adjusted = removeBridge(this.adjusted, bridge);
|
||||||
}
|
}
|
||||||
handleClickOnPoint(glyph, point) {
|
handleClickOnPoint(point) {
|
||||||
if (this.selected_point === undefined) {
|
if (this.selected_point === undefined) {
|
||||||
this.selected_point = point;
|
this.selected_point = point;
|
||||||
this.refresh(glyph);
|
this.refreshUI();
|
||||||
return;
|
return;
|
||||||
} else if (Point.equal(point, this.selected_point)) {
|
} else if (Point.equal(point, this.selected_point)) {
|
||||||
this.selected_point = undefined;
|
this.selected_point = undefined;
|
||||||
this.refresh(glyph);
|
this.refreshUI();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const bridge = [point, this.selected_point];
|
const bridge = [point, this.selected_point];
|
||||||
this.selected_point = undefined;
|
this.selected_point = undefined;
|
||||||
const without = removeBridge(glyph.stages.bridges, bridge);
|
const without = removeBridge(this.adjusted, bridge);
|
||||||
if (without.length < glyph.stages.bridges.length) {
|
if (without.length < this.adjusted.length) {
|
||||||
this.refresh(glyph);
|
this.refreshUI();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
glyph.stages.bridges.push(bridge);
|
this.adjusted.push(bridge);
|
||||||
}
|
}
|
||||||
handleEvent(glyph, event, template) {
|
handleEvent(event, template) {
|
||||||
if (template.x1 !== undefined) {
|
if (template.x1 !== undefined) {
|
||||||
this.handleClickOnBridge(
|
const bridge = [[template.x1, template.y1], [template.x2, template.y2]];
|
||||||
glyph, [[template.x1, template.y1], [template.x2, template.y2]]);
|
this.handleClickOnBridge(bridge);
|
||||||
} else if (template.cx !== undefined) {
|
} else if (template.cx !== undefined) {
|
||||||
this.handleClickOnPoint(glyph, [template.cx, template.cy]);
|
this.handleClickOnPoint([template.cx, template.cy]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
refresh(glyph) {
|
refreshUI() {
|
||||||
Session.set('stage.paths',
|
Session.set('stage.paths', [{d: this.path, fill: 'gray', stroke: 'gray'}]);
|
||||||
[{d: glyph.stages.path, fill: 'gray', stroke: 'gray'}]);
|
|
||||||
const keys = {};
|
const keys = {};
|
||||||
this.bridges.map((bridge) => {
|
this.original.map((bridge) => {
|
||||||
keys[bridgeKey(bridge)] = true;
|
keys[bridgeKey(bridge)] = true;
|
||||||
keys[bridgeKey(bridge.reverse())] = true;
|
keys[bridgeKey(bridge.reverse())] = true;
|
||||||
});
|
});
|
||||||
Session.set('stage.lines', glyph.stages.bridges.map((bridge) => ({
|
Session.set('stage.lines', this.adjusted.map((bridge) => ({
|
||||||
cls: 'selectable',
|
cls: 'selectable',
|
||||||
stroke: keys[bridgeKey(bridge)] ? 'red' : 'purple',
|
stroke: keys[bridgeKey(bridge)] ? 'red' : 'purple',
|
||||||
x1: bridge[0][0],
|
x1: bridge[0][0],
|
||||||
@ -80,8 +78,7 @@ stages.bridges = class BridgesStage extends stages.AbstractStage {
|
|||||||
stroke: color,
|
stroke: color,
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
const strokes = stroke_extractor.getStrokes(
|
const strokes = stroke_extractor.getStrokes(this.path, this.adjusted);
|
||||||
glyph.stages.path, glyph.stages.bridges);
|
|
||||||
const n = strokes.strokes.length;
|
const n = strokes.strokes.length;
|
||||||
const message = `Extracted ${n} stroke${n == 1 ? '' : 's'}.`;
|
const message = `Extracted ${n} stroke${n == 1 ? '' : 's'}.`;
|
||||||
Session.set('stage.status', strokes.log.concat([{message: message}]));
|
Session.set('stage.status', strokes.log.concat([{message: message}]));
|
||||||
|
|||||||
@ -3,9 +3,10 @@
|
|||||||
stages.path = class PathStage extends stages.AbstractStage {
|
stages.path = class PathStage extends stages.AbstractStage {
|
||||||
constructor(glyph) {
|
constructor(glyph) {
|
||||||
super('path');
|
super('path');
|
||||||
|
this.adjusted = glyph.stages.path;
|
||||||
}
|
}
|
||||||
refresh(glyph) {
|
refreshUI() {
|
||||||
const d = glyph.stages.path;
|
const d = this.adjusted;
|
||||||
Session.set('stage.paths', [{d: d, fill: 'gray', stroke: 'gray'}]);
|
Session.set('stage.paths', [{d: d, fill: 'gray', stroke: 'gray'}]);
|
||||||
Session.set('stage.status',
|
Session.set('stage.status',
|
||||||
d ? [] : [{cls: 'error', message: 'No path data.'}]);
|
d ? [] : [{cls: 'error', message: 'No path data.'}]);
|
||||||
|
|||||||
@ -24,28 +24,27 @@ stages.strokes = class StrokesStage extends stages.AbstractStage {
|
|||||||
constructor(glyph) {
|
constructor(glyph) {
|
||||||
super('strokes');
|
super('strokes');
|
||||||
const include = this.include = {};
|
const include = this.include = {};
|
||||||
this.strokes = stroke_extractor.getStrokes(
|
this.original = stroke_extractor.getStrokes(
|
||||||
glyph.stages.path, glyph.stages.bridges).strokes;
|
glyph.stages.path, glyph.stages.bridges).strokes;
|
||||||
this.strokes.map((stroke) => include[stroke] = true);
|
this.original.map((x) => this.include[x] = true);
|
||||||
if (glyph.stages.strokes && glyph.stages.strokes.length > 0 &&
|
if (glyph.stages.strokes &&
|
||||||
glyph.stages.strokes.filter((x) => !include[x]).length === 0) {
|
glyph.stages.strokes.filter((x) => !include[x]).length === 0) {
|
||||||
this.strokes.map((stroke) => include[stroke] = false);
|
this.original.map((x) => this.include[x] = false);
|
||||||
glyph.stages.strokes.map((stroke) => include[stroke] = true);
|
glyph.stages.strokes.map((x) => include[x] = true);
|
||||||
}
|
}
|
||||||
glyph.stages.strokes = this.strokes.filter((x) => this.include[x]);
|
this.adjusted = this.original.filter((x) => this.include[x]);
|
||||||
}
|
}
|
||||||
handleEvent(glyph, event, template) {
|
handleEvent(event, template) {
|
||||||
assert(this.include.hasOwnProperty(template.d));
|
assert(this.include.hasOwnProperty(template.d));
|
||||||
this.include[template.d] = !this.include[template.d];
|
this.include[template.d] = !this.include[template.d];
|
||||||
glyph.stages.strokes = this.strokes.filter((x) => this.include[x]);
|
this.adjusted = this.original.filter((x) => this.include[x]);
|
||||||
Session.set('editor.glyph', glyph);
|
|
||||||
}
|
}
|
||||||
refresh(glyph) {
|
refreshUI(character, metadata) {
|
||||||
Session.set('stage.paths',
|
Session.set('stage.paths',
|
||||||
getStrokePaths(this.strokes, this.include, this.colors));
|
getStrokePaths(this.original, this.include, this.colors));
|
||||||
const data = cjklib.getCharacterData(glyph.character);
|
const data = cjklib.getCharacterData(character);
|
||||||
const actual = glyph.stages.strokes.length;
|
const actual = this.adjusted.length;
|
||||||
const expected = glyph.metadata.strokes || data.strokes;
|
const expected = metadata.strokes || data.strokes;
|
||||||
Session.set('stage.status', [getStatusLine(actual, expected)]);
|
Session.set('stage.status', [getStatusLine(actual, expected)]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user