more work on gamebook
This commit is contained in:
parent
cc803359dd
commit
72789d3c28
|
@ -9,14 +9,13 @@
|
||||||
font-size: 1.3em;
|
font-size: 1.3em;
|
||||||
}
|
}
|
||||||
.gb_play .comment {
|
.gb_play .comment {
|
||||||
flex: 1 1 auto;
|
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
position: relative;
|
position: relative;
|
||||||
border: 1px solid #aaa;
|
border: 1px solid #aaa;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
.gb_play .comment:after {
|
.gb_play .comment::after {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
content: '';
|
content: '';
|
||||||
bottom: -9px;
|
bottom: -9px;
|
||||||
|
@ -34,24 +33,51 @@
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
}
|
}
|
||||||
|
@keyframes mascot {
|
||||||
|
50% { transform: scale(1.03); }
|
||||||
|
100% { transform: scale(1); }
|
||||||
|
}
|
||||||
.gb_play .mascot {
|
.gb_play .mascot {
|
||||||
margin: 8px 0 -7px 0;
|
margin: 10px 0 -7px 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.gb_play .soapbox {
|
.gb_play .mascot:hover {
|
||||||
|
animation: mascot 0.5s ease-in-out;
|
||||||
|
}
|
||||||
|
.gb_play .feedback {
|
||||||
|
flex: 0 0 120px;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
height: 120px;
|
height: 120px;
|
||||||
background: #e0e0e0;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
}
|
|
||||||
.gb_play .turn {
|
|
||||||
text-transform: uppercase;
|
|
||||||
line-height: 120px;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
color: #3893E8;
|
background: #e0e0e0;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.gb_play .feedback.act {
|
||||||
|
font-size: 2em;
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0.8;
|
||||||
|
transition: 0.13s;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
.gb_play .feedback.act:hover {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1.02, 1.02);
|
||||||
|
}
|
||||||
|
.gb_play .feedback.act span {
|
||||||
|
animation: rubberBand 8s infinite;
|
||||||
|
}
|
||||||
|
.gb_play .feedback.bad {
|
||||||
|
background: #dc322f;
|
||||||
|
}
|
||||||
|
.gb_play .feedback.good,
|
||||||
|
.gb_play .feedback.end {
|
||||||
|
background: #639B24;
|
||||||
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gb_buttons .button {
|
.gb_buttons .button {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -250,7 +250,7 @@ export default class AnalyseCtrl {
|
||||||
color = this.turnColor(),
|
color = this.turnColor(),
|
||||||
dests = chessUtil.readDests(this.node.dests),
|
dests = chessUtil.readDests(this.node.dests),
|
||||||
drops = chessUtil.readDrops(this.node.drops),
|
drops = chessUtil.readDrops(this.node.drops),
|
||||||
movableColor = this.practice ? this.bottomColor() : (
|
movableColor = (this.practice || this.gamebookPlay()) ? this.bottomColor() : (
|
||||||
!this.embed && (
|
!this.embed && (
|
||||||
(dests && Object.keys(dests).length > 0) ||
|
(dests && Object.keys(dests).length > 0) ||
|
||||||
drops === null || drops.length
|
drops === null || drops.length
|
||||||
|
@ -275,7 +275,7 @@ export default class AnalyseCtrl {
|
||||||
config.movable!.color = color;
|
config.movable!.color = color;
|
||||||
}
|
}
|
||||||
config.premovable = {
|
config.premovable = {
|
||||||
enabled: config.movable!.color && config.turnColor !== config.movable!.color
|
enabled: config.movable!.color && config.turnColor !== config.movable!.color && !this.gamebookPlay()
|
||||||
};
|
};
|
||||||
this.cgConfig = config;
|
this.cgConfig = config;
|
||||||
return config;
|
return config;
|
||||||
|
|
|
@ -108,7 +108,7 @@ export function make(root: AnalyseCtrl, playableDepth: () => number): PracticeCt
|
||||||
} else {
|
} else {
|
||||||
comment(null);
|
comment(null);
|
||||||
if (node.san && commentable(node)) {
|
if (node.san && commentable(node)) {
|
||||||
const parentNode = root.tree.nodeAtPath(treePath.init(root.path));
|
const parentNode = root.tree.parentNode(root.path);
|
||||||
if (commentable(parentNode, +1))
|
if (commentable(parentNode, +1))
|
||||||
comment(makeComment(parentNode, node, root.path));
|
comment(makeComment(parentNode, node, root.path));
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,8 @@ export interface RetroCtrl {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Feedback = 'find' | 'eval' | 'win' | 'fail' | 'view';
|
||||||
|
|
||||||
export function make(root: AnalyseCtrl): RetroCtrl {
|
export function make(root: AnalyseCtrl): RetroCtrl {
|
||||||
|
|
||||||
const game = root.data.game;
|
const game = root.data.game;
|
||||||
|
@ -17,7 +19,7 @@ export function make(root: AnalyseCtrl): RetroCtrl {
|
||||||
const explorerCancelPlies: number[] = [];
|
const explorerCancelPlies: number[] = [];
|
||||||
let solvedPlies: number[] = [];
|
let solvedPlies: number[] = [];
|
||||||
const current = prop<any>(null);
|
const current = prop<any>(null);
|
||||||
const feedback = prop('find'); // find | eval | win | fail | view
|
const feedback = prop<Feedback>('find');
|
||||||
|
|
||||||
const contains = window.lichess.fp.contains;
|
const contains = window.lichess.fp.contains;
|
||||||
const redraw = root.redraw;
|
const redraw = root.redraw;
|
||||||
|
|
|
@ -16,7 +16,7 @@ export function render(ctrl: AnalyseCtrl): VNode {
|
||||||
const study = ctrl.study!,
|
const study = ctrl.study!,
|
||||||
isMyMove = ctrl.turnColor() === ctrl.data.orientation,
|
isMyMove = ctrl.turnColor() === ctrl.data.orientation,
|
||||||
isCommented = !!(ctrl.node.comments || []).find(c => c.text.length > 2),
|
isCommented = !!(ctrl.node.comments || []).find(c => c.text.length > 2),
|
||||||
hasVariation = ctrl.tree.nodeAtPath(treePath.init(ctrl.path)).children.length > 1;
|
hasVariation = ctrl.tree.parentNode(ctrl.path).children.length > 1;
|
||||||
|
|
||||||
let content: MaybeVNodes;
|
let content: MaybeVNodes;
|
||||||
|
|
||||||
|
|
|
@ -1,26 +1,74 @@
|
||||||
import AnalyseCtrl from '../../ctrl';
|
import AnalyseCtrl from '../../ctrl';
|
||||||
import { StudyCtrl } from '../interfaces';
|
import { StudyCtrl } from '../interfaces';
|
||||||
import { readOnlyProp } from '../../util';
|
import { readOnlyProp } from '../../util';
|
||||||
|
import { path as treePath, ops as treeOps } from 'tree';
|
||||||
import Mascot from './mascot';
|
import Mascot from './mascot';
|
||||||
|
|
||||||
|
type Feedback = 'play' | 'good' | 'bad' | 'end';
|
||||||
|
|
||||||
|
export interface State {
|
||||||
|
feedback: Feedback;
|
||||||
|
comment?: string;
|
||||||
|
hint?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export default class GamebookPlayCtrl {
|
export default class GamebookPlayCtrl {
|
||||||
|
|
||||||
ply: Ply;
|
|
||||||
mascot = new Mascot();
|
mascot = new Mascot();
|
||||||
|
state: State;
|
||||||
|
|
||||||
constructor(readonly root: AnalyseCtrl, readonly chapterId: string, readonly redraw: () => void) {
|
constructor(readonly root: AnalyseCtrl, readonly chapterId: string, readonly redraw: () => void) {
|
||||||
this.ply = this.root.node.ply;
|
|
||||||
// root.showAutoShapes = readOnlyProp(true);
|
this.makeState();
|
||||||
// root.showGauge = readOnlyProp(true);
|
|
||||||
// root.showComputer = readOnlyProp(true);
|
// ensure all original nodes have a gamebook entry,
|
||||||
// goal(root.data.practiceGoal!);
|
// so we can differentiate original nodes from user-made ones
|
||||||
// nbMoves(0);
|
treeOps.updateAll(root.tree.root, n => n.gamebook = n.gamebook || {});
|
||||||
// success(null);
|
|
||||||
// comment(makeComment(root.tree.root));
|
|
||||||
// const chapter = studyData.chapter;
|
|
||||||
// history.replaceState(null, chapter.name, data.url + '/' + chapter.id);
|
|
||||||
// analysisUrl('/analysis/standard/' + root.node.fen.replace(/ /g, '_') + '?color=' + root.bottomColor());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private makeState(): void {
|
||||||
|
const node = this.root.node,
|
||||||
|
nodeComment = (node.comments || [])[0],
|
||||||
|
state: Partial<State> = {
|
||||||
|
comment: nodeComment ? nodeComment.text : undefined
|
||||||
|
},
|
||||||
|
parPath = treePath.init(this.root.path),
|
||||||
|
parNode = this.root.tree.nodeAtPath(parPath);
|
||||||
|
if (!this.root.onMainline && !this.root.tree.pathIsMainline(parPath)) return;
|
||||||
|
if (this.root.turnColor() === this.root.data.orientation) {
|
||||||
|
state.feedback = 'play';
|
||||||
|
state.hint = (node.gamebook || {}).hint;
|
||||||
|
} else if (this.root.onMainline) {
|
||||||
|
if (node.children[0]) {
|
||||||
|
state.feedback = 'good';
|
||||||
|
} else {
|
||||||
|
state.feedback = 'end';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
state.feedback = 'bad';
|
||||||
|
if (!state.comment) {
|
||||||
|
state.comment = parNode.gamebook!.deviation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.state = state as State;
|
||||||
|
}
|
||||||
|
|
||||||
|
retry = () => {
|
||||||
|
let path = this.root.path;
|
||||||
|
while (path && !this.root.tree.pathIsMainline(path)) path = treePath.init(path);
|
||||||
|
this.root.userJump(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
next = () => {
|
||||||
|
const child = this.root.node.children[0];
|
||||||
|
if (child) this.root.userJump(this.root.path + child.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
canJumpTo = (path: Tree.Path) => treePath.contains(this.root.path, path);
|
||||||
|
|
||||||
|
onJump = () => {
|
||||||
|
this.makeState();
|
||||||
|
};
|
||||||
|
|
||||||
private study = (): StudyCtrl => this.root.study!;
|
private study = (): StudyCtrl => this.root.study!;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,34 +4,64 @@ import GamebookPlayCtrl from './gamebookPlayCtrl';
|
||||||
// import AnalyseCtrl from '../../ctrl';
|
// import AnalyseCtrl from '../../ctrl';
|
||||||
import { bind, dataIcon, innerHTML } from '../../util';
|
import { bind, dataIcon, innerHTML } from '../../util';
|
||||||
import { enrichText } from '../studyComments';
|
import { enrichText } from '../studyComments';
|
||||||
|
import { State } from './gamebookPlayCtrl';
|
||||||
// import { MaybeVNodes } from '../../interfaces';
|
// import { MaybeVNodes } from '../../interfaces';
|
||||||
// import { throttle } from 'common';
|
// import { throttle } from 'common';
|
||||||
|
|
||||||
|
const defaultComments = {
|
||||||
|
play: 'What would you play in this position?',
|
||||||
|
good: 'Yes indeed, good move!',
|
||||||
|
bad: 'This is not the move you are looking for.',
|
||||||
|
end: 'And that is all she wrote.'
|
||||||
|
};
|
||||||
|
|
||||||
export function render(ctrl: GamebookPlayCtrl): VNode {
|
export function render(ctrl: GamebookPlayCtrl): VNode {
|
||||||
|
|
||||||
const root = ctrl.root,
|
const root = ctrl.root,
|
||||||
node: Tree.Node = root.node,
|
state = ctrl.state;
|
||||||
gb: Tree.Gamebook = node.gamebook || {},
|
// state.feedback = 'bad';
|
||||||
isMyMove = root.turnColor() === root.data.orientation,
|
|
||||||
comment = (root.node.comments || [])[0];
|
const comment = state.comment || defaultComments[state.feedback],
|
||||||
|
isMyMove = ['good', 'bad'].indexOf(state.feedback) > -1;
|
||||||
|
|
||||||
return h('div.gamebook', {
|
return h('div.gamebook', {
|
||||||
hook: { insert: _ => window.lichess.loadCss('/assets/stylesheets/gamebook.play.css') }
|
hook: { insert: _ => window.lichess.loadCss('/assets/stylesheets/gamebook.play.css') }
|
||||||
}, [
|
}, [
|
||||||
h('div.comment', h('div.content', {
|
h('div.comment', h('div.content', {
|
||||||
hook: comment && innerHTML(comment.text, text => enrichText(text, true))
|
hook: innerHTML(comment, text => enrichText(text, true))
|
||||||
})),
|
})),
|
||||||
h('img.mascot', {
|
h('img.mascot', {
|
||||||
attrs: {
|
attrs: {
|
||||||
width: 120,
|
width: 120,
|
||||||
height: 120,
|
height: 120,
|
||||||
src: window.lichess.assetUrl(`/assets/images/mascot/${ctrl.mascot.current}.svg`),
|
src: ctrl.mascot.url(),
|
||||||
title: 'Click to choose your teacher'
|
title: 'Click to choose your teacher'
|
||||||
},
|
},
|
||||||
hook: bind('click', ctrl.mascot.switch, ctrl.redraw)
|
hook: bind('click', ctrl.mascot.switch, ctrl.redraw)
|
||||||
}),
|
}),
|
||||||
h('div.soapbox', [
|
renderFeedback(ctrl, state)
|
||||||
h('div.turn', isMyMove ? 'Your turn' : 'Opponent turn')
|
|
||||||
])
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderFeedback(ctrl: GamebookPlayCtrl, state: State) {
|
||||||
|
const fb = state.feedback;
|
||||||
|
if (fb === 'bad') return h('div.feedback.act.bad', {
|
||||||
|
hook: bind('click', ctrl.retry, ctrl.redraw)
|
||||||
|
}, [
|
||||||
|
h('i', { attrs: dataIcon('P') }),
|
||||||
|
h('span', 'Retry')
|
||||||
|
]);
|
||||||
|
if (fb === 'good') return h('div.feedback.act.good', {
|
||||||
|
hook: bind('click', ctrl.next, ctrl.redraw)
|
||||||
|
}, [
|
||||||
|
h('i', { attrs: dataIcon('G') }),
|
||||||
|
h('span', 'Next')
|
||||||
|
]);
|
||||||
|
if (fb === 'end') return h('div.feedback.end', [
|
||||||
|
h('i', { attrs: dataIcon('E') }),
|
||||||
|
h('span', 'Gamebook complete')
|
||||||
|
]);
|
||||||
|
return h('div.feedback.' + fb,
|
||||||
|
h('span', fb === 'play' ? 'Your turn' : 'Opponent turn')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -16,4 +16,6 @@ export default class Mascot {
|
||||||
this.current = this.list[newIndex % this.list.length];
|
this.current = this.list[newIndex % this.list.length];
|
||||||
this.storage.set(this.current);
|
this.storage.set(this.current);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
url = () => window.lichess.assetUrl(`/assets/images/mascot/${this.current}.svg`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,8 +99,12 @@ export default function(data: StudyData, ctrl: AnalyseCtrl, tagTypes: TagTypes,
|
||||||
req.ch = vm.chapterId;
|
req.ch = vm.chapterId;
|
||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
// remain on initial position if gamebook player
|
|
||||||
if (vm.mode.sticky && !(data.chapter.gamebook && !members.canContribute())) ctrl.userJump(data.position.path);
|
function isGamebookPlay() {
|
||||||
|
return data.chapter.gamebook && !members.canContribute();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vm.mode.sticky && !isGamebookPlay()) ctrl.userJump(data.position.path);
|
||||||
|
|
||||||
function configureAnalysis() {
|
function configureAnalysis() {
|
||||||
if (ctrl.embed) return;
|
if (ctrl.embed) return;
|
||||||
|
@ -109,7 +113,7 @@ export default function(data: StudyData, ctrl: AnalyseCtrl, tagTypes: TagTypes,
|
||||||
vm.mode.write = vm.mode.write && canContribute;
|
vm.mode.write = vm.mode.write && canContribute;
|
||||||
li.pubsub.emit('chat.writeable')(data.features.chat);
|
li.pubsub.emit('chat.writeable')(data.features.chat);
|
||||||
li.pubsub.emit('chat.permissions')({local: canContribute});
|
li.pubsub.emit('chat.permissions')({local: canContribute});
|
||||||
const computer: boolean = !!(data.chapter.features.computer || data.chapter.practice);
|
const computer: boolean = !isGamebookPlay() && !!(data.chapter.features.computer || data.chapter.practice);
|
||||||
if (!computer) ctrl.getCeval().enabled(false);
|
if (!computer) ctrl.getCeval().enabled(false);
|
||||||
ctrl.getCeval().allowed(computer);
|
ctrl.getCeval().allowed(computer);
|
||||||
if (!data.chapter.features.explorer) ctrl.explorer.disable();
|
if (!data.chapter.features.explorer) ctrl.explorer.disable();
|
||||||
|
@ -144,6 +148,8 @@ export default function(data: StudyData, ctrl: AnalyseCtrl, tagTypes: TagTypes,
|
||||||
configureAnalysis();
|
configureAnalysis();
|
||||||
vm.loading = false;
|
vm.loading = false;
|
||||||
|
|
||||||
|
instanciateGamebookPlay();
|
||||||
|
|
||||||
let nextPath: Tree.Path;
|
let nextPath: Tree.Path;
|
||||||
|
|
||||||
if (vm.mode.sticky) {
|
if (vm.mode.sticky) {
|
||||||
|
@ -194,7 +200,7 @@ export default function(data: StudyData, ctrl: AnalyseCtrl, tagTypes: TagTypes,
|
||||||
let gamebookPlay: GamebookPlayCtrl | undefined;
|
let gamebookPlay: GamebookPlayCtrl | undefined;
|
||||||
|
|
||||||
function instanciateGamebookPlay() {
|
function instanciateGamebookPlay() {
|
||||||
if (!data.chapter.gamebook || members.canContribute()) return gamebookPlay = undefined;
|
if (!isGamebookPlay()) return gamebookPlay = undefined;
|
||||||
if (gamebookPlay && gamebookPlay.chapterId === vm.chapterId) return;
|
if (gamebookPlay && gamebookPlay.chapterId === vm.chapterId) return;
|
||||||
gamebookPlay = new GamebookPlayCtrl(ctrl, vm.chapterId, redraw);
|
gamebookPlay = new GamebookPlayCtrl(ctrl, vm.chapterId, redraw);
|
||||||
vm.mode.sticky = false;
|
vm.mode.sticky = false;
|
||||||
|
@ -243,6 +249,7 @@ export default function(data: StudyData, ctrl: AnalyseCtrl, tagTypes: TagTypes,
|
||||||
currentChapter,
|
currentChapter,
|
||||||
isChapterOwner,
|
isChapterOwner,
|
||||||
canJumpTo(path: Tree.Path) {
|
canJumpTo(path: Tree.Path) {
|
||||||
|
if (gamebookPlay) return gamebookPlay.canJumpTo(path);
|
||||||
return data.chapter.conceal === undefined ||
|
return data.chapter.conceal === undefined ||
|
||||||
isChapterOwner() ||
|
isChapterOwner() ||
|
||||||
treePath.contains(ctrl.path, path) || // can always go back
|
treePath.contains(ctrl.path, path) || // can always go back
|
||||||
|
@ -251,6 +258,7 @@ export default function(data: StudyData, ctrl: AnalyseCtrl, tagTypes: TagTypes,
|
||||||
onJump() {
|
onJump() {
|
||||||
chapters.localPaths[vm.chapterId] = ctrl.path;
|
chapters.localPaths[vm.chapterId] = ctrl.path;
|
||||||
if (practice) practice.onJump();
|
if (practice) practice.onJump();
|
||||||
|
if (gamebookPlay) gamebookPlay.onJump();
|
||||||
},
|
},
|
||||||
withPosition(obj) {
|
withPosition(obj) {
|
||||||
obj.ch = vm.chapterId;
|
obj.ch = vm.chapterId;
|
||||||
|
|
|
@ -57,12 +57,12 @@ export function removeChild(parent: Tree.Node, id: string): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function countChildrenAndComments(node: Tree.Node) {
|
export function countChildrenAndComments(node: Tree.Node) {
|
||||||
var count = {
|
const count = {
|
||||||
nodes: 1,
|
nodes: 1,
|
||||||
comments: (node.comments || []).length
|
comments: (node.comments || []).length
|
||||||
};
|
};
|
||||||
node.children.forEach(function(child) {
|
node.children.forEach(function(child) {
|
||||||
var c = countChildrenAndComments(child);
|
const c = countChildrenAndComments(child);
|
||||||
count.nodes += c.nodes;
|
count.nodes += c.nodes;
|
||||||
count.comments += c.comments;
|
count.comments += c.comments;
|
||||||
});
|
});
|
||||||
|
@ -70,11 +70,11 @@ export function countChildrenAndComments(node: Tree.Node) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function reconstruct(parts: any): Tree.Node {
|
export function reconstruct(parts: any): Tree.Node {
|
||||||
var root = parts[0],
|
const root = parts[0], nb = parts.length;
|
||||||
node = root;
|
let node = root, i: number;
|
||||||
root.id = '';
|
root.id = '';
|
||||||
for (var i = 1, nb = parts.length; i < nb; i++) {
|
for (i = 1; i < nb; i++) {
|
||||||
var n = parts[i];
|
const n = parts[i];
|
||||||
if (node.children) node.children.unshift(n);
|
if (node.children) node.children.unshift(n);
|
||||||
else node.children = [n];
|
else node.children = [n];
|
||||||
node = n;
|
node = n;
|
||||||
|
@ -90,11 +90,11 @@ export function merge(n1: Tree.Node, n2: Tree.Node): void {
|
||||||
n2.comments && n2.comments.forEach(function(c) {
|
n2.comments && n2.comments.forEach(function(c) {
|
||||||
if (!n1.comments) n1.comments = [c];
|
if (!n1.comments) n1.comments = [c];
|
||||||
else if (!n1.comments.filter(function(d) {
|
else if (!n1.comments.filter(function(d) {
|
||||||
return d.text === c.text;
|
return d.text === c.text;
|
||||||
}).length) n1.comments.push(c);
|
}).length) n1.comments.push(c);
|
||||||
});
|
});
|
||||||
n2.children.forEach(function(c) {
|
n2.children.forEach(function(c) {
|
||||||
var existing = childById(n1, c.id);
|
const existing = childById(n1, c.id);
|
||||||
if (existing) merge(existing, c);
|
if (existing) merge(existing, c);
|
||||||
else n1.children.push(c);
|
else n1.children.push(c);
|
||||||
});
|
});
|
||||||
|
@ -112,7 +112,7 @@ export function mainlineNodeList(from: Tree.Node): Tree.Node[] {
|
||||||
|
|
||||||
export function updateAll(root: Tree.Node, f: (node: Tree.Node) => void): void {
|
export function updateAll(root: Tree.Node, f: (node: Tree.Node) => void): void {
|
||||||
// applies f recursively to all nodes
|
// applies f recursively to all nodes
|
||||||
var update = function(node: Tree.Node) {
|
function update(node: Tree.Node) {
|
||||||
f(node);
|
f(node);
|
||||||
node.children.forEach(update);
|
node.children.forEach(update);
|
||||||
};
|
};
|
||||||
|
|
|
@ -29,6 +29,7 @@ export interface TreeWrapper {
|
||||||
merge(tree: Tree.Node): void;
|
merge(tree: Tree.Node): void;
|
||||||
removeCeval(): void;
|
removeCeval(): void;
|
||||||
removeComputerVariations(): void;
|
removeComputerVariations(): void;
|
||||||
|
parentNode(path: Tree.Path): Tree.Node;
|
||||||
getParentClock(node: Tree.Node, path: Tree.Path): Tree.Clock | undefined;
|
getParentClock(node: Tree.Node, path: Tree.Path): Tree.Clock | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,9 +149,7 @@ export function build(root: Tree.Node): TreeWrapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteNodeAt(path: Tree.Path): void {
|
function deleteNodeAt(path: Tree.Path): void {
|
||||||
var parent = nodeAtPath(treePath.init(path));
|
ops.removeChild(parentNode(path), treePath.last(path));
|
||||||
var id = treePath.last(path);
|
|
||||||
ops.removeChild(parent, id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function promoteAt(path: Tree.Path, toMainline: boolean): void {
|
function promoteAt(path: Tree.Path, toMainline: boolean): void {
|
||||||
|
@ -198,12 +197,16 @@ export function build(root: Tree.Node): TreeWrapper {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parentNode(path: Tree.Path): Tree.Node {
|
||||||
|
return nodeAtPath(treePath.init(path));
|
||||||
|
}
|
||||||
|
|
||||||
function getParentClock(node: Tree.Node, path: Tree.Path): Tree.Clock | undefined {
|
function getParentClock(node: Tree.Node, path: Tree.Path): Tree.Clock | undefined {
|
||||||
if (!('parentClock' in node)) {
|
if (!('parentClock' in node)) {
|
||||||
var parent = path && nodeAtPath(treePath.init(path));
|
const par = path && parentNode(path);
|
||||||
if (!parent) node.parentClock = node.clock;
|
if (!par) node.parentClock = node.clock;
|
||||||
else if (!('clock' in parent)) node.parentClock = undefined;
|
else if (!('clock' in par)) node.parentClock = undefined;
|
||||||
else node.parentClock = parent.clock;
|
else node.parentClock = par.clock;
|
||||||
}
|
}
|
||||||
return node.parentClock;
|
return node.parentClock;
|
||||||
}
|
}
|
||||||
|
@ -259,6 +262,7 @@ export function build(root: Tree.Node): TreeWrapper {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
parentNode,
|
||||||
getParentClock
|
getParentClock
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue