This repository has been archived by the owner. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 41
/
url-sync.js
131 lines (115 loc) · 3.57 KB
/
url-sync.js
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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/* Keeps the URL in sync with the navigator store */
import {
getters as moreInfoGetters,
actions as moreInfoActions,
} from '../more-info';
import {
getters as viewGetters,
actions as viewActions,
} from '../view';
import { activePanelName } from './getters';
import { navigate } from './actions';
const IS_SUPPORTED = history.pushState && !__DEMO__;
const PAGE_TITLE = 'Home Assistant';
const SYNCS = {};
function getSync(reactor) {
return SYNCS[reactor.hassId];
}
function pageState(pane, view) {
const state = { pane };
if (pane === 'states') {
state.view = view || null;
}
return state;
}
function pageUrl(pane, view) {
return pane === 'states' && view ?
`/${pane}/${view}` : `/${pane}`;
}
function initialSync(reactor) {
let pane;
let view;
// store current state in url or set state based on url
if (window.location.pathname === '/') {
pane = reactor.evaluate(activePanelName);
view = reactor.evaluate(viewGetters.currentView);
} else {
const parts = window.location.pathname.substr(1).split('/');
pane = parts[0];
view = parts[1];
reactor.batch(() => {
navigate(reactor, pane);
if (view) {
viewActions.selectView(reactor, view);
}
});
}
history.replaceState(pageState(pane, view), PAGE_TITLE, pageUrl(pane, view));
}
function popstateChangeListener(reactor, ev) {
const { pane, view } = ev.state;
if (reactor.evaluate(moreInfoGetters.hasCurrentEntityId)) {
getSync(reactor).ignoreNextDeselectEntity = true;
moreInfoActions.deselectEntity(reactor);
} else if (pane !== reactor.evaluate(activePanelName) ||
view !== reactor.evaluate(viewGetters.currentView)) {
reactor.batch(() => {
navigate(reactor, pane);
if (view !== undefined) {
viewActions.selectView(reactor, view);
}
});
}
}
export function startSync(reactor) {
if (!IS_SUPPORTED) {
return;
}
initialSync(reactor);
const sync = {
ignoreNextDeselectEntity: false,
popstateChangeListener: popstateChangeListener.bind(null, reactor),
unwatchNavigationObserver: reactor.observe(activePanelName, (pane) => {
if (pane !== history.state.pane) {
history.pushState(pageState(pane, history.state.view), PAGE_TITLE,
pageUrl(pane, history.state.view));
}
}),
unwatchViewObserver: reactor.observe(viewGetters.currentView, (view) => {
if (view !== history.state.view) {
history.pushState(pageState(history.state.pane, view), PAGE_TITLE,
pageUrl(history.state.pane, view));
}
}),
unwatchMoreInfoObserver: reactor.observe(
moreInfoGetters.hasCurrentEntityId,
(moreInfoEntitySelected) => {
if (moreInfoEntitySelected) {
history.pushState(history.state, PAGE_TITLE, window.location.pathname);
} else if (sync.ignoreNextDeselectEntity) {
sync.ignoreNextDeselectEntity = false;
} else {
// Otherwise SELECT_ENTITY: null will happen 2+ times on Firefox
setTimeout(() => history.back(), 0);
}
}
),
};
SYNCS[reactor.hassId] = sync;
// keep state in sync when url changes via forward/back buttons
window.addEventListener('popstate', sync.popstateChangeListener);
}
export function stopSync(reactor) {
if (!IS_SUPPORTED) {
return;
}
const sync = getSync(reactor);
if (!sync) {
return;
}
sync.unwatchNavigationObserver();
sync.unwatchViewObserver();
sync.unwatchMoreInfoObserver();
window.removeEventListener('popstate', sync.popstateChangeListener);
SYNCS[reactor.hassId] = false;
}