-
Notifications
You must be signed in to change notification settings - Fork 59
/
stateQueueManager.ts
110 lines (93 loc) · 3.48 KB
/
stateQueueManager.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
/** @module state */ /** for typedoc */
import { inArray } from '../common/common';
import { isString } from '../common/predicates';
import { StateDeclaration, _StateDeclaration } from './interface';
import { StateObject } from './stateObject';
import { StateBuilder } from './stateBuilder';
import { StateRegistryListener, StateRegistry } from './stateRegistry';
import { Disposable } from '../interface';
import { UrlRouter } from '../url/urlRouter';
import { prop } from '../common/hof';
import { StateMatcher } from './stateMatcher';
/** @internalapi */
export class StateQueueManager implements Disposable {
queue: StateObject[];
matcher: StateMatcher;
constructor(
private $registry: StateRegistry,
private $urlRouter: UrlRouter,
public states: { [key: string]: StateObject },
public builder: StateBuilder,
public listeners: StateRegistryListener[],
) {
this.queue = [];
this.matcher = $registry.matcher;
}
/** @internalapi */
dispose() {
this.queue = [];
}
register(stateDecl: _StateDeclaration) {
const queue = this.queue;
const state = StateObject.create(stateDecl);
const name = state.name;
if (!isString(name)) throw new Error('State must have a valid name');
if (this.states.hasOwnProperty(name) || inArray(queue.map(prop('name')), name))
throw new Error(`State '${name}' is already defined`);
queue.push(state);
this.flush();
return state;
}
flush() {
const { queue, states, builder } = this;
const registered: StateObject[] = [], // states that got registered
orphans: StateObject[] = [], // states that don't yet have a parent registered
previousQueueLength = {}; // keep track of how long the queue when an orphan was first encountered
const getState = name => this.states.hasOwnProperty(name) && this.states[name];
const notifyListeners = () => {
if (registered.length) {
this.listeners.forEach(listener => listener('registered', registered.map(s => s.self)));
}
};
while (queue.length > 0) {
const state: StateObject = queue.shift();
const name = state.name;
const result: StateObject = builder.build(state);
const orphanIdx: number = orphans.indexOf(state);
if (result) {
const existingState = getState(name);
if (existingState && existingState.name === name) {
throw new Error(`State '${name}' is already defined`);
}
const existingFutureState = getState(name + '.**');
if (existingFutureState) {
// Remove future state of the same name
this.$registry.deregister(existingFutureState);
}
states[name] = state;
this.attachRoute(state);
if (orphanIdx >= 0) orphans.splice(orphanIdx, 1);
registered.push(state);
continue;
}
const prev = previousQueueLength[name];
previousQueueLength[name] = queue.length;
if (orphanIdx >= 0 && prev === queue.length) {
// Wait until two consecutive iterations where no additional states were dequeued successfully.
// throw new Error(`Cannot register orphaned state '${name}'`);
queue.push(state);
notifyListeners();
return states;
} else if (orphanIdx < 0) {
orphans.push(state);
}
queue.push(state);
}
notifyListeners();
return states;
}
attachRoute(state: StateObject) {
if (state.abstract || !state.url) return;
this.$urlRouter.rule(this.$urlRouter.urlRuleFactory.create(state));
}
}